diff --git a/apps/find-orphans/.env.seed.example b/apps/find-orphans/.env.seed.example new file mode 100644 index 0000000000..6fb7e26f23 --- /dev/null +++ b/apps/find-orphans/.env.seed.example @@ -0,0 +1,44 @@ +# Configuration for scripts/seed-test-data.mjs. Copy to .env.seed and fill in +# the target space. The access token is read from .env if it is not set here. +# Use a dedicated TEST space: cleanup deletes by the seedTestData tag and +# removes the seedOrphanTest content type. + +# CMA token with write access to the space below. Falls back to the +# CONTENTFUL_ACCESS_TOKEN in .env when omitted. +# CONTENTFUL_ACCESS_TOKEN= + +CONTENTFUL_SPACE_ID= +CONTENTFUL_ENVIRONMENT_ID=master + +# --- What to create (all optional; defaults shown) ------------------------ +# Every item is tagged `seedTestData` so cleanup can remove it exactly. + +# Drafts with no title: found by the "untitled" scan criterion. +SEED_UNTITLED_DRAFTS=200 + +# Drafts with a title and no inbound links: found by "unreferenced" only. +SEED_TITLED_DRAFTS=100 + +# Titled drafts that published container entries link to: these must be +# EXCLUDED by the unreferenced scan, which is what proves the link check. +SEED_REFERENCED_DRAFTS=25 + +# Published filler entries, excluded from every scan. Raise this to test +# whether total space size affects scan behavior: it adds no API calls (the +# draft filter is server-side) but grows the corpus the links_to_entry +# reference searches run over, and it counts toward the plan record limit. +SEED_PUBLISHED_ENTRIES=0 + +# Untitled, fileless draft assets with no inbound links: found by both +# criteria when asset scanning is enabled. +SEED_DRAFT_ASSETS=50 + +# Untitled draft assets that containers link to: excluded by "unreferenced". +SEED_REFERENCED_ASSETS=10 + +# Fraction of untitled/titled drafts that get a second save, so the scan's +# "never edited" filter has both version-1 and edited items to distinguish. +SEED_EDITED_FRACTION=0.25 + +# Concurrent CMA requests, capped at 7 (the per-space rate limit per second). +SEED_CONCURRENCY=5 diff --git a/apps/find-orphans/.gitignore b/apps/find-orphans/.gitignore new file mode 100644 index 0000000000..42daf2f098 --- /dev/null +++ b/apps/find-orphans/.gitignore @@ -0,0 +1,3 @@ +# Seed-script config holds a CMA token and a test space id; keep it local. +# (.env is already ignored by the repo-root .gitignore.) +.env.seed diff --git a/apps/find-orphans/AGENTS.md b/apps/find-orphans/AGENTS.md new file mode 100644 index 0000000000..e92e348482 --- /dev/null +++ b/apps/find-orphans/AGENTS.md @@ -0,0 +1,302 @@ +# Agent Guide — find-orphans + +> Claude Code does not read AGENTS.md directly — the one-line `CLAUDE.md` beside this file +> imports it (`@AGENTS.md`) so it loads automatically every session. Keep both files. + +## What This App Does +Full-page app that scans an environment for "orphaned" draft entries and media assets. Two +criteria, each in its own tab with its own scan button and its own cached results: + +- **Untitled**: a draft (never published, not archived) with no title value in the default + locale — typically created by accident from a reference field. These are confident junk. +- **Unreferenced**: a draft that no entry links to (`links_to_entry` / `links_to_asset` count + is 0). A review lead, NOT proof of junk — top-level content (landing pages) is legitimately + unreferenced. Copy must never claim these are safe to delete. + +Scans are broad: edit history NEVER excludes results at scan time. Every result carries a +`neverEdited` flag (`sys.version === 1`) and the UNTITLED tab's results header has a segmented +view switch — "Show: All (N) / Never edited (M)" ToggleButton pills, exactly one always +pressed, clicking the active pill is a no-op — starting on Never edited (hardcoded; the +`untouchedOnly` parameter that once set this default is retired). The unreferenced tab has NO +never-edited switch at all (decided 2026-07-13: edit history says nothing about +referenced-ness). Keep it this way: silent scan-time +exclusion was tried first and read as "the scan is broken" when rows appeared in one tab and +not the other, and a single on/off toggle was tried next and read as ambiguous. + +The scan scope (entries and/or assets) is a per-run checkbox choice, both on by default. +Results deep-link into the matching editor, can be archived in bulk, and can be exported to +CSV (with editor-link column) for offline review. + +## Archetype +Standard Vite app. Page location plus an app-config screen for installation parameters. + +## Command Run-book + +| Task | Command | Needs | +|------|---------|-------| +| Dev server (localhost:3000) | `npm start` | Install the app in a space pointing at localhost, or you get the LocalhostWarning | +| Tests: watch / single run | `npm test` / `npm run test:ci` | — | +| Lint / lint with auto-fix | `npm run lint` / `npm run lint:fix` | ESLint 9 flat config (`eslint.config.mjs`); warnings fail the run | +| Production bundle | `npm run build` | — | +| Upload bundle to the app definition | `npm run build && npm run upload` | `.env` | +| Sync installation parameter definitions | `npm run update-app-parameters` | `.env`; run after editing the parameter list in `scripts/update-app-parameters.mjs` | +| Seed test data / remove it | `npm run seed-test-data` / `npm run seed-test-data:cleanup` | `.env.seed` | + +## Credentials & Env Files + +| File | Holds | Git status | +|------|-------|------------| +| `.env` | `CONTENTFUL_ACCESS_TOKEN` (CMA token), `CONTENTFUL_ORG_ID`, `CONTENTFUL_APP_DEF_ID` — the app definition is "Orphan Finder" | ignored (repo-root `.gitignore`) | +| `.env.seed` | `CONTENTFUL_SPACE_ID`, `CONTENTFUL_ENVIRONMENT_ID`, `SEED_*` knobs; token falls back to `.env` | ignored (this app's `.gitignore`) | +| `.env.seed.example` | Documented template for `.env.seed` | committed | +| `~/.contentfulrc.json` | `managementToken` from Contentful CLI login — fallback token source if `.env` is missing one | outside the repo | + +The CMA rate limit is 7 requests/second per space — Shanon's test spaces have unlimited +entries but the STANDARD rate limit; never raise a concurrency knob past 7. + +## Locations + +| Location | File | Purpose | +|----------|------|---------| +| `LOCATION_PAGE` | `src/locations/Page/` | Scan + results table + bulk archive | +| `LOCATION_APP_CONFIG` | `src/locations/ConfigScreen.tsx` | Installation parameters | + +## Installation Parameters + +Defined in `src/parameters.ts`; the constants in `src/locations/Page/utils/constants.ts` are the +defaults. All parameters are **required** on the app definition, with their default values set +there too — the ConfigScreen pre-fills the defaults and rejects saves with empty or out-of-range +values. `resolveParameters()` remains the defensive fallback on the Page side, so a scan never +runs with missing or invalid settings. + +| Parameter ID | Type | Required | Default | Purpose | +|--------------|------|----------|---------|---------| +| `maxCandidates` | Number | Yes | 500 | Hard cap on candidate entries per scan | +| `batchSize` | Number | Yes | 5 (max 7) | Concurrent CMA requests (scan queries, archiving) | + +Retired parameters (do not reintroduce): `defaultStaleDays`, `referenceBatchSize` (pre-2026-07-09 +design) and `untouchedOnly` (2026-07-13, briefly the never-edited default; retired because the +visible "All / Never edited" switch made an admin-level default pointless). Old installations +may still store their values — `resolveParameters()` ignores unknown keys. + +Parameter definitions on the app definition must use these exact IDs and types. They are +scripted: `npm run update-app-parameters` (scripts/update-app-parameters.mjs) replaces the app +definition's installation parameters via the CMA — keep that script, `src/parameters.ts`, and +the README table in sync when parameters change. + +## Test Data Seeding + +`npm run seed-test-data` (scripts/seed-test-data.mjs) fills a test environment with known +quantities of each orphan shape — untitled drafts, titled unreferenced drafts, referenced +drafts linked from published containers, fileless draft assets, optional published filler — +and prints the expected scan results per criterion at the end. That summary is computed from +actual successful creates, not the configured counts: on partial failure it warns and adjusts — +notably, "referenced" items whose container failed to CREATE count as unreferenced, while a +container that was created but failed to PUBLISH still references its targets (`links_to_entry` +counts links from drafts) and itself appears as one extra unreferenced titled draft. Config +lives in `.env.seed` +(copy `.env.seed.example`; the CMA token falls back to `.env`). Everything it creates is +tagged `seedTestData` under content type `seedOrphanTest`, so `npm run seed-test-data:cleanup` +removes it exactly. The script is additive across runs; cleanup between runs for exact counts. +Published filler adds no API calls to a scan (draft filters are server-side) — it exists to +grow the corpus the `links_to_entry` searches run over. + +## Key Dependencies + +| Package | Role | +|---------|------| +| `@contentful/app-sdk` | App Framework SDK (`sdk.cma`, `sdk.navigator`) | +| `@contentful/f36-components` | Forma 36 UI | +| `@contentful/react-apps-toolkit` | `useSDK()` hook | +| `contentful-management` | CMA types (`ContentTypeProps`, `EntryProps`) | + +## Source Layout + +``` +src/ +├── App.tsx +├── parameters.ts # Installation parameter types, defaults, resolver +├── components/ # LocalhostWarning +└── locations/ + ├── ConfigScreen.tsx # Installation parameter form + └── Page/ + ├── index.tsx # Page UI, scan orchestration, scope checkboxes + ├── types.ts # OrphanResult (entry|asset union) / progress types + ├── components/ # OrphanTable + └── utils/ + ├── constants.ts # Fallback defaults for parameters + ├── entryActions.ts # Batched bulk archive, kind-routed (pure, unit-tested) + ├── exportCsv.ts # CSV building + download trigger (pure, unit-tested) + └── orphanFinder.ts # All CMA query logic, entries + assets (pure, unit-tested) +``` + +## Sharp Edges & Invariants + +- **Draft scope is fixed and filtered server-side**: every entry and asset query includes + `sys.publishedAt[exists]=false` and `sys.archivedAt[exists]=false`. The remaining criteria are + evaluated client-side. +- **The empty-title check must stay client-side**: the API's `[exists]` operator misses + whitespace-only titles, and with a `select` query the CMA omits the `fields` object entirely + from items that have no value in any selected field — both shapes count as orphans. +- **The version check must stay client-side too**: `sys.version` is not a queryable attribute in + CMA searches, but it rides along in every returned `sys` object, so it costs nothing. + Version 1 means "never saved after creation" only because published and archived items are + already excluded — publish/unpublish/archive/unarchive (and asset file processing) also bump + `sys.version`. +- **Default-locale only**: the missing-title check and title rendering use `sdk.locales.default`. + Localized titles in other locales are not considered. +- **Content types without a text display field are skipped by the untitled scan** — including + types with NO display field configured (component types): their entries always render as + "Untitled", but flagging every draft of them would sweep in legitimate work-in-progress. + This, plus the "Never edited" filter (and the fact that archive→unarchive round-trips bump + `sys.version`), is why an "Untitled" row can appear in unreferenced results and not on the + untitled tab. That divergence is BY DESIGN and pinned in tests — investigated 2026-07-13 + after it was mistaken for a regression; do not "fix" it without Shanon deciding to change + the criteria. +- **Every view-narrowing control must narrow the selection too** — the "Never edited" pills + (`setNeverEditedFilter`) and the scope checkboxes (`setScope`, which live-filter cached + results client-side as well as scoping the next scan; unchecking prunes selections in BOTH + tabs since scope is shared). Otherwise "Archive selected" could archive rows the user can no + longer see. When filters hide every result, render the explanatory `filtered-empty-note` + naming the responsible filter, never a bare empty table. +- **Pagination is navigation, NOT a view-narrowing control** (added 2026-07-15): the results + table pages client-side at `RESULTS_PAGE_SIZE` (50) via f36 `Pagination` — no API calls, all + results are already in memory. Select-all (the header checkbox) deliberately spans EVERY + page so "archive all N" is one click; the count line and the itemized archive confirmation + are the guards. Do not prune the selection on page change. The page index lives in + `TabState.page` (survives tab switches), resets to 0 whenever the displayed set is reshaped + (new scan via `SCAN_RESET`, scope change, never-edited switch), and is CLAMPED at render + time — not written back — so rows vanishing beneath the current page (bulk archive of the + last page) can never strand the view on an empty page. The control renders only when the + displayed set exceeds one page. +- **The CSV export is broad ON PURPOSE and must say so** (added 2026-07-15): "Export CSV" + downloads the tab's FULL cached results — including rows hidden by the scope checkboxes and + the never-edited view — with `Kind` and `Edited after creation` as columns, so filtering + happens visibly in the spreadsheet, never silently in the file (same philosophy as the broad + scan). The edit-history column is deliberately positive-phrased (Shanon, 2026-07-15: a + "Never edited" yes/no column read as a confusing double negative); the UI's "Never edited" + view maps to `Edited after creation = no`. Keep cell values plain yes/no. + Because it ignores the visible filters, the button's tooltip must announce that; don't + quietly change the export to the displayed subset. Titles export as EMPTY cells (not an + "Untitled" placeholder) so blank-title spreadsheet filters match exactly the untitled + results. `exportCsv.ts` is pure (no React) and unit-tested; keep the RFC 4180 quoting, CRLF + line endings, the UTF-8 BOM (Excel mojibake otherwise), and the formula-injection guard + (leading `=`/`+`/`-`/`@` gets a literal-text apostrophe — titles are content-controlled). + Each row carries an Editor URL deep link (`…/entries/{id}` / `…/assets/{id}`, alias-aware). +- **No header info tooltip**: the page heading is plain (removed 2026-07-13); the app explains + itself via the general subtitle and each tab's visible description. +- **One entry query per content type is unavoidable** (entry queries require a single + `content_type`), so the scan runs those queries `batchSize` at a time in parallel chunks. A + chunk shares one remaining-budget snapshot and can overshoot `maxCandidates`; the result is + trimmed and flagged truncated afterwards. +- **Assets are one paged query** — they are homogeneous (no content type), so the whole media + library is a single scan step that runs after the entry phase and spends whatever remains of + the `maxCandidates` budget. This is why one scan button with scope checkboxes beats separate + entry/asset CTAs: the asset step is marginal next to the entry fan-out. +- **Reference counting is N+1 and must stay batched**: the CMA filters `links_to_entry` / + `links_to_asset` by ONE target id per query, so the unreferenced criterion costs one + `limit: 0` count request per candidate, run `batchSize` at a time (`filterUnreferenced`). + Never fan these out unbatched, and keep the cost warning in the unreferenced button's + tooltip. The + untitled criterion's content-type restriction (text display fields only) does NOT apply to + the unreferenced scan — any entry can be a link target. +- **One criterion per scan, each criterion is a tab** (chosen 2026-07-13, evolving radio → + two buttons → tabs; the OR-combined design was removed on 2026-07-09): results always have a + single unambiguous meaning, only unreferenced scans pay the reference-count cost, and + separate tabs keep the confident-junk vs review-lead expectations apart. +- **Per-tab state is cached and must survive tab switches** (`tabStates: Record` — results, truncation, selection): switching tabs NEVER clears results or re-runs + a scan; tab labels show the cached counts. Scans are exclusive (one `activeScan` at a time) + and the scope checkboxes are shared across tabs. +- **Criterion explanations are visible text inside each tab's panel**, above the scan button — + NOT tooltips on the tab labels (tried and removed 2026-07-13: they duplicated the adjacent + visible description). The unreferenced description must lead with the one-request-per-draft + cost warning. The archive button keeps its tooltip with an `InfoIcon` end-icon as the hover + cue; the archive deep links live in the confirmation modal (a hover tooltip cannot hold + clickable links). +- **`maxCandidates` caps each scan across both scopes** — the UI shows a truncation warning. + Keep the cap or replace it with real pagination, but never scan unbounded. +- **Creator names are best-effort**: `resolveCreatorNames` batches `user.getManyForSpace` + lookups (`sys.id[in]`, 100 ids per request) and swallows failures — unresolved creators render + as "Unknown user", non-User `sys.createdBy` links (apps/automations) as "App". Never let a + users-endpoint failure fail the scan. The results table shows **Created** (`sys.createdAt`), + not last-updated: orphans are by default never edited after creation. +- **Archiving routes by kind**: `archiveOrphans` calls `cma.entry.archive` or + `cma.asset.archive` per target — `OrphanResult.kind` must survive any refactor of the result + shape, and preview likewise routes to `openEntry`/`openAsset`. +- **Mixed-kind selection is guarded twice, keep both**: the scope checkboxes (hide AND + deselect a kind, so "archive only entries" = uncheck assets + select-all) and an archive + confirmation that itemizes the selection by kind ("Archive 19 entries and 4 assets"). + Kind-scoped select-all ToggleButtons existed but were removed 2026-07-13 as redundant once + the scope checkboxes became live filters — select-all deliberately spans every displayed + kind, and per-kind result tables were also considered and rejected. +- **Pagination order needs the `sys.id` tiebreaker**: entries sharing an `updatedAt` (bulk + imports) sort non-deterministically otherwise, and skip-based paging can drop or duplicate + them. +- **`resolveParameters()` clamps `batchSize` to 7** (the CMA req/s limit per space) — keep that + clamp. +- **Never read `sdk.parameters.installation` directly** — always go through `resolveParameters()` + so fresh installs and hand-edited values fall back to safe defaults. +- **`sdk.app.setReady()` must stay in the ConfigScreen init effect** — without it the config + screen never leaves its loading state. +- **Keep `orphanFinder.ts` free of React/UI imports** — it is the unit-tested core; the Page + component only orchestrates state and rendering. +- **README (including the mermaid diagram) must track logic changes** — the scan criteria are + documented in three places that must agree: README, the Page intro paragraph, and the + ConfigScreen help texts. +- **Docs are Shanon's run-book — update them unprompted**: whenever behavior, parameters, or + tooling (scripts, npm commands, env files) change, update README.md and this file in the + same change. Shanon relies on these to remember how things work and how to run them. + +## Change Checklist + +When you change one thing, these must move with it (Shanon relies on docs staying true): + +| Changed... | Also update... | +|------------|----------------| +| Scan logic or criteria | README (including the mermaid diagram), the Page intro/tab copy, this file | +| Installation parameters | `scripts/update-app-parameters.mjs` (then RUN `npm run update-app-parameters`), `src/parameters.ts`, ConfigScreen, README table, the table above | +| npm scripts, env files, tooling | README Development section, the Run-book and env tables above | +| Any of the above | Claude's memory files (standing instruction — unprompted) | + +## Marketplace Submission (Standard Contribution) + +Source: "Field Team Guide - Requirements for building and submitting apps" (Confluence, by +Jenny Dobner): . Submission is a PR to the Marketplace Apps (MPA) +repo with the app name in the PR title; after acceptance the Marketplace team owns the code +(no ongoing contributor obligations). Status against the guide's requirements: + +| Requirement | Status | +|-------------|--------| +| TypeScript | Met — TS 5.9 throughout; `npx tsc --noEmit` is clean (2026-07-15) and must stay that way even though vite/vitest don't typecheck | +| Lint script in package.json | Met (2026-07-15) — `npm run lint`, ESLint 9 flat config in `eslint.config.mjs` modeled on apps/auto-prefix plus react-hooks; `--max-warnings 0` | +| Test script, no `--passWithNoTests` | Met — `npm test` (watch) / `npm run test:ci`, 69 real tests | +| Build and start scripts | Met — `npm start` / `npm run build` | +| README | Met — kept current per the Change Checklist above | +| No boilerplate/placeholder code | Met — LocalhostWarning is the standard scaffold component | +| OWASP basics / no hardcoded secrets | Met — tokens live in gitignored `.env`* files; CSV export even guards spreadsheet formula injection | +| Dependencies up to date | Met within majors (2026-07-15) — in-range bumps applied, TS on 5.9, `npm audit` clean. Major upgrades deliberately deferred as real migrations: React 19, f36 v6, vite 8, vitest 4, react-apps-toolkit 2, contentful-management 12 | +| Secret detection | Met — nothing sensitive committed; `.env` / `.env.seed` are gitignored | +| Bundle size ≤ 10 MB | Met — production bundle ~1.7 MB | +| Static site / no SSR | Met — plain Vite client-side build, uploadable via `npm run upload` | +| Accessibility basics | Met — f36 components plus explicit aria-labels on icon-only controls | +| App name in package.json | `find-orphans` — decide before the PR whether the marketplace name should match the app definition ("Orphan Finder") | + +## Testing Notes + +- `orphanFinder.ts` and `entryActions.ts` are the pure, unit-tested core — new query or + archive logic gets tests there, not through the Page component. +- f36 `Tabs` activate on **mousedown**, not click — use the `switchTab` helper in + `test/locations/Page.test.tsx` (fires both events) rather than a bare `fireEvent.click`. +- The untitled-vs-unreferenced result divergence (see Sharp Edges) is pinned in tests on + purpose; a "failing" test there usually means the divergence rule was broken, not the test. + +## Never / Always + +- **Never** perform bulk actions without an explicit confirmation dialog — archiving goes through + `ModalConfirm`, and any future delete action must do the same. +- **Never** abort a bulk archive on the first failure — `archiveEntries` collects failures so the + remaining entries still get processed; failed entries stay listed and selected for retry. +- **Always** surface CMA failures via Forma 36 `Notification.error` and empty states via `Note`. +- **Always** keep row clicks selection-only — opening the slide-in editor is a dedicated + "Preview" action so accidental clicks never navigate. diff --git a/apps/find-orphans/CLAUDE.md b/apps/find-orphans/CLAUDE.md new file mode 100644 index 0000000000..43c994c2d3 --- /dev/null +++ b/apps/find-orphans/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/apps/find-orphans/README.md b/apps/find-orphans/README.md new file mode 100644 index 0000000000..9867bfcde9 --- /dev/null +++ b/apps/find-orphans/README.md @@ -0,0 +1,241 @@ +# Find Orphans + +A Contentful App Framework **page app** that finds orphaned draft entries and media assets, +under either of two definitions of "orphan": + +- **Untitled drafts** — items likely created by mistake, the classic case being clicking + "Create new entry" (or "Add new media") on a reference field when you meant to link an + existing item, leaving behind an empty, untitled draft. These are confident junk. +- **Unreferenced drafts** — items that no entry links to. This is a *lead*, not a verdict: + top-level content such as landing pages is often never referenced yet perfectly valid, so + these results are for review, possibly-unused content. + +## How it works + +Each criterion lives in **its own tab** with its own scan button and its own cached results — +separate scans because the two result sets carry different expectations (archive-freely junk +versus review-first leads), and per-tab caching because comparing them should be free: run the +untitled scan, switch to the unreferenced tab and scan there, switch back, and the first +results are still sitting in their table, with the counts shown right on the tab labels. Every +scan covers **drafts** (never published, not archived). The page subtitle stays general; each +tab explains its own criterion with a description inside the panel, always visible above the +scan button — the unreferenced one leads with the warning that reference checking is one +network request per draft and can take several minutes on large spaces. (Tab labels carry only +the name and cached result count; switching tabs is free, so the description is never more than +a click away.) Two checkboxes control the scope (entries +and/or assets, both on by default, shared across tabs) — they decide what the next scan fetches +AND live-filter already-scanned results client-side, hiding (and deselecting) that kind's rows +without a re-scan. The per-content-type entry queries are the slow part of candidate +collection, so an assets-only pass is quick. + +**Untitled criterion**: flags drafts whose **title has no value** in the default locale — +including whitespace-only values, which the editors also render as "Untitled". For entries the +title is the content type's display field, and content types whose display field is not a +`Symbol`/`Text` field are skipped, since their entries cannot be missing a title. Assets have a +fixed shape, so their localized `title` field is checked directly and the whole media library +is a single paged query. + +**Unreferenced criterion**: flags drafts that **no entry links to**, using one +`links_to_entry` (or `links_to_asset`) count query per candidate — the CMA can only check +referenced-ness for one target id per request, so this scan costs one extra API call per draft +and is much slower on large spaces (its button's info tooltip says so). Every content type +participates (any entry can be a link target), results keep their real titles, and the +never-edited filter below does not apply — an unreferenced entry is worth flagging no matter +how often it was edited. + +Scans are **broad first, strict on demand**: edit history never excludes anything at scan time. +Instead, every result carries a never-edited flag and the untitled tab's results header has a +segmented view switch — **Show: "All (N)" / "Never edited (M)"** — where exactly one pill is +always pressed, so the current view and both counts are always visible. (The unreferenced tab +has no such switch: edit history says nothing about whether something is referenced.) The strict view narrows the table to +items never saved after creation (`sys.version === 1`); its pill carries a tooltip explaining +the term — including that archiving and unarchiving counts as editing, so restored orphans +appear only under "All", where they may still be worth archiving again. The CMA bumps `sys.version` on every save — and on publish, unpublish, +archive, unarchive and (for assets) file processing, but those states are already excluded by +the draft scope — so version 1 can only mean the item was created and then abandoned untouched. +Narrowing the view also narrows the selection, so the archive action can never touch a hidden +row, and when the filter hides every result a note says so instead of showing an empty table. +The untitled tab starts on the "Never edited" view — the confident-junk subset — with "All" one +click away; filtering is instant and client-side, never a re-scan. + +**Why the two lists can differ**: an "Untitled" row in the unreferenced results that the +untitled tab does not show is not a bug — it is one of two documented cases. Either the draft +was edited after creation and the "Never edited" filter is hiding it (the toggle shows the +count; note that **archiving and then unarchiving an item also bumps `sys.version`**, so +orphans that were archived and restored no longer count as never-edited), or the entry belongs +to a content type with **no display field configured** (common for component types like +banners) — such entries always render as "Untitled" in Contentful, and the untitled scan skips +those types deliberately, because flagging every draft of them would sweep in legitimate +work-in-progress. Both cases are pinned in tests. + +Neither criterion is something the regular Contentful search can express: title filters only +work for one content type at a time (every content type defines its own display field), and +referenced-ness is not a searchable attribute at all. The app automates both checks across the +whole content model. + +Results are listed in one table with their title (untitled-scan results show the editor's +"Untitled" placeholder; unreferenced ones show their real titles), type (the content type name, +or "Asset"), creation date, and creator, and the result count spells out the split (e.g. "19 +entries and 4 assets found"). The date shown is **created**, not last-updated — orphans were (by default) never +edited after creation, so the creation moment is what identifies the mistake. The creator comes +from `sys.createdBy`, resolved to names via one batched space-users lookup per scan; items +created by apps or automations show "App", and creators who cannot be resolved (left the space, +or the caller may not list users) show "Unknown user". Each row has an explicit "Preview" +action that opens the matching editor in a slide-in for review. + +Large result sets are paginated, 50 rows per page, so the results header (counts, view switch, +archive button) stays within reach. Pagination is purely client-side navigation — all results +are already in memory, so paging costs no API calls, and unlike the scope checkboxes and the +never-edited switch it does NOT narrow the selection: select-all selects every result across +all pages, so "archive all 300" stays one click (the count line and the itemized confirmation +spell out the full sweep). The page resets to the first whenever the displayed set is reshaped +(new scan, scope or view change) and clamps when rows vanish beneath it (archiving away the +last page). + +Each tab's results can also be downloaded as a CSV file ("Export CSV" next to the archive +button) for offline review. The export is broad like the scan: it always contains **every** +result the scan found — including rows currently hidden by the scope checkboxes or the +never-edited view, which is why the button's tooltip says so — with the flags in their own +columns so filtering happens visibly in the spreadsheet. Columns: Kind, ID, Title (blank for +untitled results, so a blank-title spreadsheet filter matches them), Type, Created, Created +by, Edited after creation (positive phrasing — a "Never edited" column would make +double-negative cells; the UI's "Never edited" view is this column filtered to "no"), and an +Editor URL deep link that opens the entry or asset editor directly. +Titles and creator names are quoted per RFC 4180 and leading `=`/`+`/`-`/`@` characters are +neutralized so content cannot smuggle spreadsheet formulas into the file. + +Rows can be selected individually or all at once; to act on one kind only, uncheck the other +kind's scope checkbox — that hides and deselects its rows — and then select all, so archiving +all entries never sweeps assets along. The selected items are archived in bulk after a +confirmation dialog that itemizes the selection by kind ("Archive 19 entries and 4 assets"); archiving runs in rate-limit-friendly batches +through the endpoint matching each item's kind, and items that fail to archive stay listed and +selected for retry. Archiving is reversible from the editor; the archive button's tooltip (on +its info end-icon) says so, and the confirmation dialog deep-links (in a new tab) to the web +app's archived-entries and archived-assets views — +`…/views/entries?filters.0.key=__status&filters.0.op=&filters.0.val=archived` — where permanent +deletion lives. + +Scans are capped at a configurable number of draft items per run (500 by default, entries and +assets sharing the one budget, see Configuration below) to stay friendly to CMA rate limits; a +warning is shown when the cap is hit. + +## Logic flow + +```mermaid +flowchart TD + Load[App loads] --> FetchCT[Fetch all content types
paged, 1000 per request] + FetchCT --> Scan([User runs a scan from its tab
untitled or unreferenced
with a scope: entries and/or assets]) + Scan --> Filter[Entries scope: untitled scans keep content types
with a Symbol/Text display field;
unreferenced scans keep all] + Filter --> Chunk[Take next batchSize content types] + Chunk --> Query[Query draft entries per content type in parallel:
never published, not archived,
select sys + display field only] + Query --> Pages[Page through results until exhausted
or the maxCandidates budget is spent] + Pages --> More{More content types
and budget left?} + More -- yes --> Chunk + More -- no --> AssetStep[Assets scope: one paged draft-asset query
spending the remaining budget,
select sys + title only] + AssetStep --> Criterion{Criterion?} + Criterion -- untitled --> Check[Client-side check: title empty,
whitespace-only, or fields object absent
in the default locale] + Check --> Resolve + Criterion -- unreferenced --> RefCount[One links_to_entry / links_to_asset
count query per candidate, batched batchSize
at a time; keep items with 0 references] + RefCount --> Resolve[Resolve creator names from sys.createdBy:
batched space-users lookup, tolerant of failure] + Resolve --> Results[Results table, cached per tab, paged 50 per view,
with a visible Never edited view switch
on the untitled tab: sys.version == 1
+ truncation warning if capped] + Results --> Archive([User selects rows and confirms Archive]) + Archive --> Batches[Archive in batches of batchSize,
entries and assets each via their endpoint] + Batches --> Done[Archived rows leave the list;
failed ones stay listed and selected for retry] +``` + +A scope that is unchecked for the run skips its block entirely: entries-only scans never query +the media library, and assets-only scans jump straight to the asset query. + +The empty-title check runs client-side rather than via the API's `[exists]` operator for two +reasons: `[exists]` misses whitespace-only titles, and items with no value in any selected +field come back from the CMA with no `fields` object at all — both shapes must count as +orphans. The version check is also client-side by necessity: `sys.version` is not a queryable +attribute in CMA searches, but it is part of every returned `sys` object, so the check costs no +extra API traffic. + +## Configuration + +The app configuration screen (space settings, Apps) exposes installation parameters. The +parameter definitions on the app definition must use exactly these values (the IDs must match +`AppInstallationParameters` in `src/parameters.ts`): + +| Display name | ID | Type | Required | Default value | Description | +|--------------|----|------|----------|---------------|-------------| +| Maximum entries per scan | `maxCandidates` | Number | Yes | `500` | The scan stops after this many draft entries and assets, to stay friendly to API rate limits. | +| Concurrent API requests | `batchSize` | Number | Yes | `5` | How many CMA requests run at once while scanning and archiving. Must be between 1 and 7, the CMA rate limit per second. | + +(A third parameter, `untouchedOnly`, existed briefly and was retired: it only chose the +untitled tab's starting view, which the visible "All / Never edited" switch makes a per-user, +one-click choice. `resolveParameters()` ignores it if an installation still stores it.) + +Rather than maintaining these by hand in the web UI, sync them with the bundled script — the +definitions above live in `scripts/update-app-parameters.mjs` as the single source of truth, +and the script replaces the app definition's installation parameters wholesale via the CMA: + +```bash +CONTENTFUL_ACCESS_TOKEN= \ +CONTENTFUL_ORG_ID= \ +CONTENTFUL_APP_DEF_ID= \ +npm run update-app-parameters +``` + +Run it whenever a parameter is added or its wording changes (the org and app definition ids are +under Organization settings → Apps in the web app). + +All parameters are required and their defaults are set on the parameter definition, so a fresh +install starts with the values above. The config screen shows the same defaults as input +placeholders, and its save validation rejects empty, non-positive, or out-of-range values. As a +safety net, `resolveParameters()` still falls back to the defaults at scan time if the stored +parameters are ever missing or invalid. + +## Development + +```bash +npm install +npm start # dev server on http://localhost:3000 +npm test # vitest watch mode +npm run test:ci # single test run +npm run lint # ESLint over the whole app, warnings fail (--max-warnings 0) +npm run lint:fix # same, applying auto-fixes +npm run build # production bundle in ./build +``` + +To install into a space, create an app definition with a **Page** location: + +```bash +npm run create-app-definition +``` + +### Seeding test data + +To exercise the scan against a space with many drafts, the seed script fills a +test environment with known quantities of untitled drafts, titled unreferenced +drafts, referenced drafts (linked from published container entries), fileless +draft assets, and optional published filler. Copy `.env.seed.example` to +`.env.seed`, point it at a **dedicated test space**, then: + +```bash +npm run seed-test-data # create the data (prints expected scan results) +npm run seed-test-data:cleanup # delete everything the script created +``` + +The expected-results summary at the end is computed from what was actually +created: if some requests fail (the script retries but a CMA outage can still +lose items), it prints a warning and adjusted per-criterion counts instead of +the configured ones — in particular, "referenced" items whose container entry +failed to create are counted as unreferenced, since nothing links to them. +The summary describes a single run into a clean environment; the script is +additive, so run cleanup between runs to keep the counts exact. + +Everything is tagged `seedTestData` and typed `seedOrphanTest`, so cleanup is +exact. Note that published volume adds no API calls to a scan — the draft +filters are server-side — but it does grow the corpus the `links_to_entry` +reference searches run over and counts toward the plan's record limit, which +is what the `SEED_PUBLISHED_ENTRIES` knob is for. + +## Learn more + +- [Contentful App Framework](https://www.contentful.com/developers/docs/extensibility/app-framework/) +- [Links and incoming-link queries](https://www.contentful.com/developers/docs/references/content-delivery-api/links/#links-to-a-specific-item) — background on `links_to_entry`; the field-filter variant documented there needs the linking content type and field to be known, which is why the unreferenced scan uses one `links_to_entry`/`links_to_asset` count query per item instead +- [Page location](https://www.contentful.com/developers/docs/extensibility/app-framework/locations/#page) +- [Forma 36](https://f36.contentful.com/) diff --git a/apps/find-orphans/eslint.config.mjs b/apps/find-orphans/eslint.config.mjs new file mode 100644 index 0000000000..02a48fc88e --- /dev/null +++ b/apps/find-orphans/eslint.config.mjs @@ -0,0 +1,59 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import pluginReact from 'eslint-plugin-react'; +import pluginReactHooks from 'eslint-plugin-react-hooks'; +import unusedImports from 'eslint-plugin-unused-imports'; +import { defineConfig, globalIgnores } from 'eslint/config'; + +// Modeled on apps/auto-prefix (the repo's ESLint 9 flat-config reference), +// plus react-hooks: this app is orchestrated almost entirely through +// useCallback/useMemo, so exhaustive-deps is the lint rule most likely to +// catch a real bug here. +export default defineConfig([ + globalIgnores(['**/build/']), + { + settings: { + react: { + version: 'detect', + }, + }, + }, + { + files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + plugins: { js }, + extends: ['js/recommended'], + languageOptions: { globals: globals.browser }, + }, + { + // The seed/upload scripts run under Node, not the browser. + files: ['scripts/**/*.mjs'], + languageOptions: { globals: globals.node }, + }, + tseslint.configs.recommended, + pluginReact.configs.flat.recommended, + pluginReactHooks.configs.flat['recommended-latest'], + { + plugins: { + 'unused-imports': unusedImports, + }, + rules: { + // The automatic JSX runtime makes React imports unnecessary. + 'react/jsx-uses-react': 'off', + 'react/react-in-jsx-scope': 'off', + // unused-imports subsumes the base rule and can auto-fix removals. + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'warn', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_', + }, + ], + }, + }, +]); diff --git a/apps/find-orphans/index.html b/apps/find-orphans/index.html new file mode 100644 index 0000000000..6cd2814bfa --- /dev/null +++ b/apps/find-orphans/index.html @@ -0,0 +1,12 @@ + + + + + + + + +
+ + + diff --git a/apps/find-orphans/package-lock.json b/apps/find-orphans/package-lock.json new file mode 100644 index 0000000000..99263c873f --- /dev/null +++ b/apps/find-orphans/package-lock.json @@ -0,0 +1,10289 @@ +{ + "name": "find-orphans", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "find-orphans", + "version": "0.1.0", + "dependencies": { + "@contentful/app-sdk": "^4.29.1", + "@contentful/f36-components": "4.79.1", + "@contentful/f36-icons": "^5.6.0", + "@contentful/f36-tokens": "4.2.0", + "@contentful/react-apps-toolkit": "1.2.16", + "contentful-management": "^11.52.0", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@contentful/app-scripts": "^2.3.0", + "@eslint/js": "^9.39.5", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^14.3.1", + "@types/node": "^22.13.5", + "@types/react": "18.3.13", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "^4.0.3", + "eslint": "^9.39.5", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-unused-imports": "^4.4.1", + "globals": "^17.7.0", + "jsdom": "^26.0.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.64.0", + "vite": "^6.4.1", + "vitest": "^3.0.9" + } + }, + "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==", + "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/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@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-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-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "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==", + "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-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-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==", + "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==", + "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/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.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.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/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "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==", + "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.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@contentful/app-scripts": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@contentful/app-scripts/-/app-scripts-2.5.12.tgz", + "integrity": "sha512-e6jim+S824WI9cpV0aUKmdI4rX5YHtVX6+MQZdbnGl+zD+K1mzqkP+Xk791PnyC2kQloy9XiMSKJ2OgoYgGobg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-plugins/node-globals-polyfill": "^0.2.3", + "@esbuild-plugins/node-modules-polyfill": "^0.2.2", + "@segment/analytics-node": "^3.0.0", + "adm-zip": "0.5.17", + "axios": "^1.15.2", + "bottleneck": "2.19.5", + "chalk": "4.1.2", + "commander": "12.1.0", + "contentful-management": "^11.75.0", + "dotenv": "17.4.2", + "esbuild": "^0.28.0", + "ignore": "7.0.5", + "inquirer": "8.2.7", + "lodash": "4.18.1", + "merge-options": "^3.0.4", + "open": "8.4.2", + "ora": "5.4.1", + "zod": "^3.24.1" + }, + "bin": { + "contentful-app-scripts": "lib/bin.js" + }, + "engines": { + "node": ">=18", + "npm": ">=9" + } + }, + "node_modules/@contentful/app-sdk": { + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@contentful/app-sdk/-/app-sdk-4.65.0.tgz", + "integrity": "sha512-8apIN25Yk8Hjyd9k8exod/krv7q/xL34zab+U2gv5W0V2q4Jj6/XFAwz2IzzC/8HlzSWQ6uZ36YRkV4iXFISFg==", + "license": "MIT", + "dependencies": { + "contentful-management": "^12.3.1" + } + }, + "node_modules/@contentful/app-sdk/node_modules/contentful-management": { + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/contentful-management/-/contentful-management-12.8.0.tgz", + "integrity": "sha512-lARtEZYn2MYf80hz9nCYiLbJWvg95TTnOWmCZouMZtgNS1273dN27Vaz7weHQ3oSQuk7LeYXaXdGFhFPKlpilw==", + "license": "MIT", + "dependencies": { + "@contentful/rich-text-types": "^16.6.1", + "axios": "^1.15.0", + "contentful-sdk-core": "^9.4.4", + "fast-copy": "^3.0.0", + "globals": "^15.15.0", + "process": "^0.11.10" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@contentful/app-sdk/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@contentful/f36-accordion": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-accordion/-/f36-accordion-4.81.1.tgz", + "integrity": "sha512-iQ2ERYurTTxDXp031kPZy5In9qNzSQJ2sc52VRaafUPdKiVnX5igGVqAiMxcK30fPP14RccOOHVz6YnrMjGtvg==", + "license": "MIT", + "dependencies": { + "@contentful/f36-collapse": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-accordion/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-asset": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-asset/-/f36-asset-4.81.1.tgz", + "integrity": "sha512-1cXdCHhZ+ymCaEHexoeGwfvEWbjDxMU4mNX1TL1LatI4iSW7l8vhwDnVXQMMi0VmhX2U968z2kFZrjsqcgIwOg==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icon": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-asset/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-autocomplete": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-autocomplete/-/f36-autocomplete-4.81.1.tgz", + "integrity": "sha512-lUwnApdkfgbX16QdHbsxZuaIv2V+Op40m/JlPNhuF/rl5g3ndS1BerjFcaDgDMbJzc/Jl8BNOVJj0Wc7F5M9TQ==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-forms": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-popover": "^4.81.1", + "@contentful/f36-skeleton": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "@contentful/f36-utils": "^4.24.3", + "downshift": "^6.1.12", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-autocomplete/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-avatar": { + "version": "4.79.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-avatar/-/f36-avatar-4.79.1.tgz", + "integrity": "sha512-DLjvcTg40KgyI2d1yRyuFSXz59rhrk7Oa0hrM38CVQ/mctd+OF6FHSoYsrXP+xaYYQz28jW7ydvLhPHze2huEA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.79.1", + "@contentful/f36-image": "4.79.1", + "@contentful/f36-menu": "^4.79.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-tooltip": "^4.79.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-badge": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-badge/-/f36-badge-4.81.1.tgz", + "integrity": "sha512-yj7ZwuuOIvAhyVqUZdfB68bpgNB2XXnQx6QsRKWaKoM703IkRbqSKrLfeOljkgd5/5kMxTWyrvQfDTx2wfJl1A==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-badge/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-button": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-button/-/f36-button-4.81.1.tgz", + "integrity": "sha512-qQK9ZQHHSvcXt6hCeqXPbr2/C8MnBk4egPd5w0VPfOi3Sa7obyWeMuAoVP3bLCicvEu/B7FFH6E1orL6+8lUOw==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-spinner": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-tooltip": "^4.81.1", + "@contentful/f36-utils": "^4.24.3", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-card": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-card/-/f36-card-4.81.1.tgz", + "integrity": "sha512-dDidZfT31IZ3BwZhKRt0O8VZu0FrU41D8BkxkdUYpwSJJzxEMsTyaiUxB57TUd8FHEEmAbozyGcJwt0ybBqm8w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-asset": "^4.81.1", + "@contentful/f36-badge": "^4.81.1", + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-drag-handle": "^4.81.1", + "@contentful/f36-icon": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-menu": "^4.81.1", + "@contentful/f36-skeleton": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-tooltip": "^4.81.1", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17", + "truncate": "^3.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-card/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-collapse": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-collapse/-/f36-collapse-4.81.1.tgz", + "integrity": "sha512-KhaM4Z7RXOsRWzPLx5kOPpTtQCR7VmbO7TVZI9x7II0zCv+bBr9hUsy6JN+ktL4ZePXe+GT4O6FGVPMbsVWt2A==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-components": { + "version": "4.79.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-components/-/f36-components-4.79.1.tgz", + "integrity": "sha512-yP2X2aovQmwu7Ay0lTMvfmYfeHpRBsArUO6CmJTwggSS0+LcQraxmfn4YNQ6pnOVEL3RPuVh4FPcrarcWKLyjQ==", + "license": "MIT", + "dependencies": { + "@contentful/f36-accordion": "^4.79.1", + "@contentful/f36-asset": "^4.79.1", + "@contentful/f36-autocomplete": "^4.79.1", + "@contentful/f36-avatar": "4.79.1", + "@contentful/f36-badge": "^4.79.1", + "@contentful/f36-button": "^4.79.1", + "@contentful/f36-card": "^4.79.1", + "@contentful/f36-collapse": "^4.79.1", + "@contentful/f36-copybutton": "^4.79.1", + "@contentful/f36-core": "^4.79.1", + "@contentful/f36-datepicker": "^4.79.1", + "@contentful/f36-datetime": "^4.79.1", + "@contentful/f36-drag-handle": "^4.79.1", + "@contentful/f36-empty-state": "^4.79.1", + "@contentful/f36-entity-list": "^4.79.1", + "@contentful/f36-forms": "^4.79.1", + "@contentful/f36-header": "^4.79.1", + "@contentful/f36-icon": "^4.79.1", + "@contentful/f36-icons": "^4.29.0", + "@contentful/f36-image": "4.79.1", + "@contentful/f36-list": "^4.79.1", + "@contentful/f36-menu": "^4.79.1", + "@contentful/f36-modal": "^4.79.1", + "@contentful/f36-navbar": "^4.79.1", + "@contentful/f36-note": "^4.79.1", + "@contentful/f36-notification": "^4.79.1", + "@contentful/f36-pagination": "^4.79.1", + "@contentful/f36-pill": "^4.79.1", + "@contentful/f36-popover": "^4.79.1", + "@contentful/f36-skeleton": "^4.79.1", + "@contentful/f36-spinner": "^4.79.1", + "@contentful/f36-table": "^4.79.1", + "@contentful/f36-tabs": "^4.79.1", + "@contentful/f36-text-link": "^4.79.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-tooltip": "^4.79.1", + "@contentful/f36-typography": "^4.79.1", + "@contentful/f36-utils": "^4.24.3" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "@types/react-dom": ">=16.8", + "react": ">=16.8", + "react-dom": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@contentful/f36-components/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-copybutton": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-copybutton/-/f36-copybutton-4.81.1.tgz", + "integrity": "sha512-6zgcAB9kyYg/KInDa1VCVurSoi6B/RD+MCpXW1Ixtweno1qWersRjHB61k3+3pQFaLT21rlPBv/31rhfOqVsHg==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-tooltip": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-copybutton/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-core": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-core/-/f36-core-4.81.1.tgz", + "integrity": "sha512-n1H0bApzFSdL4bInsemSsJVlNkDt4Xhw+g9GMhXZqC7g+K6Yptow1mUSS+93Nmbccmumy/vWyau71wqrHWt3tA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-tokens": "^4.2.0", + "@emotion/core": "^10.1.1", + "@emotion/is-prop-valid": "^1.2.0", + "csstype": "^3.1.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-datepicker": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-datepicker/-/f36-datepicker-4.81.1.tgz", + "integrity": "sha512-LFNfVK1hPqKuGiDUxjwDYcvfHFDPWZ3Ejds//+R5o8iA7xT5/l+7Zyeg0Bo49YCTXVeg5NbsenkM68poUoRjCA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-forms": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-popover": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "date-fns": "^2.28.0", + "emotion": "^10.0.17", + "react-day-picker": "^8.7.1", + "react-focus-lock": "^2.9.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-datepicker/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-datetime": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-datetime/-/f36-datetime-4.81.1.tgz", + "integrity": "sha512-SIAD2Jl+NE05a6ke5a122VIaBOJsXjD+DXD8pKsRM5NX0zzgtIPSst2NeETuW+p2HXpenwk5PSyNRqPuS7GM/g==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "dayjs": "^1.11.5", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-drag-handle": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-drag-handle/-/f36-drag-handle-4.81.1.tgz", + "integrity": "sha512-S+GnO8wouQj723ws8rk562uZy0DvxWW4TPUt+LVb6tT69kI7RawXyNqYjEOZf06uDDvPId1AhcygT8g8JL+i/w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-utils": "^4.24.3", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-drag-handle/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-empty-state": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-empty-state/-/f36-empty-state-4.81.1.tgz", + "integrity": "sha512-x+2AZvK/ZMJtST7AjvdX6/7j9shXjIiXP96iT2LR9+Vwp3vY2ifou2A/PL1oUGVbvADjwGsoVglszQoFKsR2JQ==", + "license": "MIT", + "dependencies": { + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-entity-list": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-entity-list/-/f36-entity-list-4.81.1.tgz", + "integrity": "sha512-Wec9rXoGro75TXUi7KvGrZEjSnuyL/lxCcqhOTgtW9la2QXAF9UoXWCSFfTlHqCCmyjWYqNAVVMt8MQ5TTmPUQ==", + "license": "MIT", + "dependencies": { + "@contentful/f36-badge": "^4.81.1", + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-drag-handle": "^4.81.1", + "@contentful/f36-icon": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-menu": "^4.81.1", + "@contentful/f36-skeleton": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-entity-list/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-forms": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-forms/-/f36-forms-4.81.1.tgz", + "integrity": "sha512-1F2kc81hvGmncQIKdAX8Ij9Xznk3c2+6qcqkw+oBupsPZnCYw8e4P2evlQTxpqGtQQK5QDL57J9YB69dMigehg==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "@contentful/f36-utils": "^4.24.3", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-forms/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-header": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-header/-/f36-header-4.81.1.tgz", + "integrity": "sha512-obTSg4QbUIga7DjXVOe5n5y2o7T47KM6UGYsU4n3NA5Vsy4qJReOO8oNXBVqxt7CW9hXhXccNPjuOfIhiMXApA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-header/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-icon": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icon/-/f36-icon-4.81.1.tgz", + "integrity": "sha512-s7rX0BrGvaDnaExaZeAJ24O5Ruyzlvqlq03WlePH2XDXbTC620f4VL1iODd5JJGg0NlPDgkjRk+GG6uj9HOGMA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-icons": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-5.9.1.tgz", + "integrity": "sha512-bq2i5W8rA+jYPsdPQdBegrWqbZquYfYcNHF6VOuxZuUl9xn78FvaiWHxiaVDyjFk/3kQFSDjMLUdQI3sePJSUw==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^5.9.1", + "@contentful/f36-icon": "^5.9.1", + "@contentful/f36-tokens": "^5.1.0", + "@phosphor-icons/react": "2.1.8", + "emotion": "^10.0.17" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-icons/node_modules/@contentful/f36-core": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-core/-/f36-core-5.9.1.tgz", + "integrity": "sha512-egPXjZLcUwF+LJ7q8w/9zpMVNTWVIIEV5DEmn5nAgPd4+FLQjyWFbL8ubyOMk41F0ceCbB1cp5AILuA0xRIsvQ==", + "license": "MIT", + "dependencies": { + "@contentful/f36-tokens": "^5.1.0", + "@emotion/core": "^10.1.1", + "@emotion/is-prop-valid": "^1.2.0", + "csstype": "^3.1.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-icons/node_modules/@contentful/f36-icon": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icon/-/f36-icon-5.9.1.tgz", + "integrity": "sha512-58Pa0r5vpSX6jr6Lsvoc0ah82Hid6kvNFvu8+/vGDO//KgX7On0LjLUYiygLmyNG9FeKaJingxB8RUe2FEIR6g==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^5.9.1", + "@contentful/f36-tokens": "^5.1.0", + "@phosphor-icons/react": "2.1.8", + "emotion": "^10.0.17" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-icons/node_modules/@contentful/f36-tokens": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@contentful/f36-tokens/-/f36-tokens-5.1.0.tgz", + "integrity": "sha512-7++MPQiizK6qLhPkilWIKqM5Mhdr199tK7dL4iZ689iTwA82uvXXOojJr+0wVOurG2Odb+6/3WLe1XdveDZHEA==", + "license": "MIT" + }, + "node_modules/@contentful/f36-image": { + "version": "4.79.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-image/-/f36-image-4.79.1.tgz", + "integrity": "sha512-hDMRgm7s9FPnzTI21aJlqX1HoWF7LGx9G0r/Pu8IYjecP4W5CD2HTTSMRFV81V1UjoLiVjJhkizcA0DtVqZpog==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.79.1", + "@contentful/f36-skeleton": "^4.79.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-list": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-list/-/f36-list-4.81.1.tgz", + "integrity": "sha512-m/x6LRoeO1d4o+Vm5SsdYaTQebLM959emxdbBa2ryg4TZxTzTW7S13TB758CwEtMbvv1dnmcGiEG8ot6/zV+/g==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-menu": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-menu/-/f36-menu-4.81.1.tgz", + "integrity": "sha512-zEvMlaaM10ULsiVFSTJya+6OVX7mUsrOJ9qwb+ZUpKefTVmD28HesHksRek3BD1fcy1d3UJViT78XA6q9dwS8g==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-popover": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "@contentful/f36-utils": "^4.24.3", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-menu/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-modal": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-modal/-/f36-modal-4.81.1.tgz", + "integrity": "sha512-m8JN8+Q7hfPdDhSZrCgUb8rn2I1Y5iSHVW5r/P+7pf0AOfMeJSDRyHNNMEkn5UyZudwbUdVEcNFy/l2xDVkaZA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "@types/react-modal": "^3.13.1", + "emotion": "^10.0.17", + "react-modal": "^3.16.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-modal/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-navbar": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-navbar/-/f36-navbar-4.81.1.tgz", + "integrity": "sha512-Kfromh5vpleCmbZfXHdvkYymhN8gfZiqVZjeBsx9wPvFBkm4YDx7cc4ijVZdkFekFVNrPT8GQMHSMGR4dYKt2A==", + "license": "MIT", + "dependencies": { + "@contentful/f36-avatar": "4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icon": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-menu": "^4.81.1", + "@contentful/f36-skeleton": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-utils": "^4.23.2", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-navbar/node_modules/@contentful/f36-avatar": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-avatar/-/f36-avatar-4.81.1.tgz", + "integrity": "sha512-CBR90BcY/REi0F5ey4jQ2tygTUwvUg4Rpshli7OSDhCflw//Kyv4O97vgq6s9V4pO5XfwDEujKLnbYyJHWorTA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-image": "4.81.1", + "@contentful/f36-menu": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-tooltip": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-navbar/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-navbar/node_modules/@contentful/f36-image": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-image/-/f36-image-4.81.1.tgz", + "integrity": "sha512-D/WoHm08zaaLzzttb26AUT+YvRcsIsdgOqozWtrJoxgAkuiUxfoS8sq0VnkWd5G2IGMnym4hM1pugD3ZasczSg==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-skeleton": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-note": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-note/-/f36-note-4.81.1.tgz", + "integrity": "sha512-Wd7RYOJ9pa23ZHj95pmxCESTeJV9l3snceFwI/m+gkcdZkwVknHyAyfmo/Jjq1cDWtE6Ul3n+Tk752uFNFRpAg==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icon": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-note/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-notification": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-notification/-/f36-notification-4.81.1.tgz", + "integrity": "sha512-767WdLca+Ll+8kGKL3bdnJ3YqZTgeQmqKE4fixEcBzrUwb2eDMolzZzCHu0lHBTvvRC1m/KhcYcEiExqqGmDvA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-text-link": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17", + "react-animate-height": "^3.0.4" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-notification/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-pagination": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-pagination/-/f36-pagination-4.81.1.tgz", + "integrity": "sha512-zNtISiRV6Nf3nO1oRNACzknlxwke2JSmEdC6JeUzSjX7MkNiyMvpjq1jFypdlhcpqt1fu54kWCMA2VF+H+SEDA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-forms": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-pagination/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-pill": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-pill/-/f36-pill-4.81.1.tgz", + "integrity": "sha512-Vn12ryHRjossi/TGmNyQck90bbw3k4zVUPEwKHaZZmIM+VWGIG8BYSKyBGFV1pi93O1bXgxADldZD8lY2U05ng==", + "license": "MIT", + "dependencies": { + "@contentful/f36-button": "^4.81.1", + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-drag-handle": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-tooltip": "^4.81.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-pill/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-popover": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-popover/-/f36-popover-4.81.1.tgz", + "integrity": "sha512-3p33rAisFN41seFXgYaED3PKkAYsPvvyIakdBA2Fdxnvvx38oeKSzjIn8X1Z9Oy3m1D5CTjp2egq3/XaY/tczA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-utils": "^4.24.3", + "@popperjs/core": "^2.11.5", + "emotion": "^10.0.17", + "react-popper": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-skeleton": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-skeleton/-/f36-skeleton-4.81.1.tgz", + "integrity": "sha512-7usgRP/5a9SS1PCKYfQJ/TfIjJu6iF5mXvHT7DnU/jZFX72R3p1b88fm21Gg8Bs6PgnLIuLIsI/ObZp1u84ufA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-table": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-spinner": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-spinner/-/f36-spinner-4.81.1.tgz", + "integrity": "sha512-LX5pu0mZLkrb1gXRBMVHyiyjoXgDLPUFBXc/ZZ9KE1BYnuYp+gFiYfOtGAt+DFaNeknfjqNJf+8o36z+QXwScg==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-table": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-table/-/f36-table-4.81.1.tgz", + "integrity": "sha512-C2VcMdVX0VHpG4/oZpRR4UYIdvrXf6E5XbN2RlKL/R8LBiVr8wPFLL8o7HCT/OoYoaEWYr8/EnJyIl2BYpNvgw==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-icons": "^4.29.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-typography": "^4.81.1", + "@contentful/f36-utils": "^4.24.3", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-table/node_modules/@contentful/f36-icons": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-icons/-/f36-icons-4.29.1.tgz", + "integrity": "sha512-Klqewgt+CoU9BgWhVdFDEmQAWgI+KpsFYuYFsgdr7GCaMgHuecFSGN44SdW/bcv1Ijch3A1xEoYIOqTbLUAP7w==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.80.4", + "@contentful/f36-icon": "^4.80.4", + "@contentful/f36-tokens": "^4.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-tabs": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-tabs/-/f36-tabs-4.81.1.tgz", + "integrity": "sha512-+fspt28e3YggyMJ9c+qZeCzFWjL5FBLa5ToQHmPzKO/LGtf8x3QXyLclTLflDvLBJldh8/gJ6YxQYMKhW/XLPA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@radix-ui/react-tabs": "^1.0.1", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-text-link": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-text-link/-/f36-text-link-4.81.1.tgz", + "integrity": "sha512-Pf5lnUocJ3fTbK3062IL/vyqBUK+gM4kOeaLR3ugorpU/KqE/mxgzDaKnjAJvZMDflyi4Ejr4KdKeiOWSBZHPA==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-tokens": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@contentful/f36-tokens/-/f36-tokens-4.2.0.tgz", + "integrity": "sha512-fB7ZEO8+NJPlcGvx6EHuEoPdhgpaFAeCnymXlgC+xVLTXYKXSk8JJD4WEr9rBLcXpyyS+e3g51kNFv90cDOeEQ==", + "license": "MIT" + }, + "node_modules/@contentful/f36-tooltip": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-tooltip/-/f36-tooltip-4.81.1.tgz", + "integrity": "sha512-VG2fWUdgdUYsz6KuqtYEM+n5UzADak+EY/OGDvWo206AKi5XNn29z5VnbwvMJqjOlmepYiRZ1JFaee0+d5LEtw==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-utils": "^4.24.3", + "@popperjs/core": "^2.11.5", + "csstype": "^3.1.1", + "emotion": "^10.0.17", + "react-popper": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-typography": { + "version": "4.81.1", + "resolved": "https://registry.npmjs.org/@contentful/f36-typography/-/f36-typography-4.81.1.tgz", + "integrity": "sha512-AQNy2/4/rW743vKYOtHdzM8Dz/9CP+QwD1Hm5lCUzPQpbb6XEav/mfSXi9wjLiMMWP09vPQvScBvTDQx+0dRng==", + "license": "MIT", + "dependencies": { + "@contentful/f36-core": "^4.81.1", + "@contentful/f36-tokens": "^4.2.0", + "@contentful/f36-utils": "^4.24.3", + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/f36-utils": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@contentful/f36-utils/-/f36-utils-4.24.3.tgz", + "integrity": "sha512-9+tyDOCjEhW7m4X7Sam0VYlmcgjhLVMiapCnv1EmRstlMaeI5ti9y0y/2aQ04Na2ScoHwrMTjveoZ6V62J62tA==", + "license": "MIT", + "dependencies": { + "emotion": "^10.0.17" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@contentful/react-apps-toolkit": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@contentful/react-apps-toolkit/-/react-apps-toolkit-1.2.16.tgz", + "integrity": "sha512-vx7+T4h+w1XBbn7OfRoXr8YUTtiyRNLaltF2ziM4xMqBryu/3Y71nBbhbxD6NIfOui3QIo3TR9Q5sPTZvGbIVw==", + "license": "MIT", + "dependencies": { + "contentful-management": ">=7.30.0" + }, + "peerDependencies": { + "@contentful/app-sdk": ">=4.0.0", + "react": ">=16.8.0" + } + }, + "node_modules/@contentful/rich-text-types": { + "version": "16.8.5", + "resolved": "https://registry.npmjs.org/@contentful/rich-text-types/-/rich-text-types-16.8.5.tgz", + "integrity": "sha512-q18RJuJCOuYveGiCIjE5xLCQc5lZ3L2Qgxrlg/H2YEobDFqdtmklazRi1XwEWaK3tMg6yVXBzKKkQfLB4qW14A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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/@emotion/cache": { + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", + "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", + "license": "MIT", + "dependencies": { + "@emotion/sheet": "0.9.4", + "@emotion/stylis": "0.8.5", + "@emotion/utils": "0.11.3", + "@emotion/weak-memoize": "0.2.5" + } + }, + "node_modules/@emotion/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz", + "integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@emotion/cache": "^10.0.27", + "@emotion/css": "^10.0.27", + "@emotion/serialize": "^0.11.15", + "@emotion/sheet": "0.9.4", + "@emotion/utils": "0.11.3" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, + "node_modules/@emotion/css": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", + "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "license": "MIT", + "dependencies": { + "@emotion/serialize": "^0.11.15", + "@emotion/utils": "0.11.3", + "babel-plugin-emotion": "^10.0.27" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/serialize": { + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/unitless": "0.7.5", + "@emotion/utils": "0.11.3", + "csstype": "^2.5.7" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT" + }, + "node_modules/@emotion/serialize/node_modules/csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "license": "MIT" + }, + "node_modules/@emotion/sheet": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", + "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==", + "license": "MIT" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==", + "license": "MIT" + }, + "node_modules/@esbuild-plugins/node-globals-polyfill": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", + "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild-plugins/node-modules-polyfill": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", + "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", + "dev": true, + "license": "ISC", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "rollup-plugin-node-polyfills": "^0.2.1" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "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-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/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/@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/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/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@phosphor-icons/react": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.8.tgz", + "integrity": "sha512-RxJlAkErO+t50DsY82ga9RGOULK6Jux0MdmXqvDjtOzG3PYQFz6rjdUU2q06lPMMbJTT+d+qurKYmF7i2Uv74A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">= 16.8", + "react-dom": ">= 16.8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", + "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", + "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", + "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", + "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", + "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", + "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz", + "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", + "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.17.tgz", + "integrity": "sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", + "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@segment/analytics-core": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.8.3.tgz", + "integrity": "sha512-63X8e2DWb2ZnJ9S3H8iSOFnbeDXMkTGhA8kZG4eFAO07QBwBVyWo5BCpv6vmuOMEkv2le6t2FMg1p6ZeJQA/4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-generic-utils": "1.2.0", + "dset": "^3.1.4", + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-generic-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.2.0.tgz", + "integrity": "sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-node": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-node/-/analytics-node-3.1.0.tgz", + "integrity": "sha512-F3pJmzUxmWZFV2wxJfuGvjLViCrQBFpxLLp4++fkoDsohWwS89T7n0M5nW1zxeYc2X2rA/v7q5ou3JZIw9bl3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-core": "1.8.3", + "@segment/analytics-generic-utils": "1.2.0", + "buffer": "^6.0.3", + "jose": "^5.1.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@testing-library/dom": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "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.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/dom/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/@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/react": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", + "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.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/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "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==", + "dev": true, + "license": "MIT" + }, + "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": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.13.tgz", + "integrity": "sha512-ii/gswMmOievxAJed4PAHT949bpYjPKXvXo1v6cRB/kqc2ZR4n+SgyCyvyc5Fec5ez8VnUumI1Vk7j6fRyRogg==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.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.64.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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.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/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.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/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/mocker/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/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/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/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "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/adm-zip": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "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/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/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/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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-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.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/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "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/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "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/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/babel-plugin-emotion": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", + "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/serialize": "^0.11.16", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + } + }, + "node_modules/babel-plugin-emotion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT" + }, + "node_modules/babel-plugin-emotion/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/babel-plugin-emotion/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==", + "license": "MIT" + }, + "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/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" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "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.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "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/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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", + "engines": { + "node": ">=18" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "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/contentful-management": { + "version": "11.76.0", + "resolved": "https://registry.npmjs.org/contentful-management/-/contentful-management-11.76.0.tgz", + "integrity": "sha512-KsqIZ65q1A6IP55sxTfuAMhBTNJfgGxlVZBoV2ghBuR1LaZZyTqway5vj9KHRDl/Z1uOQQsYSiz+FayMxBXXEw==", + "license": "MIT", + "dependencies": { + "@contentful/rich-text-types": "^16.6.1", + "axios": "^1.15.0", + "contentful-sdk-core": "^9.4.4", + "fast-copy": "^3.0.0", + "globals": "^15.15.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/contentful-management/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/contentful-sdk-core": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/contentful-sdk-core/-/contentful-sdk-core-9.4.5.tgz", + "integrity": "sha512-8eGTMO11LXFAiosaiV38bjvW6cjoQFtNE38pNtVilZTXpmGFinClljihH0eriv11Ispd4q82TqtsXMJPYD/C+A==", + "license": "MIT", + "dependencies": { + "fast-copy": "^3.0.2", + "lodash": "^4.17.23", + "process": "^0.11.10", + "qs": "^6.15.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "^4.18.0" + } + }, + "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/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cosmiconfig/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==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/create-emotion": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-10.0.27.tgz", + "integrity": "sha512-fIK73w82HPPn/RsAij7+Zt8eCE8SptcJ3WoRMfxMtjteYxud8GDTKKld7MYwAX2TVhrw29uR1N/bVGxeStHILg==", + "license": "MIT", + "dependencies": { + "@emotion/cache": "^10.0.27", + "@emotion/serialize": "^0.11.15", + "@emotion/sheet": "0.9.4", + "@emotion/utils": "0.11.3" + } + }, + "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.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/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==", + "license": "MIT" + }, + "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/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "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.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/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/downshift": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.12.tgz", + "integrity": "sha512-7XB/iaSJVS4T8wGFT3WRXmSF1UlBHAA40DshZtkrIscIN+VC+Lh363skLxFTvJwtNgHxAMDGEHT4xsyQFWL+UA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^1.0.17", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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==", + "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/electron-to-chromium": { + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", + "dev": true, + "license": "ISC" + }, + "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/emotion": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/emotion/-/emotion-10.0.27.tgz", + "integrity": "sha512-2xdDzdWWzue8R8lu4G76uWX5WhyQuzATon9LmNeCy/2BHVC6dsEpfhN1a0qhELgtDVdjyEA6J8Y/VlI5ZnaH0g==", + "license": "MIT", + "dependencies": { + "babel-plugin-emotion": "^10.0.27", + "create-emotion": "^10.0.27" + } + }, + "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/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==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "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==", + "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==", + "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/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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-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/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-unused-imports": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz", + "integrity": "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "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-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/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/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/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": "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/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/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==", + "license": "BSD-3-Clause" + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, + "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-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/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/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "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/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "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/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.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/focus-lock": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.6.tgz", + "integrity": "sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "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/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "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/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-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "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-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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-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/globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/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/http-proxy-agent/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/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "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/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/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/inquirer": { + "version": "8.2.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", + "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.0", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "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/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "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-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==", + "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-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==", + "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", + "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-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-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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-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-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-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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-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-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-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "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/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/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "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/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/jsdom/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/jsdom/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/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "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==", + "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/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/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/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/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/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==", + "license": "MIT" + }, + "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/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "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/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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/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/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-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "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/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": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "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/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "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-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": "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/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==", + "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==", + "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/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-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==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "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/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.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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/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/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-animate-height": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/react-animate-height/-/react-animate-height-3.2.4.tgz", + "integrity": "sha512-wcT37yHsFWDx8DmNFMy0AqSrYgNPGAtU9dyEqwIq3kSUXW03DWSi5WqbhlOALvMXcowal8JWUJRXij6TexilGQ==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-clientside-effect": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.8.tgz", + "integrity": "sha512-ma2FePH0z3px2+WOu6h+YycZcEvFmmxIlAb62cF52bG86eMySciO/EQZeQMXd07kPCYB0a1dWDT5J+KE9mCDUw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/react-day-picker": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.2.tgz", + "integrity": "sha512-LK68OTbHB3oJNhl9cA0qVizzp3o26w61YSjAFkYi67N86iro32wx86kSNeFU/hq+gI8m1yzWhnomMLfZ041RzQ==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-focus-lock": { + "version": "2.13.7", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.13.7.tgz", + "integrity": "sha512-20lpZHEQrXPb+pp1tzd4ULL6DyO5D2KnR0G69tTDdydrmNhU7pdFmbQUYVyHUgp+xN29IuFR0PVuhOmvaZL9Og==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^1.3.6", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.7", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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==", + "license": "MIT" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, + "node_modules/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==", + "license": "MIT", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "license": "MIT", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/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/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "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-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "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": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "node_modules/rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "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/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-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/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==", + "dev": true, + "license": "MIT" + }, + "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.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "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/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/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/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "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==", + "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==", + "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==", + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "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/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "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_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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.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-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/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "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/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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/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/truncate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/truncate/-/truncate-3.0.0.tgz", + "integrity": "sha512-C+0Xojw7wZPl6MDq5UjMTuxZvBPK04mtdFet7k+GSZPINcvLZFCXg+15kWIL4wAqDB7CksIsKiRLbQ1wa7rKdw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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/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-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/typescript-eslint": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", + "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.64.0", + "@typescript-eslint/parser": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.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/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": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/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/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/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "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-encoding/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "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/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "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-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/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/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/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/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "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/apps/find-orphans/package.json b/apps/find-orphans/package.json new file mode 100644 index 0000000000..953adebf85 --- /dev/null +++ b/apps/find-orphans/package.json @@ -0,0 +1,64 @@ +{ + "name": "find-orphans", + "version": "0.1.0", + "private": true, + "dependencies": { + "@contentful/app-sdk": "^4.29.1", + "@contentful/f36-components": "4.79.1", + "@contentful/f36-icons": "^5.6.0", + "@contentful/f36-tokens": "4.2.0", + "@contentful/react-apps-toolkit": "1.2.16", + "contentful-management": "^11.52.0", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "vitest", + "test:ci": "vitest run", + "lint": "eslint . --max-warnings 0", + "lint:fix": "eslint . --fix", + "create-app-definition": "contentful-app-scripts create-app-definition", + "add-locations": "contentful-app-scripts add-locations", + "update-app-parameters": "node scripts/update-app-parameters.mjs", + "seed-test-data": "node scripts/seed-test-data.mjs", + "seed-test-data:cleanup": "node scripts/seed-test-data.mjs --cleanup", + "upload": "contentful-app-scripts upload --bundle-dir ./build" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@contentful/app-scripts": "^2.3.0", + "@eslint/js": "^9.39.5", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^14.3.1", + "@types/node": "^22.13.5", + "@types/react": "18.3.13", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "^4.0.3", + "eslint": "^9.39.5", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-unused-imports": "^4.4.1", + "globals": "^17.7.0", + "jsdom": "^26.0.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.64.0", + "vite": "^6.4.1", + "vitest": "^3.0.9" + }, + "homepage": "." +} diff --git a/apps/find-orphans/scripts/seed-test-data.mjs b/apps/find-orphans/scripts/seed-test-data.mjs new file mode 100644 index 0000000000..de092892c2 --- /dev/null +++ b/apps/find-orphans/scripts/seed-test-data.mjs @@ -0,0 +1,563 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import contentfulManagement from 'contentful-management'; + +/** + * Seeds a Contentful environment with test data for the Find Orphans app, so + * the scan can be exercised against a space with a large number of drafts. + * + * Usage: + * npm run seed-test-data # create the data + * npm run seed-test-data:cleanup # remove everything the script created + * + * Configuration comes from .env.seed (see .env.seed.example), with .env as a + * fallback for the access token. The token must be a CMA token with write + * access to the target space — use a dedicated test space; the script creates + * its own content type and never touches existing content, but the cleanup + * mode deletes by tag, so keep the tag id out of real content. + * + * Everything created here is tagged `seedTestData`, which is what makes + * cleanup safe and exact: cleanup deletes only tagged entries and assets, + * then removes the seed content type and the tag itself. + * + * What gets created, per knob (all counts are env vars, see .env.seed.example): + * + * - SEED_UNTITLED_DRAFTS drafts with no title -> found by the "untitled" + * criterion. A SEED_EDITED_FRACTION of them get a + * second save so the never-edited filter has both + * version-1 and edited items to distinguish. + * - SEED_TITLED_DRAFTS drafts with a title and no inbound links -> found + * by the "unreferenced" criterion only. Same edited + * fraction applies. + * - SEED_REFERENCED_DRAFTS titled drafts that published "container" entries + * link to -> excluded by the unreferenced criterion, + * proving the links_to_entry check works. + * - SEED_PUBLISHED_ENTRIES published entries -> excluded from every scan. + * This knob exists to answer "does total space size + * matter?": the draft queries filter server-side, so + * published volume adds zero API calls to the scan, + * but it does grow the corpus the links_to_entry / + * links_to_asset searches run over, and it counts + * toward the plan's record limit. + * - SEED_DRAFT_ASSETS untitled draft assets with no inbound links -> + * found by both criteria when asset scanning is on. + * - SEED_REFERENCED_ASSETS untitled draft assets that containers link to -> + * excluded by the unreferenced criterion. + * + * Rate limits: the CMA allows ~7 requests/second per space, and the SDK + * retries 429s automatically, so the only cost of big numbers is wall-clock + * time. Rough budget: one request per draft, two per published entry (create + * + publish), plus one container per 25 referenced items. 5,000 drafts is + * therefore ~12 minutes. + */ + +// --------------------------------------------------------------------------- +// Environment loading. Values are read from .env.seed first, then .env, and +// real environment variables always win — the same precedence dotenv uses, +// implemented inline to avoid adding a dependency for two small files. +// --------------------------------------------------------------------------- + +const loadEnvFile = (path) => { + let text; + try { + text = readFileSync(path, 'utf8'); + } catch { + return; // Missing files are fine; vars can come from the other file or the shell. + } + for (const line of text.split('\n')) { + const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/); + if (match && process.env[match[1]] === undefined) { + process.env[match[1]] = match[2].replace(/^(['"])(.*)\1$/, '$2'); + } + } +}; + +const scriptDir = new URL('.', import.meta.url).pathname; +loadEnvFile(resolve(scriptDir, '..', '.env.seed')); +loadEnvFile(resolve(scriptDir, '..', '.env')); + +const requireEnv = (name) => { + const value = process.env[name]; + if (!value) { + console.error(`Missing environment variable ${name} — see .env.seed.example`); + process.exit(1); + } + return value; +}; + +const intEnv = (name, fallback) => { + const raw = process.env[name]; + if (raw === undefined || raw === '') return fallback; + const parsed = Number.parseInt(raw, 10); + if (!Number.isFinite(parsed) || parsed < 0) { + console.error(`${name} must be a non-negative integer, got "${raw}"`); + process.exit(1); + } + return parsed; +}; + +const ACCESS_TOKEN = requireEnv('CONTENTFUL_ACCESS_TOKEN'); +const SPACE_ID = requireEnv('CONTENTFUL_SPACE_ID'); +const ENVIRONMENT_ID = process.env.CONTENTFUL_ENVIRONMENT_ID || 'master'; + +const COUNTS = { + untitledDrafts: intEnv('SEED_UNTITLED_DRAFTS', 200), + titledDrafts: intEnv('SEED_TITLED_DRAFTS', 100), + referencedDrafts: intEnv('SEED_REFERENCED_DRAFTS', 25), + publishedEntries: intEnv('SEED_PUBLISHED_ENTRIES', 0), + draftAssets: intEnv('SEED_DRAFT_ASSETS', 50), + referencedAssets: intEnv('SEED_REFERENCED_ASSETS', 10), +}; +// Fraction of untitled/titled drafts that receive a second save, so the +// scan's never-edited filter (sys.version === 1) has both kinds to show. +const EDITED_FRACTION = Number.parseFloat(process.env.SEED_EDITED_FRACTION ?? '0.25'); +// Capped at the CMA rate limit of 7 req/s per space, same reasoning as the +// app's batchSize parameter — beyond that the SDK just burns requests +// retrying 429s. +const CONCURRENCY = Math.min(7, Math.max(1, intEnv('SEED_CONCURRENCY', 5))); + +/** Everything the script creates carries this tag; cleanup deletes by it. */ +const TAG_ID = 'seedTestData'; +const CONTENT_TYPE_ID = 'seedOrphanTest'; +/** Links per published container entry that references drafts/assets. */ +const LINKS_PER_CONTAINER = 25; +/** Page size for cleanup queries (the CMA collection maximum is 1000). */ +const CLEANUP_PAGE_LIMIT = 100; + +const cma = contentfulManagement.createClient( + { accessToken: ACCESS_TOKEN }, + { type: 'plain', defaults: { spaceId: SPACE_ID, environmentId: ENVIRONMENT_ID } } +); + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** + * Runs `worker(index)` for indexes 0..count-1 with at most `concurrency` in + * flight, mirroring how the app itself batches CMA calls. Failures are + * collected instead of aborting the pool so one flaky request does not strand + * a half-seeded space with no report of what happened. + */ +const runPool = async (count, worker, label) => { + let next = 0; + let done = 0; + const errors = []; + const runners = Array.from({ length: Math.min(CONCURRENCY, count) }, async () => { + while (next < count) { + const index = next++; + try { + await worker(index); + } catch (error) { + errors.push(error); + } + done += 1; + if (done % 50 === 0) console.log(` ${label}: ${done}/${count}`); + } + }); + await Promise.all(runners); + if (errors.length > 0) { + console.error(` ${label}: ${errors.length} of ${count} failed; first error:`); + console.error(` ${errors[0].message ?? errors[0]}`); + } + return count - errors.length; +}; + +const tagLink = { sys: { type: 'Link', linkType: 'Tag', id: TAG_ID } }; +const entryLink = (id) => ({ sys: { type: 'Link', linkType: 'Entry', id } }); +const assetLink = (id) => ({ sys: { type: 'Link', linkType: 'Asset', id } }); + +const chunk = (items, size) => { + const chunks = []; + for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size)); + return chunks; +}; + +const isNotFound = (error) => { + // The SDK throws with the HTTP status on error.response or, for some call + // paths, a JSON message body containing the status — check both shapes. + if (error?.response?.status === 404) return true; + try { + return JSON.parse(error.message).status === 404; + } catch { + return false; + } +}; + +// --------------------------------------------------------------------------- +// Shared setup: default locale, tag, and the seed content type +// --------------------------------------------------------------------------- + +const getDefaultLocale = async () => { + const locales = await cma.locale.getMany({ query: { limit: 100 } }); + return locales.items.find((locale) => locale.default).code; +}; + +const ensureTag = async () => { + try { + await cma.tag.get({ tagId: TAG_ID }); + } catch (error) { + if (!isNotFound(error)) throw error; + await cma.tag.createWithId( + { tagId: TAG_ID }, + { name: 'Seed test data', sys: { visibility: 'private' } } + ); + } +}; + +const ensureContentType = async () => { + let contentType; + try { + contentType = await cma.contentType.get({ contentTypeId: CONTENT_TYPE_ID }); + } catch (error) { + if (!isNotFound(error)) throw error; + contentType = await cma.contentType.createWithId( + { contentTypeId: CONTENT_TYPE_ID }, + { + name: 'Seed Orphan Test', + description: 'Created by scripts/seed-test-data.mjs. Safe to delete via its cleanup mode.', + // A Symbol display field is what makes the type scannable by the + // untitled criterion (see getTextDisplayFieldId in orphanFinder.ts). + displayField: 'title', + fields: [ + { id: 'title', name: 'Title', type: 'Symbol', required: false, localized: false }, + { id: 'body', name: 'Body', type: 'Text', required: false, localized: false }, + { + id: 'relatedEntries', + name: 'Related entries', + type: 'Array', + required: false, + localized: false, + items: { type: 'Link', linkType: 'Entry', validations: [] }, + }, + { + id: 'relatedAssets', + name: 'Related assets', + type: 'Array', + required: false, + localized: false, + items: { type: 'Link', linkType: 'Asset', validations: [] }, + }, + ], + } + ); + } + // Entries cannot be created against an unpublished (or never-published) + // content type, so activate it whether it was just created or left behind + // unactivated by an interrupted earlier run. + if (!contentType.sys.publishedVersion) { + await cma.contentType.publish({ contentTypeId: CONTENT_TYPE_ID }, contentType); + } +}; + +// --------------------------------------------------------------------------- +// Seeding +// --------------------------------------------------------------------------- + +const seed = async () => { + const locale = await getDefaultLocale(); + await ensureTag(); + await ensureContentType(); + + const editedUntitled = Math.round(COUNTS.untitledDrafts * EDITED_FRACTION); + const editedTitled = Math.round(COUNTS.titledDrafts * EDITED_FRACTION); + const containerCount = + Math.ceil(COUNTS.referencedDrafts / LINKS_PER_CONTAINER) + + Math.ceil(COUNTS.referencedAssets / LINKS_PER_CONTAINER); + // One request per create, one extra per edit, one extra per publish; used + // only for the upfront time estimate, not for control flow. + const requestEstimate = + COUNTS.untitledDrafts + + editedUntitled + + COUNTS.titledDrafts + + editedTitled + + COUNTS.referencedDrafts + + COUNTS.publishedEntries * 2 + + COUNTS.draftAssets + + COUNTS.referencedAssets + + containerCount * 2; + console.log(`Seeding space ${SPACE_ID} / environment ${ENVIRONMENT_ID} (locale ${locale})`); + // Concurrency doubles as the requests-per-second estimate: each in-flight + // slot completes roughly one request per second at typical CMA latency. + console.log( + `~${requestEstimate} CMA requests at ~${CONCURRENCY}/s: expect roughly ${Math.ceil( + requestEstimate / CONCURRENCY / 60 + )} minute(s)\n` + ); + + const createEntry = (fields) => + cma.entry.create( + { contentTypeId: CONTENT_TYPE_ID }, + { fields, metadata: { tags: [tagLink] } } + ); + + /** Second save: bumps sys.version past 1 so the item reads as "edited". */ + const editEntry = async (entry, locale) => { + entry.fields.body = { [locale]: `${entry.fields.body?.[locale] ?? ''} (edited)` }; + await cma.entry.update({ entryId: entry.sys.id }, entry); + }; + + // Untitled drafts: body only, no title. The first editedUntitled of them + // get a second save; the rest stay at version 1 (never edited). + // Created counts are tracked right after each create succeeds, NOT from + // runPool's success count: a failed follow-up (edit, publish) still leaves + // a scannable draft in the environment, and the final summary must reflect + // what actually exists rather than what completed flawlessly. + console.log(`Creating ${COUNTS.untitledDrafts} untitled drafts (${editedUntitled} edited)...`); + let untitledCreated = 0; + await runPool( + COUNTS.untitledDrafts, + async (i) => { + const entry = await createEntry({ body: { [locale]: `Untitled seed draft #${i + 1}` } }); + untitledCreated += 1; + if (i < editedUntitled) await editEntry(entry, locale); + }, + 'untitled drafts' + ); + + console.log(`Creating ${COUNTS.titledDrafts} titled unreferenced drafts (${editedTitled} edited)...`); + let titledCreated = 0; + await runPool( + COUNTS.titledDrafts, + async (i) => { + const entry = await createEntry({ + title: { [locale]: `Seed draft ${i + 1}` }, + body: { [locale]: `Titled seed draft #${i + 1}, nothing links here.` }, + }); + titledCreated += 1; + if (i < editedTitled) await editEntry(entry, locale); + }, + 'titled drafts' + ); + + // Referenced drafts and assets are created first, then published container + // entries link to them. Containers are published so the scans themselves + // never surface them — only the drafts they point at matter. + console.log(`Creating ${COUNTS.referencedDrafts} referenced drafts...`); + const referencedEntryIds = []; + await runPool( + COUNTS.referencedDrafts, + async (i) => { + const entry = await createEntry({ + title: { [locale]: `Referenced seed draft ${i + 1}` }, + }); + referencedEntryIds.push(entry.sys.id); + }, + 'referenced drafts' + ); + + console.log(`Creating ${COUNTS.draftAssets} untitled draft assets...`); + // The asset worker is a single create, so runPool's success count IS the + // created count here — no follow-up request can fail after the create. + const draftAssetsCreated = await runPool( + COUNTS.draftAssets, + // Description only — no title, no file. A fileless draft asset is exactly + // the abandoned-upload shape the untitled asset scan is meant to catch. + (i) => + cma.asset.create( + {}, + { + fields: { description: { [locale]: `Untitled seed asset #${i + 1}` } }, + metadata: { tags: [tagLink] }, + } + ), + 'draft assets' + ); + + console.log(`Creating ${COUNTS.referencedAssets} referenced draft assets...`); + const referencedAssetIds = []; + await runPool( + COUNTS.referencedAssets, + async (i) => { + const asset = await cma.asset.create( + {}, + { + fields: { description: { [locale]: `Referenced seed asset #${i + 1}` } }, + metadata: { tags: [tagLink] }, + } + ); + referencedAssetIds.push(asset.sys.id); + }, + 'referenced assets' + ); + + const containers = [ + ...chunk(referencedEntryIds, LINKS_PER_CONTAINER).map((ids) => ({ + kind: 'entry', + ids, + fields: { relatedEntries: ids.map(entryLink) }, + })), + ...chunk(referencedAssetIds, LINKS_PER_CONTAINER).map((ids) => ({ + kind: 'asset', + ids, + fields: { relatedAssets: ids.map(assetLink) }, + })), + ]; + // Ids whose container entry was at least created. The app's unreferenced + // scan counts links from drafts too (links_to_entry / links_to_asset have + // no publish filter), so a created-but-unpublished container still makes + // its targets referenced — only a failed CREATE leaves them unreferenced. + const referencedIdCount = { entry: 0, asset: 0 }; + // Containers created but not published are themselves titled unreferenced + // drafts, so they show up on the unreferenced tab (+1 entry each). + let draftContainers = 0; + if (containers.length > 0) { + console.log(`Creating and publishing ${containers.length} container entries...`); + await runPool( + containers.length, + async (i) => { + const { kind, ids, fields } = containers[i]; + const entry = await createEntry({ + title: { [locale]: `Seed container ${i + 1}` }, + ...fields, + }); + referencedIdCount[kind] += ids.length; + try { + await cma.entry.publish({ entryId: entry.sys.id }, entry); + } catch (error) { + draftContainers += 1; + throw error; // Still counts as a failure in runPool's report. + } + }, + 'containers' + ); + } + + // Filler that was created but failed to publish is a titled unreferenced + // draft, same as a stalled container. + let draftFiller = 0; + if (COUNTS.publishedEntries > 0) { + console.log(`Creating and publishing ${COUNTS.publishedEntries} filler entries...`); + await runPool( + COUNTS.publishedEntries, + async (i) => { + const entry = await createEntry({ + title: { [locale]: `Published seed entry ${i + 1}` }, + body: { [locale]: 'Published filler to grow the total record count.' }, + }); + try { + await cma.entry.publish({ entryId: entry.sys.id }, entry); + } catch (error) { + draftFiller += 1; + throw error; + } + }, + 'published entries' + ); + } + + // The summary is computed from what was actually created, not the + // configured counts, so partial failures never print misleading numbers. + // Assets never have a title here, so both asset groups count as untitled + // regardless of whether their container exists. + const untitledAssets = draftAssetsCreated + referencedAssetIds.length; + const unreferencedEntries = + untitledCreated + + titledCreated + + (referencedEntryIds.length - referencedIdCount.entry) + + draftContainers + + draftFiller; + const unreferencedAssets = + draftAssetsCreated + (referencedAssetIds.length - referencedIdCount.asset); + + const asPlanned = + untitledCreated === COUNTS.untitledDrafts && + titledCreated === COUNTS.titledDrafts && + referencedEntryIds.length === COUNTS.referencedDrafts && + draftAssetsCreated === COUNTS.draftAssets && + referencedAssetIds.length === COUNTS.referencedAssets && + referencedIdCount.entry === referencedEntryIds.length && + referencedIdCount.asset === referencedAssetIds.length && + draftContainers === 0 && + draftFiller === 0; + console.log('\nDone.'); + if (!asPlanned) { + console.warn( + 'Warning: some creates failed, so the numbers below reflect what actually exists,' + ); + console.warn('not the configured counts. Re-run after cleanup for exact planned counts.'); + const strandedIds = + referencedEntryIds.length - + referencedIdCount.entry + + (referencedAssetIds.length - referencedIdCount.asset); + if (strandedIds > 0) { + console.warn( + ` ${strandedIds} "referenced" item(s) have no container linking to them and are` + + ' counted as unreferenced below — the links_to_entry/links_to_asset exclusion is' + + ' not fully exercised by this run.' + ); + } + } + console.log('Expected scan results in this environment:'); + console.log(` untitled criterion: ${untitledCreated} entries + ${untitledAssets} assets`); + console.log( + ` unreferenced criterion: ${unreferencedEntries} entries + ${unreferencedAssets} assets` + ); + console.log(`Remove everything with: npm run seed-test-data:cleanup`); +}; + +// --------------------------------------------------------------------------- +// Cleanup: delete everything tagged, then the content type and tag +// --------------------------------------------------------------------------- + +const cleanup = async () => { + console.log(`Cleaning seed data from space ${SPACE_ID} / environment ${ENVIRONMENT_ID}...`); + + // Deleting shrinks the collection under the query, so always fetch page 0 + // and loop until the tagged set is empty rather than advancing a skip. + const deleteAllTagged = async (collection, label) => { + let removed = 0; + while (true) { + const page = await collection.getMany({ + query: { 'metadata.tags.sys.id[in]': TAG_ID, limit: CLEANUP_PAGE_LIMIT }, + }); + if (page.items.length === 0) break; + const succeeded = await runPool( + page.items.length, + async (i) => { + const item = page.items[i]; + const params = label === 'entries' ? { entryId: item.sys.id } : { assetId: item.sys.id }; + if (item.sys.publishedVersion) await collection.unpublish(params); + await collection.delete(params); + }, + label + ); + // The loop refetches page 0 until the tagged set is empty; if nothing + // in a page could be deleted, refetching would spin on the same items + // forever, so stop and leave the error report above as the outcome. + if (succeeded === 0) break; + removed += succeeded; + } + console.log(` deleted ${removed} ${label}`); + }; + + await deleteAllTagged(cma.entry, 'entries'); + await deleteAllTagged(cma.asset, 'assets'); + + try { + const contentType = await cma.contentType.get({ contentTypeId: CONTENT_TYPE_ID }); + if (contentType.sys.publishedVersion) { + await cma.contentType.unpublish({ contentTypeId: CONTENT_TYPE_ID }); + } + await cma.contentType.delete({ contentTypeId: CONTENT_TYPE_ID }); + console.log(` deleted content type ${CONTENT_TYPE_ID}`); + } catch (error) { + if (!isNotFound(error)) throw error; + } + + try { + const tag = await cma.tag.get({ tagId: TAG_ID }); + await cma.tag.delete({ tagId: TAG_ID, version: tag.sys.version }); + console.log(` deleted tag ${TAG_ID}`); + } catch (error) { + if (!isNotFound(error)) throw error; + } + + console.log('Cleanup complete.'); +}; + +if (process.argv.includes('--cleanup')) { + await cleanup(); +} else { + await seed(); +} diff --git a/apps/find-orphans/scripts/update-app-parameters.mjs b/apps/find-orphans/scripts/update-app-parameters.mjs new file mode 100644 index 0000000000..74632708eb --- /dev/null +++ b/apps/find-orphans/scripts/update-app-parameters.mjs @@ -0,0 +1,77 @@ +import contentfulManagement from 'contentful-management'; + +/** + * Upserts the app definition's installation parameter definitions via the + * CMA, so they never have to be maintained by hand in the web UI. + * + * Usage: + * CONTENTFUL_ACCESS_TOKEN=... CONTENTFUL_ORG_ID=... CONTENTFUL_APP_DEF_ID=... \ + * npm run update-app-parameters + * + * The token must have access to the organization that owns the app + * definition (a personal CMA token of an org member with app permissions + * works). This REPLACES the installation parameter list wholesale — it is + * the single source of truth, so keep it in sync with + * `AppInstallationParameters` in src/parameters.ts and the README table. + */ +const INSTALLATION_PARAMETERS = [ + { + id: 'maxCandidates', + name: 'Maximum entries per scan', + description: + 'The scan stops after this many draft entries and assets, to stay friendly to API rate limits.', + type: 'Number', + required: true, + default: 500, + }, + { + id: 'batchSize', + name: 'Concurrent API requests', + description: + 'How many CMA requests run at once while scanning and archiving. Must be between 1 and 7, the CMA rate limit per second.', + type: 'Number', + required: true, + default: 5, + }, +]; + +const { CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_ORG_ID, CONTENTFUL_APP_DEF_ID } = process.env; + +const missing = [ + ['CONTENTFUL_ACCESS_TOKEN', CONTENTFUL_ACCESS_TOKEN], + ['CONTENTFUL_ORG_ID', CONTENTFUL_ORG_ID], + ['CONTENTFUL_APP_DEF_ID', CONTENTFUL_APP_DEF_ID], +].filter(([, value]) => !value); + +if (missing.length > 0) { + console.error(`Missing environment variables: ${missing.map(([name]) => name).join(', ')}`); + console.error( + 'Find the org and app definition ids in the Contentful web app under Organization settings > Apps.' + ); + process.exit(1); +} + +const client = contentfulManagement.createClient( + { accessToken: CONTENTFUL_ACCESS_TOKEN }, + { type: 'plain' } +); + +const scope = { organizationId: CONTENTFUL_ORG_ID, appDefinitionId: CONTENTFUL_APP_DEF_ID }; +const definition = await client.appDefinition.get(scope); + +const before = (definition.parameters?.installation ?? []).map((parameter) => parameter.id); + +// Replace only the installation parameters; instance parameters and every +// other definition field (name, locations, bundle) pass through untouched. +// The fetched definition carries sys.version, which the CMA uses for +// optimistic locking on the update. +const updated = await client.appDefinition.update(scope, { + ...definition, + parameters: { ...definition.parameters, installation: INSTALLATION_PARAMETERS }, +}); + +console.log(`Updated app definition "${updated.name}" (${updated.sys.id})`); +console.log(` before: ${before.length > 0 ? before.join(', ') : '(none)'}`); +console.log( + ` after: ${(updated.parameters?.installation ?? []).map((parameter) => parameter.id).join(', ')}` +); diff --git a/apps/find-orphans/src/App.tsx b/apps/find-orphans/src/App.tsx new file mode 100644 index 0000000000..b789eba998 --- /dev/null +++ b/apps/find-orphans/src/App.tsx @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; +import { locations } from '@contentful/app-sdk'; +import { useSDK } from '@contentful/react-apps-toolkit'; +import ConfigScreen from './locations/ConfigScreen'; +import Page from './locations/Page'; + +const ComponentLocationSettings = { + [locations.LOCATION_APP_CONFIG]: ConfigScreen, + [locations.LOCATION_PAGE]: Page, +}; + +const App = () => { + const sdk = useSDK(); + + const Component = useMemo(() => { + for (const [location, component] of Object.entries(ComponentLocationSettings)) { + if (sdk.location.is(location)) { + return component; + } + } + }, [sdk.location]); + + return Component ? : null; +}; + +export default App; diff --git a/apps/find-orphans/src/components/LocalhostWarning.tsx b/apps/find-orphans/src/components/LocalhostWarning.tsx new file mode 100644 index 0000000000..00a45aa50c --- /dev/null +++ b/apps/find-orphans/src/components/LocalhostWarning.tsx @@ -0,0 +1,31 @@ +import { Paragraph, TextLink, Note, Flex } from '@contentful/f36-components'; + +const LocalhostWarning = () => { + return ( + + + + Contentful Apps need to run inside the Contentful web app to function properly. Install + the app into a space and render your app into one of the{' '} + + available locations + + . + +
+ + + Follow{' '} + + our guide + {' '} + to get started or{' '} + open Contentful{' '} + to manage your app. + +
+
+ ); +}; + +export default LocalhostWarning; diff --git a/apps/find-orphans/src/index.tsx b/apps/find-orphans/src/index.tsx new file mode 100644 index 0000000000..ffe5ad660a --- /dev/null +++ b/apps/find-orphans/src/index.tsx @@ -0,0 +1,21 @@ +import { GlobalStyles } from '@contentful/f36-components'; +import { SDKProvider } from '@contentful/react-apps-toolkit'; + +import { createRoot } from 'react-dom/client'; +import App from './App'; +import LocalhostWarning from './components/LocalhostWarning'; + +const container = document.getElementById('root')!; +const root = createRoot(container); + +if (process.env.NODE_ENV === 'development' && window.self === window.top) { + // You can remove this if block before deploying your app + root.render(); +} else { + root.render( + + + + + ); +} diff --git a/apps/find-orphans/src/locations/ConfigScreen.tsx b/apps/find-orphans/src/locations/ConfigScreen.tsx new file mode 100644 index 0000000000..bd1e2b9294 --- /dev/null +++ b/apps/find-orphans/src/locations/ConfigScreen.tsx @@ -0,0 +1,175 @@ +import { useCallback, useEffect, useState } from 'react'; +import { ConfigAppSDK } from '@contentful/app-sdk'; +import { + Box, + Flex, + Form, + FormControl, + Heading, + Paragraph, + Subheading, + TextInput, +} from '@contentful/f36-components'; +import { useSDK } from '@contentful/react-apps-toolkit'; +import { AppInstallationParameters, DEFAULT_PARAMETERS } from '../parameters'; + +type NumberParameterId = 'maxCandidates' | 'batchSize'; + +interface NumberFieldProps { + id: NumberParameterId; + label: string; + helpText: string; + placeholder: string; + value: string; + onChange: (id: NumberParameterId, value: string) => void; +} + +const NumberField = ({ id, label, helpText, placeholder, value, onChange }: NumberFieldProps) => ( + + {label} + {/* The Box keeps the input on its own line at a sensible width instead + of stretching a number field across the whole form. */} + + onChange(id, event.target.value)} + min={1} + /> + + {helpText} + +); + +// Number inputs are kept as strings while editing, so a cleared field stays +// cleared instead of snapping to 0. +interface FormValues { + maxCandidates: string; + batchSize: string; +} + +// Human-readable names for validation messages, keyed by parameter id. +const FIELD_LABELS: Record = { + maxCandidates: 'Maximum entries per scan', + batchSize: 'Concurrent API requests', +}; + +const NUMBER_PARAMETER_IDS: NumberParameterId[] = ['maxCandidates', 'batchSize']; + +// Stored parameters may be partial (e.g. the app was installed before a +// parameter existed), so each missing value falls back to its default. This +// also seeds the fresh-install form, letting the user save without typing. +const toFormValues = (parameters: Partial): FormValues => ({ + maxCandidates: String(parameters.maxCandidates ?? DEFAULT_PARAMETERS.maxCandidates), + batchSize: String(parameters.batchSize ?? DEFAULT_PARAMETERS.batchSize), +}); + +const ConfigScreen = () => { + const sdk = useSDK(); + // The form starts pre-filled with the recommended defaults so a fresh + // install is a single click; validation on save still requires each field. + const [values, setValues] = useState(toFormValues(DEFAULT_PARAMETERS)); + + // Called by Contentful when the user clicks "Save" on the app configuration + // screen. Returning false aborts the save and keeps the dialog open. + const onConfigure = useCallback(async () => { + const parsed = {} as Record; + for (const key of NUMBER_PARAMETER_IDS) { + const parsedValue = Number.parseInt(values[key], 10); + // Empty fields fail this check too: the parameters are required, so + // there is no silent fallback to defaults on save. + if (Number.isNaN(parsedValue) || parsedValue < 1) { + sdk.notifier.error(`"${FIELD_LABELS[key]}" is required and must be a positive number.`); + return false; + } + parsed[key] = parsedValue; + } + // Enforce the CMA rate limit of 7 requests/second explicitly rather than + // silently clamping a higher value on save. + if (parsed.batchSize > 7) { + sdk.notifier.error(`"${FIELD_LABELS.batchSize}" must be between 1 and 7.`); + return false; + } + const parameters: AppInstallationParameters = parsed; + // Preserve the current location assignments (EditorInterface state) + // instead of resetting them on every save. + const currentState = await sdk.app.getCurrentState(); + return { parameters, targetState: currentState }; + }, [values, sdk]); + + useEffect(() => { + sdk.app.onConfigure(() => onConfigure()); + }, [sdk, onConfigure]); + + useEffect(() => { + const initialize = async () => { + // getParameters returns null when the app has never been configured; + // in that case the form keeps the pre-filled defaults. + const current = await sdk.app.getParameters(); + if (current) { + setValues(toFormValues(current as Partial)); + } + // Without setReady the config screen stays on a loading spinner forever. + sdk.app.setReady(); + }; + initialize(); + }, [sdk]); + + const handleChange = (id: NumberParameterId, value: string) => { + setValues((previous) => ({ ...previous, [id]: value })); + }; + + return ( + + + Set up Find Orphans + + Find Orphans scans this space for orphaned draft entries and media assets — items with no + title (the signature of an entry created by mistake from a reference field) or items that + no entry references — and lets you review or archive them from one page. + + + + Scan limits + + + These settings apply to everyone in this space. The recommended values are pre-filled, so + you can install as-is and tune them later. + + +
+ + + + + + Getting started + + + After installing, open the app from the Apps menu, run a scan, then review each entry or + archive the selected ones in bulk. + +
+
+ ); +}; + +export default ConfigScreen; diff --git a/apps/find-orphans/src/locations/Page/components/OrphanTable.tsx b/apps/find-orphans/src/locations/Page/components/OrphanTable.tsx new file mode 100644 index 0000000000..d2718a7ffd --- /dev/null +++ b/apps/find-orphans/src/locations/Page/components/OrphanTable.tsx @@ -0,0 +1,110 @@ +import { Badge, Checkbox, Table, Text, TextLink } from '@contentful/f36-components'; +import { OrphanResult } from '../types'; + +interface OrphanTableProps { + /** The rows to render — with pagination, just the current page. */ + results: OrphanResult[]; + /** + * Selectable results across every page, not just the rendered slice. + * Pagination is navigation, not a view-narrowing filter, so the header + * checkbox reflects (and select-all acts on) the whole set — the count + * line and the archive confirmation spell out what is selected. + */ + totalResultCount: number; + selectedIds: string[]; + onToggleResult: (resultId: string) => void; + onToggleAll: () => void; + onOpenResult: (result: OrphanResult) => void; + /** Disables selection while an archive operation is running. */ + isDisabled: boolean; +} + +const formatDate = (isoDate: string): string => + new Date(isoDate).toLocaleDateString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + +export const OrphanTable = ({ + results, + totalResultCount, + selectedIds, + onToggleResult, + onToggleAll, + onOpenResult, + isDisabled, +}: OrphanTableProps) => { + const allSelected = totalResultCount > 0 && selectedIds.length === totalResultCount; + const someSelected = selectedIds.length > 0 && !allSelected; + + return ( + + + + + + + Title + Type + Status + {/* Creation info, not last-updated: orphans were (by default) + never edited after creation, so created when/by whom is what + identifies the mistake and its source. */} + Created + Created by + + + + + {results.map((result) => ( + + + onToggleResult(result.id)} + /> + + + {/* Untitled-scan results never have a title; unreferenced ones + usually do. The gray "Untitled" placeholder mirrors what + the content list and media library show for titleless items. */} + {result.title !== undefined ? ( + {result.title} + ) : ( + Untitled + )} + + {result.typeName} + + Draft + + {formatDate(result.createdAt)} + {result.createdBy} + + {/* Previewing is an explicit action instead of a click on the + title, so selecting rows never accidentally triggers the + slide-in. "Preview" signals it is a slide-in, not a page + change. */} + onOpenResult(result)}> + Preview + + + + ))} + +
+ ); +}; diff --git a/apps/find-orphans/src/locations/Page/index.tsx b/apps/find-orphans/src/locations/Page/index.tsx new file mode 100644 index 0000000000..2de44132d0 --- /dev/null +++ b/apps/find-orphans/src/locations/Page/index.tsx @@ -0,0 +1,763 @@ +import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; +import { PageAppSDK } from '@contentful/app-sdk'; +import { + Box, + Button, + Checkbox, + Flex, + Heading, + ModalConfirm, + Note, + Notification, + Pagination, + SkeletonRow, + Spinner, + Table, + Tabs, + Text, + TextLink, + ToggleButton, + Tooltip, +} from '@contentful/f36-components'; +import tokens from '@contentful/f36-tokens'; +import { + DownloadSimpleIcon, + InfoIcon, + LinkBreakIcon, + TrayArrowDownIcon, + MagnifyingGlassIcon, +} from '@contentful/f36-icons'; +import { useSDK } from '@contentful/react-apps-toolkit'; +import { ContentTypeProps } from 'contentful-management'; +import { resolveParameters } from '../../parameters'; +import { OrphanTable } from './components/OrphanTable'; +import { OrphanKind, OrphanResult, ScanCriterion, ScanProgress } from './types'; +import { RESULTS_PAGE_SIZE } from './utils/constants'; +import { archiveOrphans, ArchiveProgress } from './utils/entryActions'; +import { buildOrphanCsv, downloadCsv } from './utils/exportCsv'; +import { fetchAllContentTypes, findOrphans } from './utils/orphanFinder'; + +// Name what is being checked right now, e.g. +// "Checking Article, Author… (5/51)". Steps are content types plus the +// media-library pass (or reference counts), so the label stays generic. +const progressLabel = (progress: ScanProgress): string => + `Checking ${progress.stepNames.join(', ')}… (${progress.current}/${progress.total})`; + +const pluralize = (count: number) => (count === 1 ? 'item' : 'items'); + +const countByKind = (items: OrphanResult[]) => ({ + entries: items.filter((result) => result.kind === 'entry').length, + assets: items.filter((result) => result.kind === 'asset').length, +}); + +// "19 entries and 4 assets", "1 entry", … — used for the result count and +// the archive confirmation, so a mixed selection is always spelled out and +// select-all can never archive assets the user did not know were included. +const describeCounts = ({ entries, assets }: { entries: number; assets: number }): string => { + const parts: string[] = []; + if (entries > 0) parts.push(`${entries} ${entries === 1 ? 'entry' : 'entries'}`); + if (assets > 0) parts.push(`${assets} ${assets === 1 ? 'asset' : 'assets'}`); + return parts.length > 0 ? parts.join(' and ') : '0 items'; +}; + +/** + * Everything one criterion's tab remembers. Each tab keeps its own results, + * selection, and filter, so switching between the untitled and unreferenced + * tabs never discards or re-runs a scan. + */ +interface TabState { + results: OrphanResult[] | null; + truncated: boolean; + selectedIds: string[]; + /** + * The "Never edited" results filter. The scan is always broad; this only + * narrows what is displayed (and selectable), so nothing is ever silently + * excluded — the toggle shows how many results it is holding back. + */ + neverEditedOnly: boolean; + /** + * Zero-based results-table page. Pagination is navigation, not a + * view-narrowing filter: rows on other pages stay selected and archivable. + * The page resets to 0 whenever the displayed set is reshaped (new scan, + * scope or never-edited change) and is clamped at render time when rows + * disappear underneath it (archiving). + */ + page: number; +} + +/** Reset applied when a scan starts: results clear, the filter persists. */ +const SCAN_RESET = { results: null, truncated: false, selectedIds: [], page: 0 }; + +/** + * Static per-criterion UI copy and chrome. The description is shown inside + * the tab's panel above its scan button, so it is always visible before the + * scan can be started — switching tabs is free (results are cached), which + * is also why the tab labels carry no explainer of their own. + */ +const CRITERIA: Record< + ScanCriterion, + { + tabLabel: string; + scanLabel: string; + /** Rendered inside gray Text; JSX so key caveats can carry emphasis. */ + description: ReactNode; + icon: JSX.Element; + emptyNote: string; + } +> = { + untitled: { + tabLabel: 'Untitled drafts', + scanLabel: 'Scan for untitled drafts', + description: ( + + Untitled items are the signature of something created by accident — for example by adding a + new entry on a reference field instead of linking an existing one — and abandoned. + + ), + icon: , + emptyNote: 'No orphans found — every scanned draft has a title.', + }, + unreferenced: { + tabLabel: 'Unreferenced drafts', + scanLabel: 'Scan for unreferenced drafts', + description: ( + <> + + Checking references cannot be done in bulk — this scan makes one network request per + draft, so it can take several minutes on large spaces. It finds items that no entry links + to. + + {/* The caveat gets its own paragraph so it cannot be skimmed past. */} + + Being unreferenced is not proof of junk — top-level content like landing + pages is often never referenced yet perfectly valid — so treat these results as leads for + possibly unused content and review each one before archiving. + + + ), + icon: , + emptyNote: 'No orphans found — every scanned draft is referenced by at least one entry.', + }, +}; + +const Page = () => { + const sdk = useSDK(); + // Installation parameters may be empty (fresh install, or the app was + // installed before the config screen existed), so merge with defaults. + const parameters = useMemo( + () => resolveParameters(sdk.parameters.installation), + [sdk.parameters.installation] + ); + const [contentTypes, setContentTypes] = useState([]); + const [contentTypesLoading, setContentTypesLoading] = useState(true); + // Which scan is currently running (null = none). Scans are exclusive: + // both buttons disable while one runs, and the spinner shows on the + // running one. + const [activeScan, setActiveScan] = useState(null); + const [progress, setProgress] = useState(null); + const [activeTab, setActiveTab] = useState('untitled'); + // One cached state per criterion tab; `results: null` means "this tab has + // not scanned yet" and renders the placeholder instead of a table. The + // untitled tab starts on the strict never-edited view (the confident-junk + // subset; "All" is one visible click away); the unreferenced tab always + // starts broad (edit history says nothing about referenced-ness). + const [tabStates, setTabStates] = useState>({ + untitled: { ...SCAN_RESET, neverEditedOnly: true }, + unreferenced: { ...SCAN_RESET, neverEditedOnly: false }, + }); + // Per-run scan scope, shared by both tabs. Both on by default; the entry + // fan-out is the slow part, so unchecking "Entries" gives a fast + // assets-only pass. + const [scanEntries, setScanEntries] = useState(true); + const [scanAssets, setScanAssets] = useState(true); + const [archiving, setArchiving] = useState(false); + const [archiveProgress, setArchiveProgress] = useState(null); + const [confirmArchiveOpen, setConfirmArchiveOpen] = useState(false); + + // Deep links into the web app's archived views — permanent deletion only + // exists there, so the archive confirmation hands users the destination. + // environmentAlias keeps the link on the alias the user is browsing under. + // The filter uses the web app's shareable-view format: indexed key/op/val + // triplets against the "__status" pseudo-field (empty op = equals). + const archivedListUrls = useMemo(() => { + const base = `https://app.contentful.com/spaces/${sdk.ids.space}/environments/${ + sdk.ids.environmentAlias ?? sdk.ids.environment + }`; + const archivedFilter = 'filters.0.key=__status&filters.0.op=&filters.0.val=archived'; + return { + entries: `${base}/views/entries?${archivedFilter}`, + assets: `${base}/views/assets?${archivedFilter}`, + }; + }, [sdk]); + + const scanning = activeScan !== null; + const busy = scanning || archiving; + const currentTab = tabStates[activeTab]; + // What the table (and every selection action) actually operates on: the + // tab's results narrowed by the scope checkboxes (which double as live + // client-side filters on cached results) and the "Never edited" view. + const displayedResults = useMemo( + () => + (currentTab.results ?? []).filter( + (result) => + (result.kind === 'entry' ? scanEntries : scanAssets) && + (!currentTab.neverEditedOnly || result.neverEdited) + ), + [currentTab.results, currentTab.neverEditedOnly, scanEntries, scanAssets] + ); + const selectedCounts = useMemo( + () => + countByKind(displayedResults.filter((result) => currentTab.selectedIds.includes(result.id))), + [displayedResults, currentTab.selectedIds] + ); + + const patchTab = useCallback((criterion: ScanCriterion, patch: Partial) => { + setTabStates((previous) => ({ + ...previous, + [criterion]: { ...previous[criterion], ...patch }, + })); + }, []); + + // Content types are loaded once up front: the scan needs their display + // field definitions, and the list rarely changes within a session. + useEffect(() => { + const loadContentTypes = async () => { + try { + setContentTypes(await fetchAllContentTypes(sdk.cma)); + } catch (error) { + console.error('Loading content types failed:', error); + Notification.error('Could not load content types. Please reload the app.'); + } finally { + setContentTypesLoading(false); + } + }; + loadContentTypes(); + }, [sdk.cma]); + + const runScan = useCallback( + async (criterion: ScanCriterion) => { + setActiveScan(criterion); + // A new scan replaces this tab's results and invalidates its + // selection; the tab's filter and the other tab's cache are untouched. + patchTab(criterion, SCAN_RESET); + try { + const outcome = await findOrphans(sdk.cma, contentTypes, sdk.locales.default, setProgress, { + criterion, + maxCandidates: parameters.maxCandidates, + batchSize: parameters.batchSize, + includeEntries: scanEntries, + includeAssets: scanAssets, + }); + patchTab(criterion, { results: outcome.results, truncated: outcome.truncated }); + } catch (error) { + // CMA calls run through the app bridge in the parent window, so a + // failure here may leave no trace in the iframe's network tab; this + // log is the only place the underlying error is visible. + console.error('Orphan scan failed:', error); + Notification.error('The scan failed. Please try again.'); + } finally { + setActiveScan(null); + setProgress(null); + } + }, + [sdk, contentTypes, parameters, scanEntries, scanAssets, patchTab] + ); + + const openResult = useCallback( + (result: OrphanResult) => { + // slideIn keeps the user on the results page while they inspect + // (and possibly delete) the item in the standard editor. + if (result.kind === 'entry') { + sdk.navigator.openEntry(result.id, { slideIn: true }); + } else { + sdk.navigator.openAsset(result.id, { slideIn: true }); + } + }, + [sdk] + ); + + const toggleResult = useCallback( + (resultId: string) => { + patchTab(activeTab, { + selectedIds: currentTab.selectedIds.includes(resultId) + ? currentTab.selectedIds.filter((id) => id !== resultId) + : [...currentTab.selectedIds, resultId], + }); + }, + [activeTab, currentTab.selectedIds, patchTab] + ); + + const toggleAll = useCallback(() => { + // Header checkbox: everything selected clears the selection, anything + // less (none or partial) selects all displayed results — across every + // page, not just the rendered one, so "archive all N" is one click. The + // count line and the archive confirmation spell out the full selection. + patchTab(activeTab, { + selectedIds: + currentTab.selectedIds.length < displayedResults.length + ? displayedResults.map((result) => result.id) + : [], + }); + }, [activeTab, currentTab, displayedResults, patchTab]); + + const setScope = useCallback( + // The scope checkboxes are dual-purpose: they decide what the next scan + // fetches AND live-filter already-scanned results. Unchecking a kind + // hides its rows, so — like every view change — it must deselect them, + // in both tabs, since the scope is shared. + (kind: OrphanKind, checked: boolean) => { + if (kind === 'entry') { + setScanEntries(checked); + } else { + setScanAssets(checked); + } + // Any scope change reshapes the displayed list in both tabs (the scope + // is shared), so both go back to the first page; unchecking + // additionally deselects the kind's now-hidden rows. + setTabStates((previous) => { + const adjust = (tab: TabState): TabState => ({ + ...tab, + page: 0, + selectedIds: checked + ? tab.selectedIds + : tab.selectedIds.filter((id) => + (tab.results ?? []).some((result) => result.id === id && result.kind !== kind) + ), + }); + return { untitled: adjust(previous.untitled), unreferenced: adjust(previous.unreferenced) }; + }); + }, + [] + ); + + const setNeverEditedFilter = useCallback( + // The two view pills act like radios: clicking the already-active one + // is a no-op, never an off-switch, so the current view is always the + // pressed pill. + (neverEditedOnly: boolean) => { + if (neverEditedOnly === currentTab.neverEditedOnly) return; + patchTab(activeTab, { + neverEditedOnly, + // Switching views reshapes the displayed list, so start it over + // from its first page. + page: 0, + // Narrowing the view must narrow the selection too — otherwise + // "Archive selected" could archive rows the user can no longer see. + selectedIds: neverEditedOnly + ? currentTab.selectedIds.filter((id) => + (currentTab.results ?? []).some((result) => result.id === id && result.neverEdited) + ) + : currentTab.selectedIds, + }); + }, + [activeTab, currentTab, patchTab] + ); + + const exportResults = useCallback( + (criterion: ScanCriterion) => { + // The export is broad like the scan: the tab's full cached results, + // including rows the scope checkboxes or the never-edited view are + // hiding — the CSV's Kind and Edited after creation columns carry + // those flags, so filtering happens visibly in the spreadsheet, never + // silently in the file. (The button's tooltip states this.) + const results = tabStates[criterion].results ?? []; + const csv = buildOrphanCsv(results, { + spaceId: sdk.ids.space, + environmentId: sdk.ids.environmentAlias ?? sdk.ids.environment, + }); + const date = new Date().toISOString().slice(0, 10); + downloadCsv(`orphans-${criterion}-${sdk.ids.space}-${date}.csv`, csv); + }, + [tabStates, sdk] + ); + + const runArchive = useCallback(async () => { + setConfirmArchiveOpen(false); + setArchiving(true); + try { + // The archive endpoint differs per kind, so selection ids are resolved + // back to results to recover whether each is an entry or an asset. + const targets = (currentTab.results ?? []) + .filter((result) => currentTab.selectedIds.includes(result.id)) + .map((result) => ({ id: result.id, kind: result.kind })); + const outcome = await archiveOrphans( + sdk.cma, + targets, + parameters.batchSize, + setArchiveProgress + ); + // Archived items leave the result list; failed ones stay visible and + // selected so the user can retry them. + patchTab(activeTab, { + results: + currentTab.results?.filter((result) => !outcome.archivedIds.includes(result.id)) ?? null, + selectedIds: outcome.failedIds, + }); + if (outcome.archivedIds.length > 0) { + Notification.success( + `Archived ${outcome.archivedIds.length} ${pluralize(outcome.archivedIds.length)}.` + ); + } + if (outcome.failedIds.length > 0) { + Notification.error( + `Could not archive ${outcome.failedIds.length} ${pluralize( + outcome.failedIds.length + )}. They remain selected so you can try again.` + ); + } + } catch { + Notification.error('Archiving failed. Please try again.'); + } finally { + setArchiving(false); + setArchiveProgress(null); + } + }, [sdk, activeTab, currentTab, parameters, patchTab]); + + // Tab labels carry the cached result count so users can see at a glance + // which scans have run and what they found without switching tabs. The + // criterion explanations live inside each panel (always visible above the + // scan button), so the labels stay clean. + const tabLabel = (criterion: ScanCriterion) => { + const { results } = tabStates[criterion]; + return `${CRITERIA[criterion].tabLabel}${results !== null ? ` (${results.length})` : ''}`; + }; + + /** + * One criterion's whole panel: scan controls plus that tab's cached + * results. Both panels share the scope checkboxes (per-run setting) and + * the archive machinery; only the state behind them is per-tab. + */ + const renderPanel = (criterion: ScanCriterion) => { + const tab = tabStates[criterion]; + const chrome = CRITERIA[criterion]; + // Computed per criterion (not from the activeTab-level memos): React + // evaluates both panels' JSX even though only one is mounted. The scope + // checkboxes filter first, the never-edited view second, so the view + // pills count within the current scope. + const scopeVisible = (tab.results ?? []).filter((result) => + result.kind === 'entry' ? scanEntries : scanAssets + ); + const displayed = scopeVisible.filter((result) => !tab.neverEditedOnly || result.neverEdited); + const displayedCounts = countByKind(displayed); + const neverEditedCount = scopeVisible.filter((result) => result.neverEdited).length; + // Client-side paging of the displayed results: everything is already in + // memory, so this is purely a rendering choice. The page is clamped here + // instead of written back to state — rows can vanish under the current + // page without a filter event (archiving the last page's rows), and + // clamping keeps the view valid without an effect. + const pageCount = Math.max(1, Math.ceil(displayed.length / RESULTS_PAGE_SIZE)); + const activePage = Math.min(tab.page, pageCount - 1); + const pagedResults = displayed.slice( + activePage * RESULTS_PAGE_SIZE, + (activePage + 1) * RESULTS_PAGE_SIZE + ); + return ( + + {/* The criterion's full explanation lives here, inside its tab; the + page subtitle stays general and the tab-label tooltips carry the + short form. */} + {/* Descriptions bring their own paragraphs (the unreferenced + one is two, so its caveat stands alone); the Box keeps their + internal spacing out of this column's larger gap. */} + {chrome.description} + + + {/* Dual-purpose scope: decides what the next scan fetches AND + live-filters already-scanned results (the entry fan-out is the + slow part of a scan, so an assets-only pass is quick). */} + setScope('entry', event.target.checked)} + isDisabled={busy} + testId="scope-entries"> + Entries + + setScope('asset', event.target.checked)} + isDisabled={busy} + testId="scope-assets"> + Media assets + + {activeScan === criterion && progress && ( + + + {progressLabel(progress)} + + )} + + + {tab.results === null && activeScan !== criterion && ( + // Fresh tab: without this the area below the controls would be + // blank until the first scan, which reads as something missing. + + {/* "medium" is the largest size the f36-icons v5 IconSize union + allows; the "large" this previously claimed never existed and + silently fell back at runtime. */} + + No scan has run yet + + Run a scan to check every content type and the media library for orphaned drafts. + + + )} + + {activeScan === criterion && ( + // Placeholder rows keep the results area occupied while the scan + // runs; the row count is arbitrary, it only suggests a table. + + + + +
+ )} + + {tab.truncated && ( + + The scan stopped after {parameters.maxCandidates} draft items. Archive or clean up some + of the results and scan again, or raise the limit in the app configuration. + + )} + + {tab.results !== null && + (tab.results.length === 0 ? ( + + {chrome.emptyNote} + + ) : ( + <> + + + + {describeCounts(displayedCounts)} found + {tab.selectedIds.length > 0 ? ` — ${tab.selectedIds.length} selected` : ''} + + {/* The scan is broad; these two pills switch between the + broad view and the stricter never-edited one. Exactly + one is always pressed, and both carry their counts, so + the current view — and what clicking the other pill + does — is never ambiguous. Untitled tab only: edit + history is a junk signal for untitled drafts, but says + nothing about whether something is referenced. */} + {criterion === 'untitled' && ( + + Show + setNeverEditedFilter(false)} + isDisabled={busy} + testId="filter-all"> + All ({scopeVisible.length}) + + {/* "Never edited" is a term of art (sys.version === 1), + so it gets a tooltip — including the archive gotcha: + unarchiving counts as an edit, so restored orphans + only show under "All". */} + + setNeverEditedFilter(true)} + isDisabled={busy} + testId="filter-never-edited"> + {/* Trailing info icon as the there-is-a-tooltip cue; + ToggleButton's icon prop is leading-only, so it + rides along in the children. */} + + Never edited ({neverEditedCount}) + + + + + + )} + {/* No kind-scoped select-all here: the scope checkboxes + above already hide (and deselect) a kind, so "select + only entries" is uncheck-assets + select-all, and the + archive confirmation still itemizes by kind. */} + + + {archiving && archiveProgress && ( + + + + Archiving… ({archiveProgress.current}/{archiveProgress.total}) + + + )} + {/* Same tooltip-with-visible-InfoIcon convention as the + archive button. The export deliberately ignores the + view filters (see exportResults), which is exactly the + kind of behavior that must be announced, not + discovered. */} + + {/* Archiving reads as scary-destructive next to a negative + button; the tooltip says it is reversible, and the + confirmation dialog carries the deep links to where + actual deletion lives. */} + + + + + + {displayed.length === 0 ? ( + // Filters are hiding every result: say which one, instead + // of showing an empty table — the control to widen the view + // is right above. + + {scopeVisible.length === 0 + ? `All ${tab.results.length} ${pluralize( + tab.results.length + )} are hidden by the Entries / Media assets checkboxes above.` + : `All ${scopeVisible.length} ${pluralize( + scopeVisible.length + )} in scope were edited after creation — switch the view above to “All” to show them.`} + + ) : ( + <> + + {displayed.length > RESULTS_PAGE_SIZE && ( + patchTab(criterion, { page })} + itemsPerPage={RESULTS_PAGE_SIZE} + totalItems={displayed.length} + pageLength={pagedResults.length} + isLastPage={activePage === pageCount - 1} + testId="results-pagination" + /> + )} + + )} + + ))} +
+ ); + }; + + return ( + // Full width: page apps get the whole main column, and the results table + // benefits from every pixel of it. + + + + + Find orphaned entries + + + Finds orphaned draft entries and media assets, so they can be reviewed and archived from + one place. Each tab covers a different definition of “orphaned”. + + + + {/* Each criterion is a tab holding its own cached results, so + running one scan, checking the other, and switching back never + re-runs anything. */} + setActiveTab(tab as ScanCriterion)}> + + + {tabLabel('untitled')} + + + {tabLabel('unreferenced')} + + + {renderPanel('untitled')} + {renderPanel('unreferenced')} + + + + setConfirmArchiveOpen(false)} + onConfirm={runArchive} + // The confirmation always itemizes the selection by kind, so a + // select-all that swept in assets (or entries) is visible right on + // the button before anything is archived. + confirmLabel={`Archive ${describeCounts(selectedCounts)}`} + cancelLabel="Cancel"> + + The selected {describeCounts(selectedCounts)} will be archived and disappear from the + content list and media library. Archiving is reversible: anything archived can be + unarchived from its editor at any time. + + + To delete archived items permanently, open the{' '} + + archived entries + {' '} + or{' '} + + archived assets + {' '} + list (opens in a new tab), select the items, and delete them there. + + + + ); +}; + +export default Page; diff --git a/apps/find-orphans/src/locations/Page/types.ts b/apps/find-orphans/src/locations/Page/types.ts new file mode 100644 index 0000000000..bb527c4092 --- /dev/null +++ b/apps/find-orphans/src/locations/Page/types.ts @@ -0,0 +1,68 @@ +import { PageAppSDK } from '@contentful/app-sdk'; + +export type CmaClient = PageAppSDK['cma']; + +/** Which CMA entity a result is; decides how it is previewed and archived. */ +export type OrphanKind = 'entry' | 'asset'; + +/** + * What "orphan" means for a scan — untitled drafts (created by accident and + * abandoned) or unreferenced drafts (no entry links to them). One criterion + * per scan, chosen on the page, so results stay unambiguous. + */ +export type ScanCriterion = 'untitled' | 'unreferenced'; + +/** + * A draft entry or media asset flagged by the scan: its title has no value in + * the default locale, which is the signature of an entity created by mistake + * (e.g. from a reference field) and never filled in. The shape is flattened + * to just what the UI and the archive action need, so entries and assets can + * share one result list. + */ +export interface OrphanResult { + kind: OrphanKind; + id: string; + /** Content type name for entries, "Asset" for media-library assets. */ + typeName: string; + /** + * The item's title in the default locale, or undefined when it has none + * (always undefined under the untitled criterion — that is what it finds; + * unreferenced results usually have real titles). The UI renders undefined + * as the editor's "Untitled" placeholder. + */ + title: string | undefined; + /** + * Creation date, not last-updated: "when was this created" is what + * identifies the mistake, and an edit date would understate its age. + */ + createdAt: string; + /** + * True when the item was never saved after creation (sys.version === 1; + * valid because published/archived states are excluded by the draft + * queries). Feeds the "Never edited" results filter. Note that archiving + * and unarchiving an item bumps its version, so restored orphans read as + * edited. + */ + neverEdited: boolean; + /** + * Display name of whoever created the item, resolved from sys.createdBy + * after the scan — "Jane Doe", "App" for app/automation identities, or + * "Unknown user" when the creator cannot be resolved (e.g. left the + * space, or the users lookup is not permitted). + */ + createdBy: string; +} + +export interface ScanProgress { + /** Scan steps done so far: one per content type, plus one for assets. */ + current: number; + total: number; + /** What is being checked right now (content type names or "Assets"). */ + stepNames: string[]; +} + +export interface ScanOutcome { + results: OrphanResult[]; + /** True when the scan hit the maxCandidates cap and stopped collecting further entries. */ + truncated: boolean; +} diff --git a/apps/find-orphans/src/locations/Page/utils/constants.ts b/apps/find-orphans/src/locations/Page/utils/constants.ts new file mode 100644 index 0000000000..46f6e2751b --- /dev/null +++ b/apps/find-orphans/src/locations/Page/utils/constants.ts @@ -0,0 +1,38 @@ +/** Page size for CMA entry collection requests. */ +export const PAGE_LIMIT = 100; + +/** + * Page size for the content types collection. The CMA allows up to 1000 per + * request, so virtually every space loads its content model in one call. + */ +export const CONTENT_TYPE_PAGE_LIMIT = 1000; + +/** + * Default cap on candidate entries per scan, to protect against rate limits + * on huge spaces. Overridable via the `maxCandidates` installation parameter. + */ +export const MAX_CANDIDATES = 500; + +/** + * Default number of concurrent CMA requests while scanning and archiving + * (CMA limit: 7 req/s). Overridable via the `batchSize` installation + * parameter. + */ +export const BATCH_SIZE = 5; + +/** + * Chunk size for the creator-name lookup: ids per `sys.id[in]` users query. + * Keeps each request's id list comfortably inside query-string limits. + */ +export const USER_PAGE_LIMIT = 100; + +/** Display field types that can hold a title. */ +export const TEXT_FIELD_TYPES = ['Symbol', 'Text']; + +/** + * Rows per page in the results table. Purely a rendering choice: a scan + * fetches every result up front, so paging costs no API calls — it only + * keeps the results header (counts, filters, archive button) within reach + * on large result sets. + */ +export const RESULTS_PAGE_SIZE = 50; diff --git a/apps/find-orphans/src/locations/Page/utils/entryActions.ts b/apps/find-orphans/src/locations/Page/utils/entryActions.ts new file mode 100644 index 0000000000..3d909d8750 --- /dev/null +++ b/apps/find-orphans/src/locations/Page/utils/entryActions.ts @@ -0,0 +1,58 @@ +import { CmaClient, OrphanKind } from '../types'; + +export interface ArchiveProgress { + current: number; + total: number; +} + +export interface ArchiveOutcome { + archivedIds: string[]; + failedIds: string[]; +} + +/** What the archive action needs to know about one selected result. */ +export interface ArchiveTarget { + id: string; + /** Decides which CMA endpoint archives it (entries vs assets). */ + kind: OrphanKind; +} + +/** + * Archives the given entries and assets in small concurrent batches so the + * CMA rate limit (7 req/s per space) is respected. One failed archive never + * aborts the rest of the batch; failures are collected so the caller can + * report them and keep the items listed. + */ +export const archiveOrphans = async ( + cma: CmaClient, + targets: ArchiveTarget[], + batchSize: number, + onProgress: (progress: ArchiveProgress) => void +): Promise => { + const archivedIds: string[] = []; + const failedIds: string[] = []; + + for (let i = 0; i < targets.length; i += batchSize) { + const batch = targets.slice(i, i + batchSize); + onProgress({ current: Math.min(i + batch.length, targets.length), total: targets.length }); + await Promise.all( + batch.map(async ({ id, kind }) => { + try { + // Drafts are always unpublished, so archiving cannot fail on the + // "published items cannot be archived" rule; failures here are + // permissions or version conflicts. + if (kind === 'entry') { + await cma.entry.archive({ entryId: id }); + } else { + await cma.asset.archive({ assetId: id }); + } + archivedIds.push(id); + } catch { + failedIds.push(id); + } + }) + ); + } + + return { archivedIds, failedIds }; +}; diff --git a/apps/find-orphans/src/locations/Page/utils/exportCsv.ts b/apps/find-orphans/src/locations/Page/utils/exportCsv.ts new file mode 100644 index 0000000000..1655826a71 --- /dev/null +++ b/apps/find-orphans/src/locations/Page/utils/exportCsv.ts @@ -0,0 +1,81 @@ +import { OrphanResult } from '../types'; + +/** + * CSV export of scan results for offline review. Deliberately broad, like + * the scan itself: callers pass the tab's FULL cached results — including + * rows currently hidden by the scope checkboxes or the never-edited view — + * and the Kind / Edited after creation columns carry the flags, so the + * filtering can happen in the spreadsheet instead of silently in the export. + */ + +/** Space/environment coordinates for building editor deep links. */ +export interface CsvLinkContext { + spaceId: string; + /** Pass the alias id when browsing under one, like the app's other links. */ + environmentId: string; +} + +const CSV_HEADER = [ + 'Kind', + 'ID', + 'Title', + 'Type', + 'Created', + 'Created by', + // Positive phrasing on purpose: a "Never edited" column would put double + // negatives in the cells ("Never edited: no"). The UI's "Never edited" + // view is this column filtered to "no". + 'Edited after creation', + 'Editor URL', +]; + +/** + * RFC 4180 quoting, plus a guard against spreadsheet formula injection: + * titles and creator names are content-controlled text, and Excel/Sheets + * execute cells starting with = + - or @ as formulas on open. The leading + * apostrophe is the spreadsheet convention for "treat as literal text". + */ +const escapeCell = (value: string): string => { + const literal = /^[=+\-@]/.test(value) ? `'${value}` : value; + return /[",\n\r]/.test(literal) ? `"${literal.replace(/"/g, '""')}"` : literal; +}; + +/** Editor deep link for one result — entries and assets have separate paths. */ +export const editorUrl = (result: OrphanResult, { spaceId, environmentId }: CsvLinkContext) => + `https://app.contentful.com/spaces/${spaceId}/environments/${environmentId}/${ + result.kind === 'entry' ? 'entries' : 'assets' + }/${result.id}`; + +export const buildOrphanCsv = (results: OrphanResult[], context: CsvLinkContext): string => { + const rows = results.map((result) => [ + result.kind, + result.id, + // Empty cell, not an "Untitled" placeholder: a spreadsheet filter on + // blank titles should match exactly the untitled results. + result.title ?? '', + result.typeName, + result.createdAt, + result.createdBy, + result.neverEdited ? 'no' : 'yes', + editorUrl(result, context), + ]); + // CRLF line endings per RFC 4180 — some Windows spreadsheet imports + // mis-detect bare LF files. + return [CSV_HEADER, ...rows].map((cells) => cells.map(escapeCell).join(',')).join('\r\n'); +}; + +/** Hands the CSV to the browser as a file download. */ +export const downloadCsv = (filename: string, csv: string): void => { + // The UTF-8 BOM makes Excel decode the file as UTF-8; without it, + // non-ASCII titles open as mojibake. + const blob = new Blob(['\ufeff', csv], { type: 'text/csv;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = filename; + // Firefox ignores clicks on anchors that are not in the document. + document.body.appendChild(anchor); + anchor.click(); + anchor.remove(); + URL.revokeObjectURL(url); +}; diff --git a/apps/find-orphans/src/locations/Page/utils/orphanFinder.ts b/apps/find-orphans/src/locations/Page/utils/orphanFinder.ts new file mode 100644 index 0000000000..6670151cd9 --- /dev/null +++ b/apps/find-orphans/src/locations/Page/utils/orphanFinder.ts @@ -0,0 +1,451 @@ +import { AssetProps, ContentTypeProps, EntryProps, QueryOptions } from 'contentful-management'; +import { + CmaClient, + OrphanKind, + OrphanResult, + ScanCriterion, + ScanOutcome, + ScanProgress, +} from '../types'; +import { + CONTENT_TYPE_PAGE_LIMIT, + PAGE_LIMIT, + TEXT_FIELD_TYPES, + USER_PAGE_LIMIT, +} from './constants'; + +/** + * Scan settings. The limits and the untouched filter come from installation + * parameters (see src/parameters.ts); the criterion and the two scope flags + * are a per-run choice made on the page. + */ +export interface ScanOptions { + /** What "orphan" means for this scan: untitled or unreferenced drafts. */ + criterion: ScanCriterion; + maxCandidates: number; + /** Concurrent CMA entry queries while scanning. */ + batchSize: number; + /** Scan entries of all content types. */ + includeEntries: boolean; + /** Scan media-library assets. */ + includeAssets: boolean; +} + +export const fetchAllContentTypes = async (cma: CmaClient): Promise => { + const all: ContentTypeProps[] = []; + let skip = 0; + let total = Infinity; + // The CMA caps collections at 1000 items per request, so this is a single + // round trip for virtually every space; the loop only continues for spaces + // with more than 1000 content types. + while (skip < total) { + const response = await cma.contentType.getMany({ + query: { skip, limit: CONTENT_TYPE_PAGE_LIMIT, order: 'name' }, + }); + all.push(...response.items); + total = response.total; + if (response.items.length === 0) break; + skip += response.items.length; + } + return all; +}; + +/** + * Returns the id of the content type's display field if it is a text field, + * otherwise undefined. The display field is what the Contentful UI shows as + * the entry title; only Symbol/Text fields can hold a missing-title signal, + * so content types without one are excluded from the scan entirely. + */ +export const getTextDisplayFieldId = (contentType: ContentTypeProps): string | undefined => { + const field = contentType.fields.find((f) => f.id === contentType.displayField); + return field && TEXT_FIELD_TYPES.includes(field.type) ? field.id : undefined; +}; + +export const getEntryTitle = ( + entry: EntryProps, + contentType: ContentTypeProps, + defaultLocale: string +): string | undefined => { + const displayFieldId = getTextDisplayFieldId(contentType); + if (!displayFieldId) return undefined; + // CMA entries key every field by locale; a whitespace-only title is treated + // the same as a missing one so it still renders as "Untitled" in the UI. + // `fields` itself can be absent: when a query `select`s specific fields and + // an entry has no value in any of them, the CMA omits the object entirely — + // which is exactly the shape of the orphans this scan looks for. + const value = entry.fields?.[displayFieldId]?.[defaultLocale]; + return typeof value === 'string' && value.trim() !== '' ? value : undefined; +}; + +/** + * Builds the CMA entry query for one content type. The draft scope (never + * published, not archived) is filtered server-side; whether the display field + * is empty is checked client-side by `getEntryTitle`, because the API's + * `[exists]` operator would miss whitespace-only titles. + */ +export const buildDraftEntryQuery = (contentType: ContentTypeProps): QueryOptions => { + const displayFieldId = getTextDisplayFieldId(contentType); + return { + content_type: contentType.sys.id, + // "Draft" means the entry has never been published. Entries that were + // published and then changed have a publishedAt and are excluded. + 'sys.publishedAt[exists]': false, + 'sys.archivedAt[exists]': false, + // The sys.id tiebreaker makes the order deterministic when many entries + // share an updatedAt (e.g. a bulk import); without it, skip-based paging + // can drop or duplicate entries across pages. + order: '-sys.updatedAt,sys.id', + // The scan only reads sys and the title, so skip every other field to + // keep response payloads small. Selecting fields requires content_type, + // which this query always sets. + select: displayFieldId ? `sys,fields.${displayFieldId}` : 'sys', + }; +}; + +/** + * Returns the asset's title in the default locale, or undefined when it is + * missing or whitespace-only. Assets have a fixed shape (every asset has a + * localized title field), so unlike entries no display-field lookup is + * needed. The same `select` caveat applies: the CMA omits the fields object + * entirely from assets with no value in any selected field. + */ +export const getAssetTitle = (asset: AssetProps, defaultLocale: string): string | undefined => { + const value = (asset.fields as Partial | undefined)?.title?.[defaultLocale]; + return typeof value === 'string' && value.trim() !== '' ? value : undefined; +}; + +/** + * Builds the CMA asset query. Mirrors `buildDraftEntryQuery`, but assets are + * homogeneous — no content type, so no `content_type` filter is needed for + * `select` and the whole media library is one paged query. + */ +export const buildDraftAssetQuery = (): QueryOptions => ({ + 'sys.publishedAt[exists]': false, + 'sys.archivedAt[exists]': false, + order: '-sys.updatedAt,sys.id', + select: 'sys,fields.title', +}); + +/** + * Fetches all draft entries of one content type, paging until either the + * collection is exhausted or `budget` entries have been collected. + */ +const fetchDraftsOfContentType = async ( + cma: CmaClient, + contentType: ContentTypeProps, + budget: number +): Promise<{ entry: EntryProps; contentType: ContentTypeProps }[]> => { + const items: { entry: EntryProps; contentType: ContentTypeProps }[] = []; + let skip = 0; + let total = Infinity; + while (skip < total && items.length < budget) { + const limit = Math.min(PAGE_LIMIT, budget - items.length); + const response = await cma.entry.getMany({ + query: { ...buildDraftEntryQuery(contentType), skip, limit }, + }); + items.push(...response.items.map((entry) => ({ entry, contentType }))); + total = response.total; + if (response.items.length === 0) break; + skip += response.items.length; + } + return items; +}; + +/** The sys.createdBy link target: a user, or an app/automation identity. */ +interface CreatorLink { + id: string; + linkType: string; +} + +const toCreatorLink = (sys: EntryProps['sys'] | AssetProps['sys']): CreatorLink | undefined => + sys.createdBy?.sys; + +/** + * Resolves creator user ids to display names via batched space-user lookups + * (`sys.id[in]`, USER_PAGE_LIMIT ids per request). Lookup failures and users + * who are no longer space members simply stay absent from the mapping — + * callers fall back to "Unknown user", and the scan stays useful even when + * the caller may not list space users at all. + */ +export const resolveCreatorNames = async ( + cma: CmaClient, + userIds: string[] +): Promise> => { + const names: Record = {}; + for (let i = 0; i < userIds.length; i += USER_PAGE_LIMIT) { + const chunk = userIds.slice(i, i + USER_PAGE_LIMIT); + try { + const response = await cma.user.getManyForSpace({ + query: { 'sys.id[in]': chunk.join(','), limit: chunk.length }, + }); + for (const user of response.items) { + const fullName = [user.firstName, user.lastName].filter(Boolean).join(' '); + names[user.sys.id] = fullName || user.email; + } + } catch { + // Tolerated — these ids render as "Unknown user". + } + } + return names; +}; + +/** + * Fetches all draft assets, paging until either the media library is + * exhausted or `budget` assets have been collected. + */ +const fetchDraftAssets = async (cma: CmaClient, budget: number): Promise => { + const items: AssetProps[] = []; + let skip = 0; + let total = Infinity; + while (skip < total && items.length < budget) { + const limit = Math.min(PAGE_LIMIT, budget - items.length); + const response = await cma.asset.getMany({ + query: { ...buildDraftAssetQuery(), skip, limit }, + }); + items.push(...response.items); + total = response.total; + if (response.items.length === 0) break; + skip += response.items.length; + } + return items; +}; + +/** + * Collects draft entries across all scannable content types, stopping once + * `maxCandidates` entries have been gathered so a large space cannot trigger + * an unbounded number of API calls. + * + * Content types are queried `concurrency` at a time: one query per content + * type is unavoidable (entry queries require a single content_type), but the + * queries are independent, so running them in parallel divides the wall-clock + * time of the scan by the concurrency factor. + * + * `progressTotal` is the number of steps the whole scan reports (content + * types plus the asset step, when enabled), so the progress counter does not + * jump backwards when the asset step follows. + */ +const fetchDraftCandidates = async ( + cma: CmaClient, + contentTypes: ContentTypeProps[], + maxCandidates: number, + concurrency: number, + progressTotal: number, + onProgress: (progress: ScanProgress) => void +): Promise<{ + candidates: { entry: EntryProps; contentType: ContentTypeProps }[]; + truncated: boolean; +}> => { + const candidates: { entry: EntryProps; contentType: ContentTypeProps }[] = []; + let truncated = false; + let processed = 0; + + for (let i = 0; i < contentTypes.length && candidates.length < maxCandidates; i += concurrency) { + const chunk = contentTypes.slice(i, i + concurrency); + // Every content type in the chunk shares the same remaining-budget + // snapshot, so a chunk can overshoot the cap; the combined list is + // trimmed below and the overshoot reported as truncation. + const budget = maxCandidates - candidates.length; + processed += chunk.length; + // Report before the requests fire so the UI names the content types + // being checked while they are actually in flight. + onProgress({ + current: processed, + total: progressTotal, + stepNames: chunk.map((contentType) => contentType.name), + }); + const chunkResults = await Promise.all( + chunk.map((contentType) => fetchDraftsOfContentType(cma, contentType, budget)) + ); + candidates.push(...chunkResults.flat()); + } + + if (candidates.length >= maxCandidates) { + // The cap was hit mid-scan: remaining content types (and pages) were + // not inspected, so the caller must surface a truncation warning. + truncated = true; + candidates.length = maxCandidates; + } + + return { candidates, truncated }; +}; + +/** + * One draft candidate, flattened to what both criteria and the result + * mapping need, so entries and assets flow through the same filters. + */ +interface Candidate { + kind: OrphanKind; + id: string; + typeName: string; + createdAt: string; + title: string | undefined; + version: number; + creator: CreatorLink | undefined; +} + +/** + * Keeps only candidates that no entry links to. Referenced-ness cannot be + * queried in bulk — links_to_entry / links_to_asset filter by ONE target id + * per query — so this is one count request per candidate (limit: 0 returns + * just the total), batched `batchSize` at a time. This makes the + * unreferenced criterion by far the more expensive scan: budget one request + * per draft, on top of the candidate paging. + */ +const filterUnreferenced = async ( + cma: CmaClient, + candidates: Candidate[], + batchSize: number, + onProgress: (progress: ScanProgress) => void +): Promise => { + const unreferenced: Candidate[] = []; + for (let i = 0; i < candidates.length; i += batchSize) { + const batch = candidates.slice(i, i + batchSize); + onProgress({ + current: Math.min(i + batch.length, candidates.length), + total: candidates.length, + stepNames: ['references'], + }); + const totals = await Promise.all( + batch.map((candidate) => + cma.entry + .getMany({ + query: { + [candidate.kind === 'entry' ? 'links_to_entry' : 'links_to_asset']: candidate.id, + limit: 0, + }, + }) + .then((response) => response.total) + ) + ); + unreferenced.push(...batch.filter((_, index) => totals[index] === 0)); + } + return unreferenced; +}; + +/** + * Scans the environment for orphaned drafts (never published, not archived) + * matching the chosen criterion: + * + * - "untitled": no title value in the default locale. For entries the title + * is the content type's display field, and content types whose display + * field is not a text field are skipped — their entries cannot be missing + * a title. + * - "unreferenced": no entry links to the item. Every content type + * participates (any entry can be a link target) and titles are irrelevant. + * + * Both scans are broad with respect to edit history: every result carries a + * `neverEdited` flag and the UI filters on it visibly instead of the scan + * excluding anything silently. + */ +export const findOrphans = async ( + cma: CmaClient, + contentTypes: ContentTypeProps[], + defaultLocale: string, + onProgress: (progress: ScanProgress) => void, + options: ScanOptions +): Promise => { + const scannableTypes = options.includeEntries + ? options.criterion === 'untitled' + ? contentTypes.filter((ct) => getTextDisplayFieldId(ct) !== undefined) + : contentTypes + : []; + const progressTotal = scannableTypes.length + (options.includeAssets ? 1 : 0); + + const { candidates, truncated: entriesTruncated } = await fetchDraftCandidates( + cma, + scannableTypes, + options.maxCandidates, + options.batchSize, + progressTotal, + onProgress + ); + + // Assets and entries share one maxCandidates budget, so the cap holds for + // the scan as a whole no matter which scopes are enabled. + let truncated = entriesTruncated; + let assetCandidates: AssetProps[] = []; + if (options.includeAssets) { + onProgress({ current: progressTotal, total: progressTotal, stepNames: ['Media assets'] }); + const budget = options.maxCandidates - candidates.length; + if (budget > 0) { + assetCandidates = await fetchDraftAssets(cma, budget); + if (candidates.length + assetCandidates.length >= options.maxCandidates) { + truncated = true; + } + } + } + + // Entries and assets are flattened into one candidate list so both + // criteria filter the same shape. Titles ride along: the untitled + // criterion filters on them, the unreferenced one displays them (an + // unreferenced entry usually has a real title). + const allCandidates: Candidate[] = [ + ...candidates.map(({ entry, contentType }) => ({ + kind: 'entry' as const, + id: entry.sys.id, + typeName: contentType.name, + createdAt: entry.sys.createdAt, + title: getEntryTitle(entry, contentType, defaultLocale), + version: entry.sys.version, + creator: toCreatorLink(entry.sys), + })), + ...assetCandidates.map((asset) => ({ + kind: 'asset' as const, + id: asset.sys.id, + typeName: 'Asset', + createdAt: asset.sys.createdAt, + title: getAssetTitle(asset, defaultLocale), + version: asset.sys.version, + creator: toCreatorLink(asset.sys), + })), + ]; + + // The scan itself is deliberately broad — the "never edited" distinction + // is carried on each result (see neverEdited below) and applied as a + // visible filter in the UI, never as a silent scan-time exclusion. + let orphans: Candidate[]; + if (options.criterion === 'unreferenced') { + orphans = await filterUnreferenced(cma, allCandidates, options.batchSize, onProgress); + } else { + orphans = allCandidates.filter((candidate) => candidate.title === undefined); + } + + // sys.createdBy only carries a link, so creators are resolved to names in + // one batched users lookup per USER_PAGE_LIMIT unique ids — the "who + // created this" column is often the fastest way to find the person or + // workflow that produces orphans. Resolution runs after filtering so only + // actual orphans' creators are looked up. + const userIds = [ + ...new Set( + orphans + .map((orphan) => orphan.creator) + .filter((link): link is CreatorLink => link !== undefined && link.linkType === 'User') + .map((link) => link.id) + ), + ]; + const names = await resolveCreatorNames(cma, userIds); + const creatorName = (link: CreatorLink | undefined): string => { + // Non-user identities (apps, automations) are not in the users endpoint; + // label them collectively rather than showing a raw id. + if (link && link.linkType !== 'User') return 'App'; + return (link && names[link.id]) || 'Unknown user'; + }; + + const results: OrphanResult[] = orphans.map((orphan) => ({ + kind: orphan.kind, + id: orphan.id, + typeName: orphan.typeName, + title: orphan.title, + createdAt: orphan.createdAt, + createdBy: creatorName(orphan.creator), + // The CMA bumps sys.version on every write (updates, but also publish, + // unpublish, archive and unarchive — and for assets, file processing). + // Publish and archive states are excluded by the draft queries, so + // version 1 can only mean the item was never saved after creation. The + // check must be client-side anyway: sys.version is not a queryable + // attribute in CMA searches. + neverEdited: orphan.version === 1, + })); + + return { results, truncated }; +}; diff --git a/apps/find-orphans/src/parameters.ts b/apps/find-orphans/src/parameters.ts new file mode 100644 index 0000000000..342d857464 --- /dev/null +++ b/apps/find-orphans/src/parameters.ts @@ -0,0 +1,33 @@ +import { BATCH_SIZE, MAX_CANDIDATES } from './locations/Page/utils/constants'; + +export interface AppInstallationParameters { + /** Hard cap on candidate entries per scan. */ + maxCandidates: number; + /** Concurrent CMA requests while scanning and archiving. */ + batchSize: number; +} + +export const DEFAULT_PARAMETERS: AppInstallationParameters = { + maxCandidates: MAX_CANDIDATES, + batchSize: BATCH_SIZE, +}; + +const toPositiveInt = (value: unknown, fallback: number): number => { + const parsed = typeof value === 'string' ? Number.parseInt(value, 10) : value; + return typeof parsed === 'number' && Number.isFinite(parsed) && parsed >= 1 + ? Math.floor(parsed) + : fallback; +}; + +/** + * Merges raw installation parameters with defaults. Handles fresh installs + * (empty object) and hand-edited or legacy values (strings, out-of-range). + */ +export const resolveParameters = (raw: unknown): AppInstallationParameters => { + const params = (raw ?? {}) as Partial>; + return { + maxCandidates: toPositiveInt(params.maxCandidates, DEFAULT_PARAMETERS.maxCandidates), + // Capped at 7: the CMA rate limit is 7 requests/second per space. + batchSize: Math.min(7, toPositiveInt(params.batchSize, DEFAULT_PARAMETERS.batchSize)), + }; +}; diff --git a/apps/find-orphans/src/setupTests.ts b/apps/find-orphans/src/setupTests.ts new file mode 100644 index 0000000000..3604793183 --- /dev/null +++ b/apps/find-orphans/src/setupTests.ts @@ -0,0 +1,6 @@ +import '@testing-library/jest-dom/vitest'; +import { configure } from '@testing-library/react'; + +configure({ + testIdAttribute: 'data-test-id', +}); diff --git a/apps/find-orphans/test/App.test.tsx b/apps/find-orphans/test/App.test.tsx new file mode 100644 index 0000000000..8cba42577b --- /dev/null +++ b/apps/find-orphans/test/App.test.tsx @@ -0,0 +1,20 @@ +import { describe, expect, it, vi } from 'vitest'; +import { render } from '@testing-library/react'; +import App from '../src/App'; + +const mocks = vi.hoisted(() => ({ + sdk: { + location: { is: vi.fn().mockReturnValue(false) }, + }, +})); + +vi.mock('@contentful/react-apps-toolkit', () => ({ + useSDK: () => mocks.sdk, +})); + +describe('App', () => { + it('renders nothing when the location is not recognized', () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/apps/find-orphans/test/entryActions.test.ts b/apps/find-orphans/test/entryActions.test.ts new file mode 100644 index 0000000000..41ad55fe7c --- /dev/null +++ b/apps/find-orphans/test/entryActions.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it, vi } from 'vitest'; +import { archiveOrphans, ArchiveTarget } from '../src/locations/Page/utils/entryActions'; +import { createMockCma } from './mocks'; + +const entry = (id: string): ArchiveTarget => ({ id, kind: 'entry' }); +const asset = (id: string): ArchiveTarget => ({ id, kind: 'asset' }); + +describe('archiveOrphans', () => { + it('archives every target and reports progress in batches', async () => { + const { cma, entryArchive } = createMockCma(); + const onProgress = vi.fn(); + + const outcome = await archiveOrphans(cma, [entry('a'), entry('b'), entry('c')], 2, onProgress); + + expect(outcome.archivedIds).toEqual(expect.arrayContaining(['a', 'b', 'c'])); + expect(outcome.failedIds).toEqual([]); + expect(entryArchive).toHaveBeenCalledTimes(3); + // Two batches for three targets with a batch size of two. + expect(onProgress).toHaveBeenNthCalledWith(1, { current: 2, total: 3 }); + expect(onProgress).toHaveBeenNthCalledWith(2, { current: 3, total: 3 }); + }); + + it('routes each target to the endpoint matching its kind', async () => { + const { cma, entryArchive, assetArchive } = createMockCma(); + + const outcome = await archiveOrphans(cma, [entry('e1'), asset('a1')], 5, vi.fn()); + + expect(outcome.archivedIds).toEqual(expect.arrayContaining(['e1', 'a1'])); + expect(entryArchive).toHaveBeenCalledWith({ entryId: 'e1' }); + expect(assetArchive).toHaveBeenCalledWith({ assetId: 'a1' }); + }); + + it('collects failures without aborting the rest of the batch', async () => { + const { cma } = createMockCma({ failArchiveIds: ['b'] }); + + const outcome = await archiveOrphans(cma, [entry('a'), entry('b'), asset('c')], 5, vi.fn()); + + expect(outcome.archivedIds).toEqual(expect.arrayContaining(['a', 'c'])); + expect(outcome.failedIds).toEqual(['b']); + }); + + it('handles an empty selection without any API calls', async () => { + const { cma, entryArchive, assetArchive } = createMockCma(); + + const outcome = await archiveOrphans(cma, [], 5, vi.fn()); + + expect(outcome).toEqual({ archivedIds: [], failedIds: [] }); + expect(entryArchive).not.toHaveBeenCalled(); + expect(assetArchive).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/find-orphans/test/exportCsv.test.ts b/apps/find-orphans/test/exportCsv.test.ts new file mode 100644 index 0000000000..1a303f9609 --- /dev/null +++ b/apps/find-orphans/test/exportCsv.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest'; +import { buildOrphanCsv, editorUrl } from '../src/locations/Page/utils/exportCsv'; +import { OrphanResult } from '../src/locations/Page/types'; + +const context = { spaceId: 'space-id', environmentId: 'master' }; + +const makeResult = (overrides: Partial = {}): OrphanResult => ({ + kind: 'entry', + id: 'entry-1', + typeName: 'Article', + title: undefined, + createdAt: '2026-01-01T00:00:00Z', + createdBy: 'Jane Doe', + neverEdited: true, + ...overrides, +}); + +describe('editorUrl', () => { + it('routes entries and assets to their separate editor paths', () => { + expect(editorUrl(makeResult(), context)).toBe( + 'https://app.contentful.com/spaces/space-id/environments/master/entries/entry-1' + ); + expect(editorUrl(makeResult({ kind: 'asset', id: 'asset-1' }), context)).toBe( + 'https://app.contentful.com/spaces/space-id/environments/master/assets/asset-1' + ); + }); +}); + +describe('buildOrphanCsv', () => { + it('writes a header row plus one CRLF-separated row per result', () => { + const csv = buildOrphanCsv( + [makeResult(), makeResult({ id: 'entry-2', neverEdited: false })], + context + ); + const lines = csv.split('\r\n'); + expect(lines).toHaveLength(3); + // "Edited after creation", not "Never edited": the positive phrasing + // avoids double-negative cells ("Never edited: no"). + expect(lines[0]).toBe('Kind,ID,Title,Type,Created,Created by,Edited after creation,Editor URL'); + expect(lines[1]).toBe( + 'entry,entry-1,,Article,2026-01-01T00:00:00Z,Jane Doe,no,' + + 'https://app.contentful.com/spaces/space-id/environments/master/entries/entry-1' + ); + expect(lines[2]).toContain(',yes,'); + }); + + it('leaves missing titles as empty cells so spreadsheet blank-filters match them', () => { + const csv = buildOrphanCsv([makeResult({ title: undefined })], context); + expect(csv.split('\r\n')[1].startsWith('entry,entry-1,,Article')).toBe(true); + expect(csv).not.toContain('Untitled'); + }); + + it('quotes cells containing commas, quotes, or newlines per RFC 4180', () => { + const csv = buildOrphanCsv([makeResult({ title: 'Hello, "World"\nSecond line' })], context); + expect(csv).toContain('"Hello, ""World""\nSecond line"'); + }); + + it('neutralizes leading formula characters so Excel treats them as text', () => { + // Titles are content-controlled: a title like =HYPERLINK(...) would + // execute as a formula when the CSV is opened, so it gets the + // literal-text apostrophe prefix. + const csv = buildOrphanCsv([makeResult({ title: '=HYPERLINK("http://evil")' })], context); + expect(csv).toContain(`"'=HYPERLINK(""http://evil"")"`); + }); +}); diff --git a/apps/find-orphans/test/locations/ConfigScreen.test.tsx b/apps/find-orphans/test/locations/ConfigScreen.test.tsx new file mode 100644 index 0000000000..d817e2e839 --- /dev/null +++ b/apps/find-orphans/test/locations/ConfigScreen.test.tsx @@ -0,0 +1,138 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import ConfigScreen from '../../src/locations/ConfigScreen'; +import { createMockConfigSdk } from '../mocks'; + +const mocks = vi.hoisted(() => ({ + sdk: undefined as unknown, +})); + +vi.mock('@contentful/react-apps-toolkit', () => ({ + useSDK: () => mocks.sdk, +})); + +// The component registers its save handler via sdk.app.onConfigure; pull the +// latest registered callback so tests can invoke it like the web app does. +const getSaveCallback = (sdk: ReturnType) => { + const calls = sdk.app.onConfigure.mock.calls; + return calls[calls.length - 1][0] as () => Promise; +}; + +const fillAllFields = () => { + fireEvent.change(screen.getByLabelText(/Maximum entries per scan/), { + target: { value: '500' }, + }); + fireEvent.change(screen.getByLabelText(/Concurrent API requests/), { + target: { value: '5' }, + }); +}; + +describe('ConfigScreen', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('signals readiness and pre-fills the defaults on a fresh install', async () => { + const sdk = createMockConfigSdk(null); + mocks.sdk = sdk; + + render(); + + await waitFor(() => expect(sdk.app.setReady).toHaveBeenCalled()); + // The defaults are real values, not placeholders, so the app can be + // installed without touching the form. + expect(screen.getByLabelText(/Maximum entries per scan/)).toHaveValue(500); + expect(screen.getByLabelText(/Concurrent API requests/)).toHaveValue(5); + }); + + it('saves the pre-filled defaults without any edits', async () => { + const sdk = createMockConfigSdk(null); + mocks.sdk = sdk; + + render(); + await waitFor(() => expect(sdk.app.setReady).toHaveBeenCalled()); + + const result = await getSaveCallback(sdk)(); + expect(result).toMatchObject({ + parameters: { maxCandidates: 500, batchSize: 5 }, + }); + }); + + it('loads previously stored parameters', async () => { + const sdk = createMockConfigSdk({ maxCandidates: 100 }); + mocks.sdk = sdk; + + render(); + + await waitFor(() => expect(screen.getByLabelText(/Maximum entries per scan/)).toHaveValue(100)); + // A parameter missing from storage falls back to its default. + expect(screen.getByLabelText(/Concurrent API requests/)).toHaveValue(5); + }); + + it('saves the entered parameters on configure', async () => { + const sdk = createMockConfigSdk(null); + mocks.sdk = sdk; + + render(); + await waitFor(() => expect(sdk.app.setReady).toHaveBeenCalled()); + + fillAllFields(); + fireEvent.change(screen.getByLabelText(/Maximum entries per scan/), { + target: { value: '1000' }, + }); + + const result = await getSaveCallback(sdk)(); + expect(result).toMatchObject({ + parameters: { maxCandidates: 1000, batchSize: 5 }, + }); + }); + + it('rejects the save when a required field is cleared', async () => { + const sdk = createMockConfigSdk(null); + mocks.sdk = sdk; + + render(); + await waitFor(() => expect(sdk.app.setReady).toHaveBeenCalled()); + + fireEvent.change(screen.getByLabelText(/Maximum entries per scan/), { + target: { value: '' }, + }); + + await expect(getSaveCallback(sdk)()).resolves.toBe(false); + expect(sdk.notifier.error).toHaveBeenCalled(); + }); + + it('rejects the save when a value is not a positive number', async () => { + const sdk = createMockConfigSdk(null); + mocks.sdk = sdk; + + render(); + await waitFor(() => expect(sdk.app.setReady).toHaveBeenCalled()); + + fillAllFields(); + fireEvent.change(screen.getByLabelText(/Maximum entries per scan/), { + target: { value: '0' }, + }); + + await expect(getSaveCallback(sdk)()).resolves.toBe(false); + expect(sdk.notifier.error).toHaveBeenCalled(); + }); + + it('rejects a batch size above the CMA rate limit of 7', async () => { + const sdk = createMockConfigSdk(null); + mocks.sdk = sdk; + + render(); + await waitFor(() => expect(sdk.app.setReady).toHaveBeenCalled()); + + fillAllFields(); + fireEvent.change(screen.getByLabelText(/Concurrent API requests/), { + target: { value: '50' }, + }); + + await expect(getSaveCallback(sdk)()).resolves.toBe(false); + expect(sdk.notifier.error).toHaveBeenCalledWith( + '"Concurrent API requests" must be between 1 and 7.' + ); + }); +}); diff --git a/apps/find-orphans/test/locations/Page.test.tsx b/apps/find-orphans/test/locations/Page.test.tsx new file mode 100644 index 0000000000..6e2f630d36 --- /dev/null +++ b/apps/find-orphans/test/locations/Page.test.tsx @@ -0,0 +1,497 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import Page from '../../src/locations/Page'; +import { + createMockCma, + createMockSdk, + makeMockAsset, + makeMockEntry, + makeMockUser, + mockArticleContentType, +} from '../mocks'; + +const mocks = vi.hoisted(() => ({ + sdk: undefined as unknown, +})); + +vi.mock('@contentful/react-apps-toolkit', () => ({ + useSDK: () => mocks.sdk, +})); + +const scan = async (button = 'scan-untitled-button') => { + await waitFor(() => expect(screen.getByTestId(button)).toBeEnabled()); + fireEvent.click(screen.getByTestId(button)); +}; + +// Radix-based f36 Tabs activate on mousedown, not click. +const switchTab = (tabTestId: string) => { + fireEvent.mouseDown(screen.getByTestId(tabTestId)); + fireEvent.click(screen.getByTestId(tabTestId)); +}; + +describe('Page', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('renders the heading and the scan button', async () => { + const { cma } = createMockCma({ contentTypes: [mockArticleContentType] }); + mocks.sdk = createMockSdk(cma); + + render(); + + expect(screen.getByText('Find orphaned entries')).toBeInTheDocument(); + // Each criterion is a tab; the active tab's panel holds its scan button + // with the criterion's description visible above it. + expect(screen.getByTestId('tab-untitled')).toBeInTheDocument(); + expect(screen.getByTestId('tab-unreferenced')).toBeInTheDocument(); + expect(screen.getByTestId('scan-untitled-button')).toBeInTheDocument(); + await waitFor(() => expect(screen.getByTestId('scan-untitled-button')).toBeEnabled()); + }); + + it('scans, lists orphaned entries, and opens one on demand', async () => { + const orphan = makeMockEntry('orphan-1', 'article'); + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [orphan] }, + users: [makeMockUser('user-1', 'Jane', 'Doe')], + }); + const sdk = createMockSdk(cma); + mocks.sdk = sdk; + + render(); + await scan(); + + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + expect(screen.getByTestId('result-count')).toHaveTextContent('1 entry found'); + // Scoped to the table: "Untitled" also appears as a criterion radio label. + expect(within(screen.getByTestId('orphan-table')).getByText('Untitled')).toBeInTheDocument(); + expect(screen.getByText('Article')).toBeInTheDocument(); + // The creator column resolves sys.createdBy to the user's name. + expect(screen.getByText('Jane Doe')).toBeInTheDocument(); + + // Previewing is a dedicated action so row clicks never trigger the slide-in. + fireEvent.click(screen.getByText('Preview')); + expect(sdk.navigator.openEntry).toHaveBeenCalledWith('orphan-1', { slideIn: true }); + }); + + it('lists orphaned assets alongside entries and previews them in the asset editor', async () => { + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [makeMockEntry('orphan-entry', 'article')] }, + assets: [makeMockAsset('orphan-asset')], + }); + const sdk = createMockSdk(cma); + mocks.sdk = sdk; + + render(); + await scan(); + + await waitFor(() => + expect(screen.getByTestId('result-count')).toHaveTextContent('1 entry and 1 asset found') + ); + expect(screen.getByText('Asset')).toBeInTheDocument(); + + // The asset row's preview must open the asset editor, not the entry editor. + fireEvent.click(within(screen.getByTestId('orphan-row-orphan-asset')).getByText('Preview')); + expect(sdk.navigator.openAsset).toHaveBeenCalledWith('orphan-asset', { slideIn: true }); + expect(sdk.navigator.openEntry).not.toHaveBeenCalled(); + }); + + it('archives one kind via scope filtering and itemizes the confirmation', async () => { + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { + article: [makeMockEntry('entry-a', 'article'), makeMockEntry('entry-b', 'article')], + }, + assets: [makeMockAsset('asset-a')], + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + + // "Archive only entries" = hide assets via the scope checkbox, then + // select-all: the hidden asset can never be swept along. + fireEvent.click(screen.getByTestId('scope-assets')); + fireEvent.click(screen.getByTestId('select-all')); + expect(screen.getByTestId('result-count')).toHaveTextContent('2 selected'); + + // The confirmation names kinds, not generic items. + fireEvent.click(screen.getByTestId('archive-button')); + expect(await screen.findByText('Archive 2 entries')).toBeInTheDocument(); + + // Select-all on a mixed list itemizes both kinds. + fireEvent.click(screen.getByText('Cancel')); + fireEvent.click(screen.getByTestId('scope-assets')); + fireEvent.click(screen.getByTestId('select-all')); + fireEvent.click(screen.getByTestId('archive-button')); + expect(await screen.findByText('Archive 2 entries and 1 asset')).toBeInTheDocument(); + }); + + it('finds unreferenced drafts with their real titles when that criterion is selected', async () => { + // Titled, so invisible to the untitled scan — but nothing links to it. + const deadPage = makeMockEntry('dead-page', 'article', { + title: { 'en-US': 'Old landing page' }, + }); + const linked = makeMockEntry('linked', 'article', { title: { 'en-US': 'Linked page' } }); + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [deadPage, linked] }, + referenceCounts: { linked: 2 }, + }); + mocks.sdk = createMockSdk(cma); + + render(); + switchTab('tab-unreferenced'); + await scan('scan-unreferenced-button'); + + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + expect(screen.getByTestId('result-count')).toHaveTextContent('1 entry found'); + // Unreferenced results show their real titles, not the Untitled placeholder. + expect(screen.getByText('Old landing page')).toBeInTheDocument(); + expect(screen.queryByTestId('orphan-row-linked')).not.toBeInTheDocument(); + // Edit history says nothing about referenced-ness, so this tab has no + // never-edited view switch. + expect(screen.queryByTestId('filter-never-edited')).not.toBeInTheDocument(); + expect(screen.queryByTestId('filter-all')).not.toBeInTheDocument(); + }); + + it('filters results to never-edited items with a visible toggle', async () => { + const untouched = makeMockEntry('untouched', 'article'); + const edited = makeMockEntry('edited', 'article', {}, '2026-06-01T00:00:00Z', 4); + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [untouched, edited] }, + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + + // The untitled tab starts on the never-edited view — the edited draft + // is hidden, but visibly: both pills carry counts. + expect(screen.getByTestId('result-count')).toHaveTextContent('1 entry found'); + expect(screen.getByTestId('filter-all')).toHaveTextContent('All (2)'); + expect(screen.getByTestId('filter-never-edited')).toHaveTextContent('Never edited (1)'); + expect(screen.queryByTestId('orphan-row-edited')).not.toBeInTheDocument(); + + // Switching to "All" broadens instantly — no re-scan. + fireEvent.click(screen.getByTestId('filter-all')); + expect(screen.getByTestId('result-count')).toHaveTextContent('2 entries found'); + expect(screen.getByTestId('orphan-row-edited')).toBeInTheDocument(); + + // Re-narrowing drops selections of rows it hides, so "Archive selected" + // can never archive something invisible. + fireEvent.click(within(screen.getByTestId('orphan-row-edited')).getByRole('checkbox')); + expect(screen.getByTestId('result-count')).toHaveTextContent('1 selected'); + fireEvent.click(screen.getByTestId('filter-never-edited')); + expect(screen.getByTestId('result-count')).not.toHaveTextContent('selected'); + }); + + it('explains when the never-edited filter hides every result', async () => { + const edited = makeMockEntry('edited', 'article', {}, '2026-06-01T00:00:00Z', 4); + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [edited] }, + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + + // Everything found was edited, so with the never-edited view active the + // table gives way to an explanatory note — the view pills stay above it. + await waitFor(() => expect(screen.getByTestId('filtered-empty-note')).toBeInTheDocument()); + expect(screen.queryByTestId('orphan-table')).not.toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('filter-all')); + expect(screen.getByTestId('orphan-table')).toBeInTheDocument(); + expect(screen.queryByTestId('filtered-empty-note')).not.toBeInTheDocument(); + }); + + it('scope checkboxes filter cached results client-side after a scan', async () => { + const { cma, entryGetMany } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [makeMockEntry('orphan-entry', 'article')] }, + assets: [makeMockAsset('orphan-asset')], + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + await waitFor(() => + expect(screen.getByTestId('result-count')).toHaveTextContent('1 entry and 1 asset found') + ); + fireEvent.click(screen.getByTestId('select-all')); + const scanQueries = entryGetMany.mock.calls.length; + + // Unchecking a scope hides that kind from the cached results instantly — + // no re-scan — and deselects the hidden rows. + fireEvent.click(screen.getByTestId('scope-assets')); + expect(screen.getByTestId('result-count')).toHaveTextContent('1 entry found — 1 selected'); + expect(screen.queryByTestId('orphan-row-orphan-asset')).not.toBeInTheDocument(); + expect(entryGetMany.mock.calls.length).toBe(scanQueries); + + // Rechecking brings the rows back (still deselected). + fireEvent.click(screen.getByTestId('scope-assets')); + expect(screen.getByTestId('orphan-row-orphan-asset')).toBeInTheDocument(); + expect( + within(screen.getByTestId('orphan-row-orphan-asset')).getByRole('checkbox') + ).not.toBeChecked(); + }); + + it('scans only the checked scopes', async () => { + const { cma, assetGetMany } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [makeMockEntry('orphan-entry', 'article')] }, + assets: [makeMockAsset('orphan-asset')], + }); + mocks.sdk = createMockSdk(cma); + + render(); + // Uncheck assets: the scan must skip the media library entirely. + fireEvent.click(screen.getByTestId('scope-assets')); + await scan(); + + await waitFor(() => + expect(screen.getByTestId('result-count')).toHaveTextContent('1 entry found') + ); + expect(assetGetMany).not.toHaveBeenCalled(); + + // With both scopes off there is nothing to scan, so the scan buttons + // disable — the scope setting is shared, so this holds on both tabs. + fireEvent.click(screen.getByTestId('scope-entries')); + expect(screen.getByTestId('scan-untitled-button')).toBeDisabled(); + switchTab('tab-unreferenced'); + expect(screen.getByTestId('scan-unreferenced-button')).toBeDisabled(); + }); + + it('keeps each tab’s results when switching between tabs without re-scanning', async () => { + const { cma, entryGetMany } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [makeMockEntry('orphan-1', 'article')] }, + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + const scanQueries = entryGetMany.mock.calls.length; + + // The unreferenced tab has not scanned: it shows its own placeholder. + switchTab('tab-unreferenced'); + expect(screen.getByTestId('pre-scan-placeholder')).toBeInTheDocument(); + + // Switching back shows the cached untitled results without any new scan. + switchTab('tab-untitled'); + expect(screen.getByTestId('orphan-table')).toBeInTheDocument(); + expect(entryGetMany.mock.calls.length).toBe(scanQueries); + // The tab label carries the cached count. + expect(screen.getByTestId('tab-untitled')).toHaveTextContent('Untitled drafts (1)'); + }); + + it('shows an empty state when nothing matches', async () => { + const { cma } = createMockCma({ contentTypes: [mockArticleContentType] }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + + await waitFor(() => expect(screen.getByTestId('empty-note')).toBeInTheDocument()); + }); + + it('shows a placeholder before the first scan and removes it afterwards', async () => { + const { cma } = createMockCma({ contentTypes: [mockArticleContentType] }); + mocks.sdk = createMockSdk(cma); + + render(); + // The results area is never blank: a placeholder fills it on fresh open. + expect(screen.getByTestId('pre-scan-placeholder')).toBeInTheDocument(); + + await scan(); + await waitFor(() => expect(screen.getByTestId('empty-note')).toBeInTheDocument()); + expect(screen.queryByTestId('pre-scan-placeholder')).not.toBeInTheDocument(); + }); + + it('selects all entries and archives them after confirmation', async () => { + const orphanA = makeMockEntry('orphan-a', 'article'); + const orphanB = makeMockEntry('orphan-b', 'article'); + const { cma, entryArchive } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [orphanA, orphanB] }, + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + + // The archive button only activates once something is selected. + expect(screen.getByTestId('archive-button')).toBeDisabled(); + fireEvent.click(screen.getByTestId('select-all')); + expect(screen.getByTestId('archive-button')).toBeEnabled(); + + fireEvent.click(screen.getByTestId('archive-button')); + // The confirmation modal must be accepted before anything is archived, + // and it carries the deep links to the web app's archived views where + // permanent deletion lives. + expect(entryArchive).not.toHaveBeenCalled(); + const archivedLink = await screen.findByTestId('archived-entries-link'); + expect(archivedLink).toHaveAttribute( + 'href', + 'https://app.contentful.com/spaces/space-id/environments/master/views/entries?filters.0.key=__status&filters.0.op=&filters.0.val=archived' + ); + expect(archivedLink).toHaveAttribute('target', '_blank'); + fireEvent.click(await screen.findByText('Archive 2 entries')); + + await waitFor(() => expect(entryArchive).toHaveBeenCalledTimes(2)); + // Archived entries leave the list, which empties it here. + await waitFor(() => expect(screen.getByTestId('empty-note')).toBeInTheDocument()); + }); + + it('paginates results past 50 rows while select-all spans every page', async () => { + const entries = Array.from({ length: 120 }, (_, i) => makeMockEntry(`orphan-${i}`, 'article')); + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: entries }, + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + + // Only the first page renders, but the counts describe the whole set. + expect(screen.getAllByTestId(/^orphan-row-/)).toHaveLength(50); + expect(screen.getByTestId('result-count')).toHaveTextContent('120 entries found'); + expect(screen.getByTestId('results-pagination')).toBeInTheDocument(); + + // Select-all spans every page, not just the rendered one — "archive all + // 120" must be one click, with the count line spelling out the sweep. + fireEvent.click(screen.getByTestId('select-all')); + expect(screen.getByTestId('result-count')).toHaveTextContent('120 selected'); + + // Pagination is navigation, not a view-narrowing filter: rows on the + // next page arrive already selected, nothing was deselected by paging. + fireEvent.click(screen.getByText('Next')); + const secondPageRows = screen.getAllByTestId(/^orphan-row-/); + expect(secondPageRows).toHaveLength(50); + expect(within(secondPageRows[0]).getByRole('checkbox')).toBeChecked(); + + // Real view changes reset to the first page: deep pages of the previous + // view are meaningless coordinates in the reshaped list. + fireEvent.click(screen.getByText('Next')); + expect(screen.getAllByTestId(/^orphan-row-/)).toHaveLength(20); + fireEvent.click(screen.getByTestId('filter-all')); + expect(screen.getByTestId('orphan-row-orphan-0')).toBeInTheDocument(); + }); + + it('exports every cached result to CSV, including rows hidden by the view filters', async () => { + const untouched = makeMockEntry('untouched', 'article'); + const edited = makeMockEntry('edited', 'article', {}, '2026-06-01T00:00:00Z', 4); + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [untouched, edited] }, + }); + mocks.sdk = createMockSdk(cma); + + // jsdom implements neither object URLs nor real downloads; capture the + // blob handed to the download anchor and read the CSV back out of it. + const originalCreate = URL.createObjectURL; + const originalRevoke = URL.revokeObjectURL; + const createObjectURL = vi.fn((_blob: Blob) => 'blob:mock'); + URL.createObjectURL = createObjectURL; + URL.revokeObjectURL = vi.fn(); + try { + render(); + await scan(); + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + + // The default never-edited view hides the edited row from the table... + expect(screen.queryByTestId('orphan-row-edited')).not.toBeInTheDocument(); + fireEvent.click(screen.getByTestId('export-csv-button')); + + const blob = createObjectURL.mock.calls[0][0]; + // jsdom's Blob has no .text(); FileReader is the API it does implement. + const csv = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = () => reject(reader.error); + reader.readAsText(blob); + }); + const lines = csv.split('\r\n'); + // ...but the export is broad like the scan: both rows are in the + // file, distinguished by the "Edited after creation" column (positive + // phrasing, so never-edited items read "no"), with editor links. + const untouchedLine = lines.find((line) => line.includes('/entries/untouched')); + const editedLine = lines.find((line) => line.includes('/entries/edited')); + expect(untouchedLine).toContain(',no,'); + expect(editedLine).toContain(',yes,'); + expect(untouchedLine).toContain( + 'https://app.contentful.com/spaces/space-id/environments/master/entries/untouched' + ); + expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock'); + } finally { + URL.createObjectURL = originalCreate; + URL.revokeObjectURL = originalRevoke; + } + }); + + it('clamps the page when archiving empties the last page', async () => { + const entries = Array.from({ length: 60 }, (_, i) => makeMockEntry(`orphan-${i}`, 'article')); + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: entries }, + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + + // Select exactly the last page's 10 rows and archive them away. + fireEvent.click(screen.getByText('Next')); + const lastPageRows = screen.getAllByTestId(/^orphan-row-/); + expect(lastPageRows).toHaveLength(10); + for (const row of lastPageRows) { + fireEvent.click(within(row).getByRole('checkbox')); + } + fireEvent.click(screen.getByTestId('archive-button')); + fireEvent.click(await screen.findByText('Archive 10 entries')); + + // The page the user was on no longer exists: the view clamps to the + // remaining single page instead of rendering an empty table, and the + // pagination control disappears at exactly one page. + await waitFor(() => expect(screen.getAllByTestId(/^orphan-row-/)).toHaveLength(50)); + expect(screen.queryByTestId('results-pagination')).not.toBeInTheDocument(); + }); + + it('keeps entries that failed to archive listed and selected', async () => { + const orphanA = makeMockEntry('orphan-a', 'article'); + const orphanB = makeMockEntry('orphan-b', 'article'); + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType], + entriesByContentType: { article: [orphanA, orphanB] }, + failArchiveIds: ['orphan-b'], + }); + mocks.sdk = createMockSdk(cma); + + render(); + await scan(); + await waitFor(() => expect(screen.getByTestId('orphan-table')).toBeInTheDocument()); + + fireEvent.click(screen.getByTestId('select-all')); + fireEvent.click(screen.getByTestId('archive-button')); + fireEvent.click(await screen.findByText('Archive 2 entries')); + + await waitFor(() => + expect(screen.queryByTestId('orphan-row-orphan-a')).not.toBeInTheDocument() + ); + expect(screen.getByTestId('orphan-row-orphan-b')).toBeInTheDocument(); + // The f36 testId sits on the checkbox wrapper, so assert on the input. + expect(within(screen.getByTestId('orphan-row-orphan-b')).getByRole('checkbox')).toBeChecked(); + }); +}); diff --git a/apps/find-orphans/test/mocks/index.ts b/apps/find-orphans/test/mocks/index.ts new file mode 100644 index 0000000000..937ff550b5 --- /dev/null +++ b/apps/find-orphans/test/mocks/index.ts @@ -0,0 +1,4 @@ +export * from './mockContentTypes'; +export * from './mockEntries'; +export * from './mockCma'; +export * from './mockSdk'; diff --git a/apps/find-orphans/test/mocks/mockCma.ts b/apps/find-orphans/test/mocks/mockCma.ts new file mode 100644 index 0000000000..eb28bbd396 --- /dev/null +++ b/apps/find-orphans/test/mocks/mockCma.ts @@ -0,0 +1,98 @@ +import { vi } from 'vitest'; +import { AssetProps, ContentTypeProps, EntryProps, UserProps } from 'contentful-management'; +import { CmaClient } from '../../src/locations/Page/types'; + +const collection = (items: T[], total = items.length) => ({ + sys: { type: 'Array' as const }, + items, + total, + skip: 0, + limit: 100, +}); + +export interface MockCmaOptions { + contentTypes?: ContentTypeProps[]; + /** Entries returned for entry queries, keyed by content type id. */ + entriesByContentType?: Record; + /** Assets returned for asset queries. */ + assets?: AssetProps[]; + /** Space users returned for the creator-name lookup. */ + users?: UserProps[]; + /** + * Incoming-reference totals for links_to_entry / links_to_asset count + * queries, keyed by target id. Ids absent here count as unreferenced. + */ + referenceCounts?: Record; + /** Entry/asset ids whose archive call should reject. */ + failArchiveIds?: string[]; +} + +export const createMockCma = ({ + contentTypes = [], + entriesByContentType = {}, + assets = [], + users = [], + referenceCounts = {}, + failArchiveIds = [], +}: MockCmaOptions = {}) => { + const contentTypeGetMany = vi.fn().mockResolvedValue(collection(contentTypes)); + const entryGetMany = vi + .fn() + .mockImplementation(({ query }: { query: Record }) => { + // Reference-count queries (limit: 0) only read the collection total. + if ('links_to_entry' in query || 'links_to_asset' in query) { + const targetId = (query.links_to_entry ?? query.links_to_asset) as string; + return Promise.resolve(collection([], referenceCounts[targetId] ?? 0)); + } + const entries = entriesByContentType[query.content_type as string] ?? []; + if (query.skip && (query.skip as number) >= entries.length) { + return Promise.resolve(collection([], entries.length)); + } + return Promise.resolve(collection(entries)); + }); + const assetGetMany = vi + .fn() + .mockImplementation(({ query }: { query: Record }) => { + if (query.skip && (query.skip as number) >= assets.length) { + return Promise.resolve(collection([], assets.length)); + } + return Promise.resolve(collection(assets)); + }); + const userGetManyForSpace = vi + .fn() + .mockImplementation(({ query }: { query: Record }) => { + // Mirror the sys.id[in] filter so tests can model users who left the + // space (present in an entry's createdBy but absent here). + const requestedIds = String(query['sys.id[in]'] ?? '').split(','); + return Promise.resolve( + collection(users.filter((user) => requestedIds.includes(user.sys.id))) + ); + }); + const rejectOrResolve = (id: string) => + failArchiveIds.includes(id) + ? Promise.reject(new Error(`Cannot archive ${id}`)) + : Promise.resolve({ sys: { id } }); + const entryArchive = vi + .fn() + .mockImplementation(({ entryId }: { entryId: string }) => rejectOrResolve(entryId)); + const assetArchive = vi + .fn() + .mockImplementation(({ assetId }: { assetId: string }) => rejectOrResolve(assetId)); + + const cma = { + contentType: { getMany: contentTypeGetMany }, + entry: { getMany: entryGetMany, archive: entryArchive }, + asset: { getMany: assetGetMany, archive: assetArchive }, + user: { getManyForSpace: userGetManyForSpace }, + }; + + return { + cma: cma as unknown as CmaClient, + contentTypeGetMany, + entryGetMany, + entryArchive, + assetGetMany, + assetArchive, + userGetManyForSpace, + }; +}; diff --git a/apps/find-orphans/test/mocks/mockContentTypes.ts b/apps/find-orphans/test/mocks/mockContentTypes.ts new file mode 100644 index 0000000000..588b01228e --- /dev/null +++ b/apps/find-orphans/test/mocks/mockContentTypes.ts @@ -0,0 +1,85 @@ +import { ContentTypeProps } from 'contentful-management'; + +const baseSys = (id: string) => + ({ + id, + type: 'ContentType', + version: 1, + space: { sys: { type: 'Link', linkType: 'Space', id: 'space-id' } }, + environment: { sys: { type: 'Link', linkType: 'Environment', id: 'master' } }, + createdAt: '2026-01-01T00:00:00Z', + updatedAt: '2026-01-01T00:00:00Z', + } as ContentTypeProps['sys']); + +export const mockArticleContentType: ContentTypeProps = { + sys: baseSys('article'), + name: 'Article', + description: '', + displayField: 'title', + fields: [ + { + id: 'title', + name: 'Title', + type: 'Symbol', + localized: false, + required: false, + disabled: false, + omitted: false, + validations: [], + }, + { + id: 'body', + name: 'Body', + type: 'Text', + localized: false, + required: false, + disabled: false, + omitted: false, + validations: [], + }, + ], +}; + +/** + * Content type with no display field configured at all — common for + * component types (banners, teasers) where nobody marks an entry title. + * Every entry of such a type renders as "Untitled" in Contentful. + */ +export const mockNoDisplayFieldContentType: ContentTypeProps = { + sys: baseSys('banner'), + name: 'Basic Promotional Banner', + description: '', + displayField: null as unknown as string, + fields: [ + { + id: 'cta', + name: 'Call to action', + type: 'Symbol', + localized: false, + required: false, + disabled: false, + omitted: false, + validations: [], + }, + ], +}; + +/** Content type whose display field is not a text field (edge case: no scannable title). */ +export const mockNumericDisplayContentType: ContentTypeProps = { + sys: baseSys('counter'), + name: 'Counter', + description: '', + displayField: 'count', + fields: [ + { + id: 'count', + name: 'Count', + type: 'Integer', + localized: false, + required: false, + disabled: false, + omitted: false, + validations: [], + }, + ], +}; diff --git a/apps/find-orphans/test/mocks/mockEntries.ts b/apps/find-orphans/test/mocks/mockEntries.ts new file mode 100644 index 0000000000..f4a1651ff6 --- /dev/null +++ b/apps/find-orphans/test/mocks/mockEntries.ts @@ -0,0 +1,68 @@ +import { AssetProps, EntryProps, UserProps } from 'contentful-management'; + +const createdByLink = (userId: string) => ({ + sys: { type: 'Link', linkType: 'User', id: userId }, +}); + +export const makeMockEntry = ( + id: string, + contentTypeId: string, + fields: EntryProps['fields'] = {}, + createdAt = '2026-01-01T00:00:00Z', + // Version 1 = never saved after creation; pass a higher version to model + // an entry that has been edited. + version = 1, + createdById = 'user-1' +): EntryProps => + ({ + sys: { + id, + type: 'Entry', + version, + contentType: { sys: { type: 'Link', linkType: 'ContentType', id: contentTypeId } }, + space: { sys: { type: 'Link', linkType: 'Space', id: 'space-id' } }, + environment: { sys: { type: 'Link', linkType: 'Environment', id: 'master' } }, + createdAt, + createdBy: createdByLink(createdById), + updatedAt: createdAt, + }, + fields, + } as EntryProps); + +export const makeMockAsset = ( + id: string, + // Assets always define a localized title field, so a title of undefined + // models the CMA omitting `fields` under a select query (the orphan shape). + title?: string, + createdAt = '2026-01-01T00:00:00Z', + version = 1, + createdById = 'user-1' +): AssetProps => + ({ + sys: { + id, + type: 'Asset', + version, + space: { sys: { type: 'Link', linkType: 'Space', id: 'space-id' } }, + environment: { sys: { type: 'Link', linkType: 'Environment', id: 'master' } }, + createdAt, + createdBy: createdByLink(createdById), + updatedAt: createdAt, + }, + ...(title !== undefined ? { fields: { title: { 'en-US': title } } } : {}), + // Double cast: the mock sys carries only what the scan reads, which does + // not "sufficiently overlap" with the full AssetProps sys shape. + } as unknown as AssetProps); + +export const makeMockUser = ( + id: string, + firstName: string, + lastName: string, + email = `${id}@example.com` +): UserProps => + ({ + sys: { id, type: 'User' }, + firstName, + lastName, + email, + } as UserProps); diff --git a/apps/find-orphans/test/mocks/mockSdk.ts b/apps/find-orphans/test/mocks/mockSdk.ts new file mode 100644 index 0000000000..c540e8424e --- /dev/null +++ b/apps/find-orphans/test/mocks/mockSdk.ts @@ -0,0 +1,43 @@ +import { vi } from 'vitest'; +import { CmaClient } from '../../src/locations/Page/types'; +import { AppInstallationParameters } from '../../src/parameters'; + +export const createMockSdk = ( + cma: CmaClient, + installation: Partial = {} +) => ({ + cma, + parameters: { + installation, + }, + locales: { + default: 'en-US', + available: ['en-US'], + }, + location: { + is: vi.fn().mockReturnValue(false), + }, + navigator: { + openEntry: vi.fn(), + openAsset: vi.fn(), + }, + ids: { + space: 'space-id', + environment: 'master', + }, +}); + +/** SDK mock for the ConfigScreen location (`ConfigAppSDK` surface). */ +export const createMockConfigSdk = (storedParameters: unknown = null) => ({ + app: { + // onConfigure registers the save callback; the test grabs it from + // mock.calls to simulate the user clicking "Save". + onConfigure: vi.fn(), + getParameters: vi.fn().mockResolvedValue(storedParameters), + getCurrentState: vi.fn().mockResolvedValue({ EditorInterface: {} }), + setReady: vi.fn(), + }, + notifier: { + error: vi.fn(), + }, +}); diff --git a/apps/find-orphans/test/orphanFinder.test.ts b/apps/find-orphans/test/orphanFinder.test.ts new file mode 100644 index 0000000000..e0cc03b486 --- /dev/null +++ b/apps/find-orphans/test/orphanFinder.test.ts @@ -0,0 +1,461 @@ +import { describe, expect, it, vi } from 'vitest'; +import { + buildDraftAssetQuery, + buildDraftEntryQuery, + fetchAllContentTypes, + findOrphans, + getAssetTitle, + getEntryTitle, + getTextDisplayFieldId, +} from '../src/locations/Page/utils/orphanFinder'; +import { + createMockCma, + makeMockAsset, + makeMockEntry, + makeMockUser, + mockArticleContentType, + mockNoDisplayFieldContentType, + mockNumericDisplayContentType, +} from './mocks'; + +const noProgress = vi.fn(); +const options = { + criterion: 'untitled' as const, + maxCandidates: 500, + batchSize: 5, + includeEntries: true, + includeAssets: true, +}; + +describe('getTextDisplayFieldId', () => { + it('returns the display field id when it is a text field', () => { + expect(getTextDisplayFieldId(mockArticleContentType)).toBe('title'); + }); + + it('returns undefined when the display field is not a text field', () => { + expect(getTextDisplayFieldId(mockNumericDisplayContentType)).toBeUndefined(); + }); +}); + +describe('getEntryTitle', () => { + it('returns the display field value in the default locale', () => { + const entry = makeMockEntry('e1', 'article', { title: { 'en-US': 'Hello' } }); + expect(getEntryTitle(entry, mockArticleContentType, 'en-US')).toBe('Hello'); + }); + + it('returns undefined for empty or missing values', () => { + const empty = makeMockEntry('e1', 'article', { title: { 'en-US': ' ' } }); + const missing = makeMockEntry('e2', 'article'); + expect(getEntryTitle(empty, mockArticleContentType, 'en-US')).toBeUndefined(); + expect(getEntryTitle(missing, mockArticleContentType, 'en-US')).toBeUndefined(); + }); + + it('returns undefined when the fields object is absent entirely', () => { + // With a `select` query, the CMA omits `fields` from entries that have no + // value in any selected field — the exact shape of an orphaned entry. + const noFields = makeMockEntry('e3', 'article'); + delete (noFields as Partial).fields; + expect(getEntryTitle(noFields, mockArticleContentType, 'en-US')).toBeUndefined(); + }); +}); + +describe('getAssetTitle', () => { + it('returns the title in the default locale', () => { + expect(getAssetTitle(makeMockAsset('a1', 'Sunset photo'), 'en-US')).toBe('Sunset photo'); + }); + + it('returns undefined for whitespace-only titles and absent fields', () => { + expect(getAssetTitle(makeMockAsset('a1', ' '), 'en-US')).toBeUndefined(); + // No title argument = no fields object, the shape a select query returns + // for an asset with no value in any selected field. + expect(getAssetTitle(makeMockAsset('a2'), 'en-US')).toBeUndefined(); + }); +}); + +describe('buildDraftEntryQuery', () => { + it('scopes to non-archived draft entries of the content type', () => { + const query = buildDraftEntryQuery(mockArticleContentType); + expect(query).toMatchObject({ + content_type: 'article', + 'sys.publishedAt[exists]': false, + 'sys.archivedAt[exists]': false, + }); + }); + + it('orders with a sys.id tiebreaker for stable skip-based pagination', () => { + // Without the tiebreaker, entries sharing an updatedAt (bulk imports) + // sort non-deterministically and paging can drop or duplicate them. + expect(buildDraftEntryQuery(mockArticleContentType).order).toBe('-sys.updatedAt,sys.id'); + }); + + it('selects only sys and the display field to keep payloads small', () => { + expect(buildDraftEntryQuery(mockArticleContentType).select).toBe('sys,fields.title'); + }); +}); + +describe('buildDraftAssetQuery', () => { + it('mirrors the entry query without a content type filter', () => { + expect(buildDraftAssetQuery()).toEqual({ + 'sys.publishedAt[exists]': false, + 'sys.archivedAt[exists]': false, + order: '-sys.updatedAt,sys.id', + select: 'sys,fields.title', + }); + }); +}); + +describe('fetchAllContentTypes', () => { + it('returns all content types', async () => { + const { cma } = createMockCma({ + contentTypes: [mockArticleContentType, mockNumericDisplayContentType], + }); + const types = await fetchAllContentTypes(cma); + expect(types).toHaveLength(2); + }); +}); + +describe('findOrphans', () => { + it('lists drafts with an empty display field and excludes titled ones', async () => { + const orphan = makeMockEntry('orphan', 'article'); + const healthy = makeMockEntry('healthy', 'article', { title: { 'en-US': 'Has title' } }); + const { cma } = createMockCma({ + entriesByContentType: { article: [orphan, healthy] }, + }); + + const outcome = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, options); + + expect(outcome.truncated).toBe(false); + expect(outcome.results).toHaveLength(1); + expect(outcome.results[0]).toMatchObject({ kind: 'entry', id: 'orphan', typeName: 'Article' }); + }); + + it('treats a whitespace-only title as missing', async () => { + const blank = makeMockEntry('blank', 'article', { title: { 'en-US': ' ' } }); + const { cma } = createMockCma({ entriesByContentType: { article: [blank] } }); + + const outcome = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, options); + + expect(outcome.results.map((r) => r.id)).toEqual(['blank']); + }); + + it('lists untitled draft assets and excludes titled ones', async () => { + const { cma } = createMockCma({ + assets: [makeMockAsset('orphan-asset'), makeMockAsset('photo', 'Sunset photo')], + }); + + const outcome = await findOrphans(cma, [], 'en-US', noProgress, options); + + expect(outcome.results).toHaveLength(1); + expect(outcome.results[0]).toMatchObject({ + kind: 'asset', + id: 'orphan-asset', + typeName: 'Asset', + }); + }); + + it('resolves creator names, labelling app identities and departed users', async () => { + const byJane = makeMockEntry('by-jane', 'article', {}, '2026-01-01T00:00:00Z', 1, 'user-jane'); + // A creator id with no matching space user models someone who left. + const departed = makeMockEntry( + 'departed', + 'article', + {}, + '2026-01-01T00:00:00Z', + 1, + 'user-gone' + ); + const appMade = makeMockEntry('app-made', 'article'); + (appMade.sys.createdBy as { sys: { linkType: string } }).sys.linkType = 'AppDefinition'; + const { cma } = createMockCma({ + entriesByContentType: { article: [byJane, departed, appMade] }, + assets: [makeMockAsset('asset-by-jane', undefined, '2026-01-01T00:00:00Z', 1, 'user-jane')], + users: [makeMockUser('user-jane', 'Jane', 'Doe')], + }); + + const outcome = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, options); + + const creators = Object.fromEntries(outcome.results.map((r) => [r.id, r.createdBy])); + expect(creators).toEqual({ + 'by-jane': 'Jane Doe', + departed: 'Unknown user', + 'app-made': 'App', + 'asset-by-jane': 'Jane Doe', + }); + }); + + it('skips the users lookup when no orphan has a user creator', async () => { + const appMade = makeMockEntry('app-made', 'article'); + (appMade.sys.createdBy as { sys: { linkType: string } }).sys.linkType = 'AppDefinition'; + const { cma, userGetManyForSpace } = createMockCma({ + entriesByContentType: { article: [appMade] }, + }); + + await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, options); + + expect(userGetManyForSpace).not.toHaveBeenCalled(); + }); + + it('surfaces the creation date on results', async () => { + const orphan = makeMockEntry('orphan', 'article', {}, '2026-03-05T00:00:00Z'); + const { cma } = createMockCma({ entriesByContentType: { article: [orphan] } }); + + const outcome = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, options); + + expect(outcome.results[0].createdAt).toBe('2026-03-05T00:00:00Z'); + }); + + it('includes edited untitled drafts and marks never-edited ones with a flag', async () => { + // The scan is deliberately broad: edit history never excludes a result. + // sys.version increments on every save, so version 1 = never edited — + // carried as a flag for the UI's "Never edited" filter. + const untouched = makeMockEntry('untouched', 'article'); + const edited = makeMockEntry('edited', 'article', {}, '2026-06-01T00:00:00Z', 4); + const { cma } = createMockCma({ + entriesByContentType: { article: [untouched, edited] }, + assets: [makeMockAsset('touched-asset', undefined, '2026-06-01T00:00:00Z', 3)], + }); + + const outcome = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, options); + + const flags = Object.fromEntries(outcome.results.map((r) => [r.id, r.neverEdited])); + expect(flags).toEqual({ + untouched: true, + edited: false, + 'touched-asset': false, + }); + }); + + it('skips entries or assets when their scope is off', async () => { + const { cma, entryGetMany, assetGetMany } = createMockCma({ + entriesByContentType: { article: [makeMockEntry('orphan', 'article')] }, + assets: [makeMockAsset('orphan-asset')], + }); + + const entriesOnly = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, { + ...options, + includeAssets: false, + }); + expect(entriesOnly.results.map((r) => r.id)).toEqual(['orphan']); + expect(assetGetMany).not.toHaveBeenCalled(); + + entryGetMany.mockClear(); + const assetsOnly = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, { + ...options, + includeEntries: false, + }); + expect(assetsOnly.results.map((r) => r.id)).toEqual(['orphan-asset']); + expect(entryGetMany).not.toHaveBeenCalled(); + }); + + it('finds untitled never-edited drafts under both criteria', async () => { + // Cross-criterion consistency: a version-1 untitled draft that nothing + // references is an orphan by either definition, so it must appear in + // both scans' results. + const orphan = makeMockEntry('orphan', 'article'); + const { cma } = createMockCma({ entriesByContentType: { article: [orphan] } }); + + const untitled = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, options); + const unreferenced = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, { + ...options, + criterion: 'unreferenced', + }); + + expect(untitled.results.map((r) => r.id)).toEqual(['orphan']); + expect(unreferenced.results.map((r) => r.id)).toEqual(['orphan']); + }); + + it('skips content types with no display field configured (unreferenced-scan-only territory)', async () => { + // Component types (banners, teasers) often have no entry title at all; + // their entries always render as "Untitled" in Contentful, but the + // untitled scan deliberately skips them — flagging every draft of such + // types would sweep in legitimate work-in-progress. They still surface + // in the unreferenced scan, which is why an "Untitled" row can appear + // there and not here. Pinned so the discrepancy is documented behavior. + const bannerDraft = makeMockEntry('banner-1', 'banner'); + const { cma, entryGetMany } = createMockCma({ + entriesByContentType: { banner: [bannerDraft] }, + }); + + const outcome = await findOrphans(cma, [mockNoDisplayFieldContentType], 'en-US', noProgress, { + ...options, + includeAssets: false, + }); + + expect(outcome.results).toHaveLength(0); + expect(entryGetMany).not.toHaveBeenCalled(); + + // The same type IS scanned under the unreferenced criterion. + const unreferenced = await findOrphans( + cma, + [mockNoDisplayFieldContentType], + 'en-US', + noProgress, + { + ...options, + criterion: 'unreferenced', + includeAssets: false, + } + ); + expect(unreferenced.results.map((r) => r.id)).toEqual(['banner-1']); + }); + + it('skips content types without a text display field', async () => { + // Entries of such types cannot be missing a title, so querying them + // would only waste API calls. + const { cma, entryGetMany } = createMockCma({ + entriesByContentType: { counter: [makeMockEntry('c1', 'counter')] }, + }); + + const outcome = await findOrphans(cma, [mockNumericDisplayContentType], 'en-US', noProgress, { + ...options, + includeAssets: false, + }); + + expect(outcome.results).toHaveLength(0); + expect(entryGetMany).not.toHaveBeenCalled(); + }); + + it('caps the fetched drafts at maxCandidates across entries and assets', async () => { + const entries = [makeMockEntry('e1', 'article'), makeMockEntry('e2', 'article')]; + const { cma, assetGetMany } = createMockCma({ + entriesByContentType: { article: entries }, + assets: [makeMockAsset('a1')], + }); + + const outcome = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, { + ...options, + maxCandidates: 1, + }); + + expect(outcome.truncated).toBe(true); + expect(outcome.results).toHaveLength(1); + // Entries consumed the whole budget, so the asset query never fires. + expect(assetGetMany).not.toHaveBeenCalled(); + }); + + it('combines results from multiple content types scanned in one parallel chunk', async () => { + const articleType = mockArticleContentType; + // A second scannable type: clone the article type under a different id + // so both have a text display field. + const noteType = { + ...mockArticleContentType, + sys: { ...mockArticleContentType.sys, id: 'note' }, + name: 'Note', + }; + const { cma, entryGetMany } = createMockCma({ + entriesByContentType: { + article: [makeMockEntry('a1', 'article')], + note: [makeMockEntry('n1', 'note')], + }, + }); + + const outcome = await findOrphans(cma, [articleType, noteType], 'en-US', noProgress, { + ...options, + includeAssets: false, + }); + + expect(outcome.results.map((r) => r.id)).toEqual(expect.arrayContaining(['a1', 'n1'])); + // One entry query per content type; there is no reference-count phase. + expect(entryGetMany).toHaveBeenCalledTimes(2); + }); + + it('reports content type and asset steps against one combined total', async () => { + const onProgress = vi.fn(); + const orphan = makeMockEntry('orphan', 'article'); + const { cma } = createMockCma({ entriesByContentType: { article: [orphan] } }); + + await findOrphans(cma, [mockArticleContentType], 'en-US', onProgress, options); + + // One content type plus the asset step = 2 total steps. + expect(onProgress).toHaveBeenNthCalledWith(1, { + current: 1, + total: 2, + stepNames: ['Article'], + }); + expect(onProgress).toHaveBeenNthCalledWith(2, { + current: 2, + total: 2, + stepNames: ['Media assets'], + }); + }); +}); + +describe('findOrphans (unreferenced criterion)', () => { + const unreferencedOptions = { ...options, criterion: 'unreferenced' as const }; + + it('keeps only drafts that no entry links to, with their titles', async () => { + const linked = makeMockEntry('linked', 'article', { title: { 'en-US': 'Linked' } }); + const unlinked = makeMockEntry('unlinked', 'article', { title: { 'en-US': 'Unlinked' } }); + const { cma } = createMockCma({ + entriesByContentType: { article: [linked, unlinked] }, + assets: [makeMockAsset('used-asset', 'Hero'), makeMockAsset('unused-asset', 'Spare')], + referenceCounts: { linked: 3, 'used-asset': 1 }, + }); + + const outcome = await findOrphans( + cma, + [mockArticleContentType], + 'en-US', + noProgress, + unreferencedOptions + ); + + expect(outcome.results.map((r) => r.id).sort()).toEqual(['unlinked', 'unused-asset']); + // Unreferenced results keep their real titles for display. + expect(outcome.results.find((r) => r.id === 'unlinked')?.title).toBe('Unlinked'); + }); + + it('includes content types without a text display field', async () => { + // Any entry can be a link target, so the untitled-scan restriction to + // text display fields does not apply here. + const { cma } = createMockCma({ + entriesByContentType: { counter: [makeMockEntry('c1', 'counter')] }, + }); + + const outcome = await findOrphans(cma, [mockNumericDisplayContentType], 'en-US', noProgress, { + ...unreferencedOptions, + includeAssets: false, + }); + + expect(outcome.results.map((r) => r.id)).toEqual(['c1']); + }); + + it('includes edited drafts, carrying the neverEdited flag', async () => { + // An unreferenced entry is worth flagging no matter how often it was + // edited; the flag still rides along for the UI filter. + const edited = makeMockEntry( + 'edited', + 'article', + { title: { 'en-US': 'Edited' } }, + '2026-01-01T00:00:00Z', + 7 + ); + const { cma } = createMockCma({ entriesByContentType: { article: [edited] } }); + + const outcome = await findOrphans(cma, [mockArticleContentType], 'en-US', noProgress, { + ...unreferencedOptions, + includeAssets: false, + }); + + expect(outcome.results.map((r) => r.id)).toEqual(['edited']); + expect(outcome.results[0].neverEdited).toBe(false); + }); + + it('reports reference-count progress after the candidate phases', async () => { + const onProgress = vi.fn(); + const { cma } = createMockCma({ + entriesByContentType: { article: [makeMockEntry('e1', 'article')] }, + }); + + await findOrphans(cma, [mockArticleContentType], 'en-US', onProgress, { + ...unreferencedOptions, + includeAssets: false, + }); + + expect(onProgress).toHaveBeenLastCalledWith({ + current: 1, + total: 1, + stepNames: ['references'], + }); + }); +}); diff --git a/apps/find-orphans/test/parameters.test.ts b/apps/find-orphans/test/parameters.test.ts new file mode 100644 index 0000000000..1937cf62fd --- /dev/null +++ b/apps/find-orphans/test/parameters.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; +import { DEFAULT_PARAMETERS, resolveParameters } from '../src/parameters'; + +describe('resolveParameters', () => { + it('returns defaults for missing or empty parameters', () => { + expect(resolveParameters(undefined)).toEqual(DEFAULT_PARAMETERS); + expect(resolveParameters(null)).toEqual(DEFAULT_PARAMETERS); + expect(resolveParameters({})).toEqual(DEFAULT_PARAMETERS); + }); + + it('keeps valid values and coerces numeric strings', () => { + const resolved = resolveParameters({ maxCandidates: 1000, batchSize: '3' }); + expect(resolved).toEqual({ maxCandidates: 1000, batchSize: 3 }); + }); + + it('falls back to defaults for invalid values', () => { + const resolved = resolveParameters({ maxCandidates: -5, batchSize: 'many' }); + expect(resolved).toEqual(DEFAULT_PARAMETERS); + }); + + it('caps batchSize at the CMA rate limit of 7', () => { + expect(resolveParameters({ batchSize: 50 }).batchSize).toBe(7); + }); + + it('ignores parameters from retired definitions', () => { + // Installations saved before a parameter was removed may still store it + // (e.g. untouchedOnly, defaultStaleDays); unknown keys must be dropped. + const resolved = resolveParameters({ untouchedOnly: true, defaultStaleDays: 30 }); + expect(resolved).toEqual(DEFAULT_PARAMETERS); + }); +}); diff --git a/apps/find-orphans/tsconfig.json b/apps/find-orphans/tsconfig.json new file mode 100644 index 0000000000..8fe18b5ba8 --- /dev/null +++ b/apps/find-orphans/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "types": ["@testing-library/jest-dom"] + }, + "include": ["src", "test"] +} diff --git a/apps/find-orphans/vite.config.mts b/apps/find-orphans/vite.config.mts new file mode 100644 index 0000000000..b2c43f22cb --- /dev/null +++ b/apps/find-orphans/vite.config.mts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + base: '', + build: { + outDir: 'build', + }, + server: { + host: 'localhost', + port: 3000, + }, +}); diff --git a/apps/find-orphans/vitest.config.mts b/apps/find-orphans/vitest.config.mts new file mode 100644 index 0000000000..7548a9871a --- /dev/null +++ b/apps/find-orphans/vitest.config.mts @@ -0,0 +1,18 @@ +import { loadEnv } from 'vite'; +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./src/setupTests.ts'], + env: loadEnv('test', process.cwd(), ''), + server: { + deps: { + inline: ['@contentful/f36-icons'], + }, + }, + }, +});