diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..0d222e5 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(grep -i article find apps/server/src -name \"*.controller.ts\")" + ] + } +} diff --git a/.claude/skills/abler-design/SKILL.md b/.claude/skills/abler-design/SKILL.md new file mode 100644 index 0000000..2983a74 --- /dev/null +++ b/.claude/skills/abler-design/SKILL.md @@ -0,0 +1,139 @@ +--- +name: abler-design +description: > + Design- und Entwicklungs-Skill für das abler.tirol Ökosystem. + Verwende diesen Skill immer wenn du für Simon Abler eine neue Seite, + Komponente, Frontend, Landing Page oder UI-Element unter einer der + abler.tirol Subdomains erstellst oder bearbeitest — also für + abler.tirol, api.abler.tirol, barcode.abler.tirol, pdf.abler.tirol, + klara.abler.tirol, sims.abler.tirol oder jede neue *.abler.tirol Domain. + Auch beim Erstellen neuer Subdomains, beim Anpassen von Docker/nginx-Configs, + beim Schreiben von READMEs für abler.tirol Repos, oder wenn das Gespräch + Designentscheidungen für dieses Ökosystem betrifft. Trigger auch bei + Fragen wie "wie soll X bei abler.tirol aussehen" oder "erstelle mir + eine Seite im abler-Stil". +--- + +# abler.tirol — Design & Development Skill + +Dieses Skill definiert das vollständige Design-System und die Entwicklungsregeln +für das `abler.tirol` Ökosystem. Es gilt für alle bestehenden und neuen Produkte. + +--- + +## Schritt 1: Subdomain identifizieren + +Bevor du anfängst, stelle fest für welche Subdomain du arbeitest. +Lies dann die entsprechende Referenzdatei: + +| Subdomain | Referenzdatei | Produkt-Typ | +|---|---|---| +| `abler.tirol` | `references/abler-tirol.md` | Landing / Static | +| `api.abler.tirol` | `references/api.md` | API + Minimal-Frontend | +| `barcode.abler.tirol` | `references/barcode.md` | Pure Frontend | +| `pdf.abler.tirol` | `references/pdf.md` | Pure Frontend | +| `klara.abler.tirol` | `references/klara.md` | Fullstack | +| `sims.abler.tirol` | `references/sims.md` | Fullstack | +| Neue Subdomain | `references/new-product.md` | Checkliste | + +**Neue Subdomain?** → Lies `references/new-product.md` für die Checkliste. + +--- + +## Schritt 2: Universelle Regeln (gelten für ALLE Subdomains) + +### Typografie — unveränderlich + +```html + + + +``` + +| Rolle | Font | Verwendung | +|---|---|---| +| Headlines | `DM Serif Display` | Hero-Titel, H1/H2 | +| Body / UI | `Inter` | Fließtext, Labels | +| Code / Mono | `DM Mono` | Tags, Badges, Code, Terminal | + +```css +--text-display: clamp(3rem, 8vw, 6rem); +--text-h1: clamp(2.2rem, 5vw, 3.75rem); +--text-h2: clamp(1.8rem, 3.5vw, 3rem); +--text-body: 0.95rem; +--text-label: 0.72rem; /* DM Mono + uppercase + letter-spacing: 0.18em */ +``` + +### Geteilte Neutrals + +```css +:root { + --white: #ffffff; + --bg-light: #f7f7f4; + --bg-subtle: #fbfbf9; + --ink: #0f172a; + --ink-2: #475569; + --ink-3: #94a3b8; + --border: #e2e8f0; + --border-sub: rgba(226,232,240,0.7); + --radius-xl: 2rem; + --radius-lg: 1.5rem; + --shadow-card: 0 25px 80px -30px rgba(15,23,42,0.18); + --shadow-sm: 0 1px 3px rgba(15,23,42,0.06); + --sans: 'Inter', sans-serif; + --mono: 'DM Mono', monospace; + --serif: 'DM Serif Display', serif; +} +``` + +### Layout + +- Max-Width: `72rem` · Section-Padding: `5rem` Desktop / `3rem` Mobile +- Seitenabstand: `1.5rem` Mobile · `2.5rem` Tablet+ + +### Komponenten + +Buttons immer Pill-Form (`border-radius: 999px`), nie eckig. +Cards hover: `transform: translateY(-4px)` + Shadow. +Tags: `DM Mono`, `border-radius: 999px`. +Nav: sticky, max. 4–5 Einträge, Logo in `DM Mono uppercase`. + +### Animationen + +```css +@keyframes fadeUp { from { opacity:0; transform:translateY(18px); } to { opacity:1; transform:translateY(0); } } +/* Stagger: 0.05s–0.15s · max duration: 0.7s · kein Parallax, kein Bounce */ +``` + +### Zweisprachigkeit DE/EN + +```html + +

Text

Text

+``` +```css +[data-lang], span[data-lang] { display: none !important; } +body.de [data-lang="de"], body.de span[data-lang="de"] { display: revert !important; } +body.en [data-lang="en"], body.en span[data-lang="en"] { display: revert !important; } +body.de span[data-lang="de"], body.en span[data-lang="en"] { display: inline !important; } +``` + +### Docker / Traefik + +Pure Frontend → `nginx:alpine`. Jede Domain bekommt einen **eigenen Traefik-Router** +(separates TLS-Zertifikat pro Domain, nie mehrere Domains in einem Router). + +--- + +## Schritt 3: Subdomain-Referenz lesen + +Lies jetzt die passende Datei aus `references/` für Farben, Charakter +und subdomain-spezifische Eigenheiten. + +--- + +## Verbotsliste (alle Subdomains) + +❌ Fonts von Google · ❌ Lila/Violette Gradienten · ❌ System-Fonts +❌ Eckige Buttons · ❌ `#000` als Textfarbe · ❌ >2 Akzentfarben gleichzeitig +❌ Hardcodierte Farbwerte · ❌ Überfüllte Nav · ❌ Parallax / Bounce diff --git a/.claude/skills/abler-design/references/abler-tirol.md b/.claude/skills/abler-design/references/abler-tirol.md new file mode 100644 index 0000000..c0e8301 --- /dev/null +++ b/.claude/skills/abler-design/references/abler-tirol.md @@ -0,0 +1,51 @@ +# abler.tirol — Landing Page + +## Charakter +Sachlich, übergeordnet, verbindend. Der Anker des Ökosystems. +Kein starker Farbakzent — die Subdomains bringen Farbe. + +## Akzentfarben +```css +--accent-primary: #0f172a; /* Slate 900 */ +--accent-secondary: #334155; /* Slate 700 */ +``` + +## Produkttyp: Landing / Static +- Reines HTML/CSS/JS — kein Framework, kein Build-Step +- Kein eigenes Backend, kein API-Zugriff +- Docker: `nginx:alpine` mit `index.html` + +## Hero-Stil +Hell (`--bg-light`), Dashboard-Widget rechts, fadeUp-Animationen. +Kein Dark Hero — neutrales Intro das alle Produkte zusammenhält. + +## Typografie-Gewicht +Ausgewogen. `DM Serif Display` italic für einzelne Wort-Akzente im Hero-Titel. +`Inter` für Body. `DM Mono` für Labels und Nav-Logo. + +## Besonderheiten +- Zeigt alle aktiven Produkte als Cards im 2-Spalten-Grid +- 5. oder weitere Cards: `card-wide` (full-width, horizontal Layout Desktop) +- Hero-Widget zeigt Anzahl aktiver Produkte + Status-Zeilen +- Nav-Badges verlinken direkt zu den Subdomains +- Sprach-Toggle DE/EN oben rechts + +## Card-Farben pro Produkt +```css +/* api */ linear-gradient(135deg, #0f172a, #334155) +/* klara */ linear-gradient(135deg, #064e3b, #065f46) +/* barcode */ linear-gradient(135deg, #78350f, #92400e) +/* pdf */ linear-gradient(135deg, #1e1b4b, #312e81) +/* sims */ linear-gradient(135deg, #134e4a, #0f3d3a) +/* daedalus */ linear-gradient(135deg, #1a1a2e, #16213e) +``` + +## Repo +`github.com/simonabler/abler-tirol` + +## Neue Produkte hinzufügen +1. CSS-Variable `---from` / `---to` in `:root` ergänzen +2. `.card-top-` CSS-Klasse mit Gradient anlegen +3. Card-HTML im Products-Grid einfügen +4. Widget-Zähler erhöhen, Widget-Row ergänzen +5. Nav-Badge optional hinzufügen diff --git a/.claude/skills/abler-design/references/api.md b/.claude/skills/abler-design/references/api.md new file mode 100644 index 0000000..fb2b998 --- /dev/null +++ b/.claude/skills/abler-design/references/api.md @@ -0,0 +1,68 @@ +# api.abler.tirol — Zentrale REST-API + +## Charakter +Technisch, präzise, terminal-nah. `DM Mono` dominiert sichtbarer als +bei anderen Produkten. Dark Hero mit Terminal-Widget. Developer-First. + +## Akzentfarben +```css +--accent-primary: #0f172a; /* Slate 900 */ +--accent-secondary: #1e3a5f; /* Deep Blue-Slate */ +--accent-highlight: #5bffc3; /* Terminal Green — Cursor, Live-Indikatoren */ +--accent-code: #3b8fff; /* Code Blue — Syntax-Highlighting */ +--bg-dark: #0b0d11; /* Hero / Terminal backgrounds */ +``` + +## Produkttyp: API + Minimal-Frontend +- NestJS Backend (Port 3000) +- Angular Frontend (Port 80) +- Eigene Datenbank (SQLite / PostgreSQL) +- CORS: `*` — öffentlich nutzbar +- Fonts werden hier **gehostet** und an alle anderen Subdomains ausgeliefert + +## Font-Server Endpunkte +``` +GET /fonts/abler-stack.css → kombiniertes @font-face CSS +GET /fonts/files/:filename → individuelle woff2-Dateien +``` +Font-Dateien liegen in `apps/server/src/assets/fonts/files/` (woff2 aus `@fontsource`). +NestJS GlobalPrefix ist `api` — `/fonts/*` ist davon **ausgenommen** (`exclude: ['fonts/(.*)']`). + +## Hero-Stil +Dark Hero (`--bg-dark`) mit Grid-Overlay, Glow-Effekten und Terminal-Widget. +Terminal zeigt Live-Curl-Beispiele mit animiertem Cursor (`blink 1.1s step-end`). + +## Typografie-Gewicht +Mono-lastig. `DM Mono` für Labels, Badges, Terminal, alle technischen Texte. +`DM Serif Display` wird hier nicht oder sehr sparsam eingesetzt. + +## Swagger / API Docs +Swagger UI unter `/api` — beide Domains als Server eingetragen: +```ts +.addServer('https://api.abler.tirol') +.addServer('https://hub.abler.tirol') // Legacy, bleibt aktiv +``` + +## Traefik — beide Domains, getrennte Router +```yaml +# Frontend: hub + api → selber Service, 2 Router für 2 Zertifikate +hub-frontend: Host(`hub.abler.tirol`) && PathPrefix(`/`) +api-frontend: Host(`api.abler.tirol`) && PathPrefix(`/`) +# Backend: /api/* und /fonts/* → NestJS +hub-backend: Host(`hub.abler.tirol`) && (PathPrefix(`/api`) || PathPrefix(`/fonts`)) +api-backend: Host(`api.abler.tirol`) && (PathPrefix(`/api`) || PathPrefix(`/fonts`)) +``` + +## Repo +`github.com/simonabler/simonapi` (NX Monorepo: `apps/server` + `apps/simonapi`) + +## API-Endpunkte (Übersicht) +`/api/qr` · `/api/barcode` · `/api/barcode/gs1` · `/api/crypto` +`/api/watermark` · `/api/signpacks` · `/api/utils` · `/api/locks` +`/fonts/abler-stack.css` · `/fonts/files/:filename` + +## Besonderheiten beim Entwickeln +- Neue Endpunkte: eigenes NestJS Module in `apps/server/src/app//` +- Font-Assets: woff2 aus `@fontsource` in `apps/server/src/assets/fonts/files/` +- Webpack kopiert `src/assets/` automatisch nach `dist/` +- API-Keys: 3 Tiers — Free (10/min), Pro (`sk_pro_`), Industrial (`sk_ind_`) diff --git a/.claude/skills/abler-design/references/barcode.md b/.claude/skills/abler-design/references/barcode.md new file mode 100644 index 0000000..17fbb6f --- /dev/null +++ b/.claude/skills/abler-design/references/barcode.md @@ -0,0 +1,58 @@ +# barcode.abler.tirol — Barcode & QR Generator + +## Charakter +Industriell, präzise, physisch-nah. Erinnert an Etiketten, Scanner, +Lagerlogistik. Helles Layout mit Amber-Akzenten. +Primäre Zielgruppe: Logistik, Handel, Produktion — keine Developer. + +## Akzentfarben +```css +--accent-primary: #78350f; /* Amber 900 */ +--accent-secondary: #92400e; /* Amber 800 */ +--accent-highlight: #f59e0b; /* Amber 500 — aktive Zustände, Scan-Indikator */ +--accent-light: #fef3c7; /* Amber 100 — subtile Hintergründe */ +``` + +## Produkttyp: Pure Frontend +- Reines HTML/CSS/JS oder Angular SPA +- **Kein eigenes Backend** — alle Daten via `fetch()` von `api.abler.tirol` +- Docker: `nginx:alpine` +- Fonts: `api.abler.tirol/fonts/abler-stack.css` + +## API-Aufrufe (Beispiele) +```js +// QR Code generieren +const res = await fetch('https://api.abler.tirol/api/qr', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: 'url', payload: { url: input }, format: 'svg' }) +}); + +// Standard Barcode +const url = `https://api.abler.tirol/api/barcode/svg?type=code128&text=${encodeURIComponent(input)}&includetext=true`; + +// GS1 (benötigt API-Key für Pro-Endpunkte) +const res = await fetch('https://api.abler.tirol/api/barcode/gs1/render', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': key }, + body: JSON.stringify({ symbology: 'gs1-128', format: 'png', items: [...] }) +}); +``` + +## Hero-Stil +Hell (`--bg-light`), Amber-Akzente. Generator-UI im Vordergrund — +kein langer Marketing-Text, direkt zum Tool. + +## Typografie-Gewicht +`DM Serif Display` + `Inter`. Kein Terminal-Look. +Eyebrows und Tags in `DM Mono`. + +## UI-Besonderheiten +- Generator steht sofort im Viewport — kein Scroll nötig +- Live-Preview des Barcodes während der Eingabe (debounced, ~300ms) +- Download-Button für PNG/SVG direkt neben der Vorschau +- Amber-Akzent als Scan-Linie / aktiver Rahmen bei der Preview +- Typ-Auswahl: QR / Code128 / EAN / GS1 als Pill-Tabs + +## Repo +`github.com/simonabler/barcode-abler-tirol` diff --git a/.claude/skills/abler-design/references/klara.md b/.claude/skills/abler-design/references/klara.md new file mode 100644 index 0000000..308ca8c --- /dev/null +++ b/.claude/skills/abler-design/references/klara.md @@ -0,0 +1,48 @@ +# klara.abler.tirol — Dokumentationstool für Lehrkräfte + +## Charakter +Ruhig, warm, menschlich. Betont Vertrauen und Datenschutz. +Helles Layout, viel Weißraum, Serif-Titel (kursiv). +Wenig technischer Jargon — spricht Lehrkräfte an, nicht Developer. + +## Akzentfarben +```css +--accent-primary: #064e3b; /* Emerald 900 */ +--accent-secondary: #065f46; /* Emerald 800 */ +--accent-highlight: #10b981; /* Emerald 500 — aktive Elemente, Icons */ +--accent-light: #ecfdf5; /* Emerald 50 — subtile Hintergründe */ +``` + +## Produkttyp: Fullstack (eigenständig) +- **Eigenes NestJS Backend** + PostgreSQL/SQLite +- **Eigene Authentifizierung** (Google OAuth + JWT) +- Kein Zugriff auf `api.abler.tirol` — vollständig unabhängig +- Self-Hosted: Nutzer betreiben eigene Instanz +- Fonts: `api.abler.tirol/fonts/abler-stack.css` + +## Hero-Stil +Sehr hell, viel Weißraum. `DM Serif Display` italic dominant im Titel +("Dokumentation, die *endlich* mitdenkt."). +Kein Dark Hero, kein Terminal-Widget. + +## Typografie-Gewicht +Serif-dominant. `DM Serif Display` italic für Haupttitel. +`Inter` light (300) für Fließtext — ruhig, lesbar. +`DM Mono` nur für technische Labels (DSGVO-Badges, Status). + +## DSGVO / Datenschutz +Klara ist DSGVO-konform by design — das ist ein Kernelement des Brandings: +- "Self-hosted" Badge prominent +- "Keine Daten bei Drittanbietern" +- "Open Source — jede Zeile Code öffentlich" +Datenschutz-Section ist Pflichtbestandteil jeder Seite. + +## UI-Besonderheiten +- App-Interface: Angular, Bootstrap 5 +- Schülerprofile, Notizen, Leistungen als separate Bereiche +- Keine komplexen Mega-Menüs — Reduktion ist Kernwert +- Testimonials von Lehrkräften auf der Landing Page +- CTA: "Klara jetzt ausprobieren →" + "Selbst hosten" + +## Repo +`github.com/simonabler/Klara` diff --git a/.claude/skills/abler-design/references/new-product.md b/.claude/skills/abler-design/references/new-product.md new file mode 100644 index 0000000..4afca79 --- /dev/null +++ b/.claude/skills/abler-design/references/new-product.md @@ -0,0 +1,112 @@ +# Neue Subdomain / Neues Produkt — Checkliste + +Wenn ein neues Produkt unter `*.abler.tirol` erstellt wird, +folge dieser Checkliste. Danach ergänze eine neue Referenzdatei +in `references/.md` nach dem Muster der bestehenden. + +--- + +## 1. Produkt-Typ festlegen + +| Typ | Wann | Beispiel | +|---|---|---| +| **Pure Frontend** | Kein eigenes Backend nötig, alle Daten von api.abler.tirol | barcode, pdf | +| **Fullstack** | Eigene DB, eigene Auth, unabhängig | klara, sims | +| **Landing** | Nur statische Seite | abler.tirol | +| **API + Minimal-Frontend** | Backend ist das Produkt | api | + +## 2. Akzentfarbe wählen + +Bestehende Farben (nicht nochmal verwenden): +- Slate / Blue-Slate → api +- Emerald → klara +- Amber → barcode +- Indigo → pdf +- Teal → sims +- Nacht-Dunkel → daedalus + +Empfohlene freie Paletten für neue Produkte: +- Rose / Pink → warme Consumer-Tools +- Sky / Cyan → Kommunikation, Echtzeit +- Orange → Warnung, Monitoring +- Violet (helles) → Kreativ-Tools +- Stone / Warm-Gray → Neutrales, Dokumentation + +Immer: dunkle `from`-Farbe (900) + etwas hellere `to`-Farbe (800) für Gradient. + +## 3. CSS-Variablen anlegen + +```css +---from: <900-shade>; +---to: <800-shade>; +---highlight: <500-shade>; +---light: <50-shade>; +``` + +## 4. Dateien erstellen + +### Pure Frontend (Static) +``` +-abler-tirol/ +├── index.html +├── nginx.conf +├── Dockerfile +├── docker-compose.yml +└── README.md +``` + +### Fullstack +``` +-abler-tirol/ (oder eigener Repo-Name) +├── apps/ +│ ├── server/ (NestJS) +│ └── frontend/ (Angular o.ä.) +├── docker-compose.yml +├── dockerfiles/ +└── README.md +``` + +## 5. Font-Einbindung + +```html + + +``` + +## 6. Traefik-Labels (docker-compose.yml) + +Eigener Router pro Domain — nie mehrere Domains in einem Router: +```yaml +- "traefik.http.routers.-frontend.rule=Host(`.abler.tirol`) && PathPrefix(`/`)" +- "traefik.http.routers.-frontend.tls=true" +- "traefik.http.routers.-frontend.tls.certresolver=letsEncrypt" +- "traefik.http.routers.-frontend.entrypoints=websecure" +- "traefik.http.routers.-frontend.service=-svc" +- "traefik.http.services.-svc.loadbalancer.server.port=80" +``` + +## 7. abler.tirol Landing Page aktualisieren + +In `github.com/simonabler/abler-tirol/index.html`: +1. CSS-Variable `---from` / `---to` in `:root` +2. `.card-top-` Gradient-Klasse +3. Neue Card im `products-grid` (bei 5. und folgenden: `card-wide`) +4. Widget-Zähler erhöhen +5. Widget-Row ergänzen + +## 8. DESIGN_PRINCIPLES.md aktualisieren + +- Neue Zeile in der Produktcharakter-Tabelle (Abschnitt 9) +- Neue Akzentfarbe in Abschnitt 4 +- Neuer Repo-Eintrag in Abschnitt 11 + +## 9. Diese Skill-Datei ergänzen + +Neue Datei `references/.md` nach dem Muster der bestehenden anlegen: +- Charakter & Zielgruppe +- Akzentfarben (CSS) +- Produkttyp & Tech-Stack +- Hero-Stil +- Typografie-Gewicht +- UI-Besonderheiten +- Repo-Link diff --git a/.claude/skills/abler-design/references/pdf.md b/.claude/skills/abler-design/references/pdf.md new file mode 100644 index 0000000..8e08780 --- /dev/null +++ b/.claude/skills/abler-design/references/pdf.md @@ -0,0 +1,55 @@ +# pdf.abler.tirol — PDF Werkzeugkasten + +## Charakter +Professionell, büro-nah, vertrauenswürdig. Für Notare, Verwaltung, +Büros, Lehrkräfte. Ruhiger als api, formaler als klara. +Primäre Zielgruppe: Büro, Verwaltung, alle die PDFs verwalten müssen. + +## Akzentfarben +```css +--accent-primary: #1e1b4b; /* Indigo 950 */ +--accent-secondary: #312e81; /* Indigo 900 */ +--accent-highlight: #6366f1; /* Indigo 500 — aktive Elemente */ +--accent-light: #eef2ff; /* Indigo 50 — subtile Hintergründe */ +``` + +## Produkttyp: Pure Frontend +- Reines HTML/CSS/JS oder Angular SPA +- **Kein eigenes Backend** — alle Operationen via `api.abler.tirol` +- Datei-Uploads gehen direkt an die API (multipart/form-data) +- Docker: `nginx:alpine` +- Fonts: `api.abler.tirol/fonts/abler-stack.css` + +## API-Aufrufe (Beispiele) +```js +// PDF signieren (Signpack-Workflow) +// 1. Upload +const form = new FormData(); +form.append('file', file); +form.append('expiresInMinutes', '60'); +const { id, token } = await fetch('https://api.abler.tirol/api/signpacks', { + method: 'POST', body: form +}).then(r => r.json()); + +// 2. Signing-Link teilen: api.abler.tirol/api/signpacks/:id/meta?token=:token +// 3. Bundle herunterladen +const bundle = await fetch(`https://api.abler.tirol/api/signpacks/${id}/bundle.zip?token=${token}`); +``` + +## Hero-Stil +Hell (`--bg-light`), Indigo-Akzente. Tool-First — Upload-Bereich +prominent im Hero. Kein langer Intro-Text. + +## Typografie-Gewicht +`Inter`-dominant, sans-lastig. Formaler, weniger verspielt. +`DM Serif Display` nur sparsam für H1. + +## UI-Besonderheiten +- Drag & Drop Upload-Zone prominent (Indigo-Border on hover) +- Drei klar getrennte Tools: Sign / Merge / Split — Pill-Tab Navigation +- Kein Account nötig, keine Daten gespeichert (Signpack: temp. Token) +- Datenschutz-Hinweis prominent: "Kein Upload zu Drittanbietern" +- Indigo als aktiver Zustand bei Steps / Progress-Indikatoren + +## Repo +`github.com/simonabler/pdf-abler-tirol` diff --git a/.claude/skills/abler-design/references/sims.md b/.claude/skills/abler-design/references/sims.md new file mode 100644 index 0000000..8df178d --- /dev/null +++ b/.claude/skills/abler-design/references/sims.md @@ -0,0 +1,113 @@ +# sims.abler.tirol — Lagerverwaltungssystem + +## Charakter +Direkt, funktional, industriell-nah. Für Lager, Werkstätten, kleine Betriebe. +Das UI darf nicht vom UX ablenken — Daten und Aktionen stehen im Vordergrund, +nicht das Interface selbst. Teal als ruhiger, industrieller Gegenpol zu Emerald (klara). + +## Akzentfarben +```css +--accent-primary: #134e4a; /* Teal 900 */ +--accent-secondary: #0f3d3a; /* Teal 950 */ +--accent-highlight: #14b8a6; /* Teal 500 — aktive Elemente */ +--accent-light: #f0fdfa; /* Teal 50 — Hover-Hintergrund, subtile Fills */ +``` + +## Status-Farben (sims-spezifisch) +```css +--status-ok: #14b8a6; /* Teal — In Bestand */ +--status-low: #f59e0b; /* Amber — Niedrig */ +--status-empty: #ef4444; /* Rot — Leer */ +--status-ok-bg: #f0fdfa; +--status-low-bg: #fffbeb; +--status-empty-bg: #fef2f2; +``` + +## Radius-Regeln (sims) +sims verwendet **reduzierte Radien** — funktional, nicht dekorativ: +```css +--radius-pill: 999px; /* Buttons, Badges — abler-Ökosystem-Pflicht */ +--radius-md: 6px; /* Cards, Inputs, Tabellen, Panels */ +--radius-sm: 4px; /* Tags, kleine Elemente */ +--radius-xs: 3px; /* Code, inline */ +``` +Kein `--radius-xl` (2rem) oder `--radius-lg` (1.5rem) auf funktionalen Elementen. + +## Produkttyp: Fullstack (eigenständig) +- **Eigenes Backend** (NestJS) +- **Eigene Datenbank** — Artikel, Bestände, Bewegungen persistent +- Kein Zugriff auf `api.abler.tirol` +- Self-Hosted +- Fonts: `api.abler.tirol/fonts/abler-stack.css` +- Design Guid: design-guide.html +## Hero-Stil +Hell (`--bg-light`), Teal-Akzente. Funktional-first. +Kein ausladender Marketing-Hero — direkt zur App oder zum Einstieg. + +## Typografie-Gewicht +`Inter`-dominant. `DM Serif Display` nur sparsam (Seitenüberschriften). +`DM Mono` für alle technischen Werte: Artikelnummern, Mengenangaben, Beträge, Status-Labels. + +## UI-Besonderheiten + +### Navigation (Sidebar im App-Kontext) +- Hintergrund: `--accent-secondary` (#0f3d3a) +- Aktiver Eintrag: `background: rgba(20,184,166,0.15)`, `color: --accent-highlight` +- Sektions-Labels: `DM Mono`, `uppercase`, `rgba(255,255,255,0.2)` +- Sidebar-Items: `border-radius: var(--radius-sm)` (4px) + +### Tabellen +- Responsive: `overflow-x: auto` — Spalten werden **nie** versteckt +- Header: `DM Mono`, `uppercase`, `letter-spacing: 0.1em`, `--ink-3` +- Zeilenhover: `background: var(--accent-light)` +- Artikelnummern, Mengen, Beträge: immer `DM Mono` +- Wrapper: `border-radius: var(--radius-md)` (6px) + +### Cards +- `border-radius: var(--radius-md)` (6px) — kein 2rem +- Hover: nur `box-shadow: var(--shadow-card)` — **kein** `translateY` +- Kontext-Signal via `border-left: 3px solid` (Teal / Amber / Rot) +- Kein dekorativer Gradient-Top + +### Buttons +- Pill-Form (`border-radius: 999px`) — Ökosystem-Pflicht +- Hover: nur Farbwechsel — **kein** `translateY(-1px)` +- Kompaktes Padding: `0.55rem 1.25rem` +- Focus: `box-shadow: var(--shadow-focus)`, `outline: none` + +### Status-Badges +``` +Teal → "In Bestand" → badge-ok +Amber → "Niedrig" → badge-low +Rot → "Leer" → badge-empty +``` +- Font: `DM Mono`, `font-size: 0.7rem` +- Dot-Indikator (`::before`) für schnelle Lesbarkeit in Tabellen + +### Formulare +- `border-radius: 6px` auf Inputs/Selects +- Labels: `DM Mono`, `uppercase`, `letter-spacing: 0.1em`, `--ink-3` +- Artikelnummer- und Mengen-Inputs: `font-family: var(--mono)` +- Schnellsuche prominent platzieren (Artikel-Nr. + Bezeichnung) + +### Animationen +- Hover: **kein** `translateY` — nur Shadow/Farbwechsel +- Page Load: `fadeUp` mit `0.4s ease`, Stagger `0.07s` +- Focus: Teal-Ring via `box-shadow: var(--shadow-focus)` + +## Dokument-Layout (Lieferschein / Rechnung) +- Header: `--accent-secondary` Hintergrund, Dokumenttyp in `DM Serif Display` +- Dokumentnummer: `DM Mono`, `--accent-highlight` +- Positionstabelle: Standard-Tabellen-Regeln (siehe oben) +- Footer: Gesamtbetrag in `DM Mono`, bündig rechts + +## DSGVO +Self-Hosted = keine Fremddaten. Datenschutz-Hinweis knapp — +die Zielgruppe (Lager, Werkstatt) erwartet das als Standard. + +## Zweisprachigkeit +- App-Kern (Dashboard, Tabellen, Formulare): **nur DE** +- Öffentliche Landing-Page: DE/EN mit Toggle + +## Repo +`github.com/simonabler/sim-system` \ No newline at end of file diff --git a/.claude/skills/test-agent/SKILL.md b/.claude/skills/test-agent/SKILL.md new file mode 100644 index 0000000..9833f87 --- /dev/null +++ b/.claude/skills/test-agent/SKILL.md @@ -0,0 +1,188 @@ +--- +name: test-agent +description: Erstellt Requirements (mit Evidenz), Testmatrizen und Tests. Keine Halluzination. Keine Tests "geradebiegen". +--- + +MODE CONTROL +- Wenn die User-Nachricht mit "MODE=SPEC" beginnt: liefere nur A–D (Inventory/REQs/Unklarheiten/Risiken). +- Wenn "MODE=MATRIX": liefere nur E–F + H–J (Dimensionen/Matrix/Coverage/Gaps/Decision). +- Wenn "MODE=TESTS": liefere nur G + K (Testfälle/Testcode + File Map). +- Sonst: Full pipeline A–K. + + + + +MASTER SYSTEM PROMPT – TEST AGENT (Spec → Testmatrix → Tests) | STRICT NON-HALLUCINATION & NO-BENDING + +Du bist ein extrem konservativer Test-Agent. Du erzeugst (1) Requirements/Specs mit Evidenz, (2) eine Testmatrix mit Traceability, (3) konkrete Testfälle (und optional Testcode), ausschließlich basierend auf den bereitgestellten Inputs. Du bist „defect-friendly“: Wenn etwas nicht passt, kann der Code falsch sein. Du biegst keine Tests gerade. + +==================================================================== +0) NICHT-HALLUZINIEREN (HARTES GESETZ) +==================================================================== +- Erfinde niemals: Anforderungen, Endpunkte, Felder, Business-Regeln, Error-Codes, Rollen, Datenformate, Validierungen, Randfälle, Mocks/Fixtures oder Tooling, die nicht aus Inputs belegbar sind. +- Jede Aussage muss eine Quelle/Evidenz haben oder als UNBELEGT / UNKLAR / INFERIERT markiert werden. +- INFERIERT ist nur erlaubt, wenn es unmittelbar aus Code-Struktur ableitbar ist (z.B. Funktionssignatur, Schema, klarer Kontrollfluss). INFERIERT darf niemals als Fakt formuliert werden. +- Wenn Informationen fehlen: markiere BLOCKED statt Annahmen zu treffen. +- Wenn Test fehlschlagen, weil der code einen Fehler hat, lasse den Test failen, der code wird im nachgang bereinigt. + +==================================================================== +1) KEIN „GERADEBIEGEN“ (TEST-INTEGRITÄT) +==================================================================== +- Passe erwartete Ergebnisse NICHT an, nur damit die aktuelle Implementierung grün wird. +- Wenn Tests scheitern, sind mögliche Ursachen: + a) Defekt im Code + b) Defekt/Unklarheit in der Spezifikation + c) Setup/Umgebung/Testdaten-Problem + -> aber niemals „wir machen die Erwartung weicher“. +- Niemals „expected to fail“ als Ausrede markieren. Tests bleiben neutral. Abweichungen werden als Potential Defect dokumentiert. + +==================================================================== +2) SOURCE OF TRUTH & KONFLIKTE +==================================================================== +Quellen-Priorität (von hoch nach niedrig): +1) Explizite Anforderungen / Acceptance Criteria / Tickets / Spezifikation +2) API-Verträge (OpenAPI/Swagger/JSON Schema), Schnittstellenverträge +3) Dokumentation (README, ADRs), normative Kommentare („must/shall“) +4) Code-Verhalten (nur wenn 1–3 fehlen; dann als Derived-from-code kennzeichnen) + +Konflikte: +- Wenn Quellen widersprechen: dokumentiere den Konflikt mit Referenzen. +- Entscheide NICHT „was richtig ist“, außer eine höher priorisierte Quelle ist eindeutig. +- Erzeuge eine Decision Needed Liste. + +==================================================================== +3) INPUTS & ARBEITSMODUS +==================================================================== +Du erhältst Inputs wie: Code, Tickets, README, API-Spec, Logs, Beispiele, Testframework-Vorgaben. +- Wenn Testframework/Tech-Stack NICHT eindeutig gegeben ist: generiere framework-agnostische Tests (Given/When/Then + Pseudocode) und markiere Tooling als UNBELEGT. +- Wenn ein Framework explizit gegeben ist (z.B. pytest/jest/junit): generiere lauffähige Testdateien in diesem Framework (sofern alle nötigen Details vorhanden sind). Sonst: BLOCKED-Teile mit TODO. + +Determinismus: +- Keine flakey Tests. +- Keine echten externen Calls ohne Mock/Stub, außer Inputs verlangen explizit Integration gegen echte Systeme. +- Keine willkürlichen Sleeps/Timeouts ohne Evidenz. + +==================================================================== +4) OUTPUT-ANFORDERUNGEN (TRACEABILITY, STRIKTES FORMAT) +==================================================================== +Du lieferst IMMER in exakt dieser Reihenfolge und mit diesen Überschriften: + +A) INPUT INVENTORY +B) REQUIREMENTS KATALOG (mit Evidenz) +C) UNKLARHEITEN & OFFENE FRAGEN +D) RISIKEN & TESTAUSWIRKUNGEN +E) TESTDIMENSIONEN +F) TESTMATRIX (Traceability) +G) TESTFÄLLE (Spezifikation oder Code) +H) COVERAGE SUMMARY +I) GAPS & BLOCKERS +J) DECISION NEEDED +K) OPTIONAL: TEST FILE MAP (nur wenn du Dateien generierst) + +Wichtig: +- Jede Requirement bekommt eine REQ-ID: REQ-001, REQ-002, … +- Jede Testmatrix-Zeile referenziert genau eine REQ-ID (oder „NON-REQ“ für seltene technische Checks). +- Jeder Testfall bekommt eine TC-ID: TC-001, TC-002, … +- Jeder Testfall referenziert mindestens eine REQ-ID (oder NON-REQ). + +==================================================================== +5) DETAILREGELN PRO SEKTION +==================================================================== + +A) INPUT INVENTORY +- Liste alle Inputs auf (Dateiname/Quelle/Abschnitt). +- Notiere fehlende Artefakte (z.B. keine Spec) sachlich. + +B) REQUIREMENTS KATALOG (mit Evidenz) +Für jedes Requirement: +- ID: REQ-xxx +- Titel: kurz +- Beschreibung: präzise, testbar +- Quelle: (z.B. Ticket #, OpenAPI, Datei+Zeile) +- Evidenz: kurzes Zitat/Paraphrase + Referenz (Datei/Abschnitt/Zeile wenn vorhanden) +- Priorität: High/Med/Low nur wenn belegt, sonst UNBELEGT +- Status: CONFIRMED / INFERIERT / UNKLAR +Regeln: +- Keine vagen Worte ohne messbares Kriterium. +- Keine Best-Practice-Sätze als Requirement erfinden. + +C) UNKLARHEITEN & OFFENE FRAGEN +- UQ-001, UQ-002 … +- Frage + warum unklar (fehlende Evidenz) + was klärt es. + +D) RISIKEN & TESTAUSWIRKUNGEN +- Nur Risiken nennen, die aus Inputs ableitbar sind (z.B. fehlende Validierung, fehlende Error-Codes). +- Keine frei erfundenen Security/Performance-Annahmen. + +E) TESTDIMENSIONEN +- Definiere Dimensionen NUR wenn belegbar (Rollen, Plattformen, Datenvarianten, Locale, Error-Handling). +- Wenn nicht belegbar: „Keine belegbaren Dimensionen außer Standardpfad“. + +F) TESTMATRIX (Traceability) +Erzeuge eine Markdown-Tabelle mit Spalten: +- REQ-ID +- Feature/Komponente (aus Evidenz, sonst UNBELEGT) +- Szenario (konkret, testbar) +- Testtyp (Unit/Integration/E2E/API/UI/Security/Performance) – nur wenn sinnvoll ableitbar, sonst Functional +- Priorität/Risiko (nur wenn belegt, sonst UNBELEGT) +- Positiv/Negativ +- Datenvarianten (nur belegbar; sonst Standarddaten) +- Erwartetes Ergebnis (präzise; wenn unklar: BLOCKED (UNKLAR)) +- Automatisierbarkeit (Yes/No/Maybe) – konservativ +- Notes/Evidenz-Referenz + +G) TESTFÄLLE (Spezifikation oder Code) +Entscheidung: +- Wenn Framework gegeben UND genügend Setup-Infos vorhanden: generiere Testcode. +- Sonst: Given/When/Then + Pseudocode. + +Pro Testfall: +- TC-ID +- REQ-ID(s) +- Titel +- Typ +- Voraussetzung/Setup +- Schritte +- Testdaten +- Erwartetes Ergebnis (messbar) +- Orakel/Evidenz (woher stammt die Erwartung) +- Automatisierung: Yes/No/Blocked +- Status: READY / BLOCKED (mit Grund) +Zusatz: +- Wenn du eine wahrscheinliche Abweichung erkennst: Abschnitt „Potential Defect“ mit Evidenz (ohne Erwartung zu ändern). + +H) COVERAGE SUMMARY +- Anzahl Requirements +- Anzahl abgedeckter Requirements +- Liste fehlender Abdeckung (REQ-IDs) + Begründung + +I) GAPS & BLOCKERS +- GAP-001 …: Beschreibung, betroffene REQ-IDs, welche Info fehlt +- BLOCKED: welche Tests/Matrix-Zeilen, warum blockiert, was benötigt wird + +J) DECISION NEEDED +- Konkrete Entscheidungen, die Product/Dev klären muss, inkl. betroffene REQ-IDs und widersprüchliche Evidenz. + +K) OPTIONAL: TEST FILE MAP +- Nur wenn Testcode erzeugt wird: + - Dateipfad → enthaltene TC-IDs/REQ-IDs + +==================================================================== +6) VERBOTE +==================================================================== +- Keine stillen Annahmen über Auth, Rollen, Statuscodes, Fehlermeldungen. +- Keine erfundenen Validierungen (z.B. E-Mail-Format) ohne Evidenz. +- Kein Greenwashing +- Keine Änderungsvorschläge am Produktivcode als Teil der Tests. +- Keine versteckten Annahmen über Datenbank-/Netzwerkzustand. +- Keine unbelegten Performance-Schwellen. + +==================================================================== +7) INPUT-INTERFACE (WIE DU DIE AUFGABE INTERPRETIERST) +==================================================================== +Du interpretierst den nächsten User-Input als: +- Artefakte/Anforderungen/Code, die du analysieren sollst +- Optional: gewünschtes Testframework (z.B. "pytest"), Zielplattform, Ordnerstruktur +Wenn diese Infos fehlen, bleibst du framework-agnostisch und markierst fehlende Stellen als BLOCKED. + +BEGINNE NUN mit Abschnitt A) INPUT INVENTORY basierend auf den erhaltenen Inputs. \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9a2b043 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,68 @@ +##### General build context hygiene for Nx/Node/Angular/NestJS repo + +# VCS and metadata +.git +.gitignore +.gitattributes +.github + +# Node/Nx/Angular outputs and caches +node_modules +dist +tmp +out-tsc +coverage +.nx/cache +.nx/workspace-data +.angular +.eslintcache +.cache +.turbo + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +testem.log +libpeerconnection.log + +# IDE/editor junk +.vscode +.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +.DS_Store +Thumbs.db + +# Types, typings and sass caches +/typings +/.sass-cache + +# Environment files (keep examples) +.env +.env.* +!.env.example +!.env.*.example + +# Playwright/Jest/E2E artifacts +playwright-report +test-results +.nyc_output +apps/*-e2e/playwright-report +apps/*-e2e/test-results + +# Local databases and dumps +*.sqlite +*.db +signpacks.sqlite + +# Misc +.cursor +/postgres diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e87a00 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.env b/.env new file mode 100644 index 0000000..195a80d --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +APP_PDF_SLIP_PATH=C:\Users\ematric\Desktop\private\SIMSystem\pdf +APP_PDF_BILL_PATH=C:\Users\ematric\Desktop\private\SIMSystem\pdf + +SQLITE_RUN_SYNCHRONIZE=true +#SQLITE_PATH=prod_neu.sqlite3 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d5c92a --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# compiled output +dist +tmp +out-tsc + +# dependencies +node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db + +.nx/cache +.nx/workspace-data + +.angular +sim*.db +uploads/settings/* +!uploads/settings/.gitkeep diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..113709c --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +# Add files here to ignore them from prettier formatting +/dist +/coverage +/.nx/cache +/.nx/workspace-data +.angular diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..544138b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..db03eda --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "nrwl.angular-console", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "firsttris.vscode-jest-runner", + "ms-playwright.playwright" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8137e5a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug server with Nx", + "runtimeExecutable": "npx", + "runtimeArgs": ["nx", "serve", "server"], + "env": { + "NODE_OPTIONS": "--inspect=9229" + }, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "skipFiles": ["/**"], + "sourceMaps": true, + "outFiles": [ + "${workspaceFolder}/apps/server/dist/**/*.(m|c|)js", + "!**/node_modules/**" + ] + } + ] +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..552a00a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,348 @@ +# SIMS — Übergabe-Kontext für Claude Code + +## Projekt-Überblick + +**Repository:** `github.com/simonabler/sim-system` · Branch: `first-init` +**Typ:** NX Monorepo — Fullstack Inventory Management System +**Teil des:** `abler.tirol` Ökosystems (siehe Design-Prinzipien unten) + +--- + +## Monorepo-Struktur + +``` +sim-system/ +├── apps/ +│ ├── server/ → NestJS 11 Backend (REST API) +│ └── sims/ → Angular 20 Frontend (Standalone Components) +├── libs/ +│ └── domain/ → Shared Domain Library +├── package.json → Root (alle Dependencies) +├── nx.json +└── tsconfig.base.json +``` + +--- + +## Backend (`apps/server`) + +### Tech Stack +- NestJS 11, TypeORM 0.3, SQLite, Webpack (via NX) +- Swagger-Docs: `GET /api/v1/doc` +- Global Prefix: `/api/v1` + +### Starten +```bash +# Dev (watch mode) +npx nx serve server + +# Production build +npx nx build server +node dist/apps/server/main.js +``` + +### Umgebungsvariablen +Datei: `apps/server/.env` (liegt im .gitignore, `.env.example` als Vorlage) + +```env +NODE_ENV=development +APP_PORT=9000 +APP_URL=http://localhost:9000 +APP_JWT_SECRET=change_me_in_production +SQLITE_PATH=./storage/sims.sqlite +SQLITE_RUN_MIGRATION=true +SQLITE_RUN_SYNCHRONIZE=false +``` + +### ⚠️ Wichtig beim ersten Setup +`sqlite3` braucht native Kompilierung: +```bash +npm install # OHNE --ignore-scripts (kompiliert sqlite3 native binary) +``` + +### API-Routen (alle unter `/api/v1/`) +| Route | Beschreibung | +|---|---| +| `GET /articles` | Alle Artikel | +| `GET /articles?code=X` | Artikel nach Barcode | +| `POST /articles` | Artikel anlegen | +| `PATCH /articles/:id` | Artikel updaten | +| `DELETE /articles/:id` | Artikel löschen | +| `POST /articles/:id/inventory` | Inventurbuchung | +| `POST /articles/import` | CSV-Import | +| `GET /articlegroups` | Artikelgruppen | +| `GET /customers` | Kunden | +| `GET /customers/:id` | Kunde by ID | +| `GET /customers/:id/slipsheets` | Lieferscheine des Kunden | +| `GET /customers/:id/bills` | Rechnungen des Kunden | +| `PATCH /customers/:id` | Kunde updaten | +| `POST /customers` | Kunde anlegen | +| `DELETE /customers/:id` | Kunde löschen | +| `PUT /customers/:id/discounts` | Rabatt setzen | +| `DELETE /customers/:id/discounts/:dId` | Rabatt löschen | +| `GET /slipsheets` | Alle Lieferscheine | +| `GET /slipsheets/:id` | Lieferschein by ID | +| `POST /slipsheets` | Lieferschein anlegen | +| `PUT /slipsheets/:id` | Lieferschein updaten | +| `POST /slipsheets/:id` | Order-Entry hinzufügen | +| `GET /slipsheets/:id/pdf` | Lieferschein als PDF | +| `POST /slipsheets/:id/print` | Drucken | +| `GET /bills` | Alle Rechnungen | +| `POST /bills/generate` | Rechnung aus Lieferscheinen erzeugen | +| `PUT /bills/:id` | Rechnung updaten | +| `POST /bills/:id` | Rechnung neu erstellen (recreate) | +| `GET /bills/:id/pdf` | Rechnung als PDF | +| `GET /dashboard/summary` | Dashboard KPIs | +| `PUT /order-entries/:id` | Order-Entry updaten | + +### Bekannte offene Bugs (aus `kontext.md` — noch nicht behoben) +- `GET /slipsheets/:id` gibt durch falsches Array-Indexing `undefined` zurück +- `GET /bills/:id/pdf` hat Side-Effects (erzeugt Rechnung neu bei jedem Aufruf) +- `generateBill()` löscht im Fehlerfall bestehende Rechnungen +- Nummernvergabe für Rechnungen/Lieferscheine ohne DB-Lock (Race Condition) +- `inventoryDate` ist `CreateDateColumn` statt normalem Column + +--- + +## Frontend (`apps/sims`) + +### Tech Stack +- Angular 20, Standalone Components, `provideRouter`, `bootstrapApplication` +- `@ng-select/ng-select` für Dropdowns +- `ngx-toastr` für Notifications +- Design: `abler.tirol` Design-System (Steel-Blue für SIMS) + +### Starten +```bash +npx nx serve sims # :4200, Proxy → :9000 +``` + +### Proxy +`apps/sims/proxy.conf.json` leitet `/api/*` auf `http://localhost:9000` weiter. + +### Routing-Struktur +``` +/ → /dashboard +/dashboard → DashboardComponent +/dashboard/inventory → InventoryListComponent +/article → ArticleComponent +/mobile/tracking → MobileTrackingComponent +/mobile/inventory → MobileInventoryComponent +/admin/customer → CustomerComponent +/admin/customer/new → CustomerEditComponent +/admin/customer/detail/:id → CustomerDetailComponent +/admin/customer/edit/:id → CustomerEditComponent +/admin/bills → BillsComponent +/login → LoginComponent +``` + +### Services (alle `providedIn: 'root'`) +- `AuthenticationService` — Login/Logout (JWT in localStorage) +- `ArticleService` — CRUD Artikel + CSV-Import +- `ArticleGroupService` — Artikelgruppen +- `CustomerService` — CRUD Kunden + Discounts + Slipsheets/Bills +- `ShoppingcartService` — Lieferscheine, Orders, Annotations +- `BillService` — Rechnungen +- `DashboardService` — KPI-Summary +- `UserService` — Benutzer + +### Interceptors (functional, in `app.config.ts`) +- `backendInterceptor` — Prefixes relative URLs mit `environment.apiUrl` +- `errorInterceptor` — 401 → logout + reload, andere Fehler → Toast + +--- + +## Design-System (`abler.tirol`) + +Alle CSS-Variablen sind in `apps/sims/src/styles.scss` definiert. + +### SIMS Akzentfarben (Steel-Blue / Lager) +```css +--accent-primary: #1e3a5f; +--accent-secondary: #2d5282; +--accent-highlight: #3b82f6; +--accent-light: #eff6ff; +``` + +### Geteilte Neutrals +```css +--bg-light: #f7f7f4 +--ink: #0f172a +--ink-2: #475569 +--ink-3: #94a3b8 +--border: #e2e8f0 +``` + +### Fonts (von `api.abler.tirol` geladen) +- Headlines: `DM Serif Display` +- Interface/Body: `Inter` +- Code/Labels/Tags: `DM Mono` + +### Regeln +- Buttons immer `border-radius: 999px` (Pill-Form) +- Cards: `border-radius: 2rem` +- Kein reines Schwarz `#000` — immer `var(--ink)` +- Keine System-Fonts +- Max. 2 Akzentfarben gleichzeitig sichtbar + +--- + +## Was bereits erledigt ist ✅ + +### Migration (Branch `first-init`) +1. **NX Monorepo** — `SIMSystem-backend` + `SIMSystem-frontend` zusammengeführt +2. **Backend auf NestJS 11 / TypeORM 0.3 gebracht:** + - Alle `src/` absolute Imports → relative Pfade + - `@hapi/joi` → `joi` + - `pdfmake` korrekt eingebunden (`Printer.js` default export) + - `model.repository.ts` für TypeORM v0.3 Generics gefixt + - `base.service.ts` Generics gefixt + - sqlite provider mit expliziten Entity-Klassen statt Glob + - mock-Dateien aus App-Build ausgeschlossen + - webpack.config.js: `src`-Alias + konditionelle Migrations-Assets +3. **Frontend von Angular 9 → Angular 20 migriert:** + - Alle NgModules gelöscht → Standalone Components + - `bootstrapApplication` + `app.config.ts` + - `provideRouter` / `provideHttpClient` / `provideAnimations` / `provideToastr` + - Functional Interceptors + Functional Guards + - `@coreui/angular` + `ngx-bootstrap` + `ngx-perfect-scrollbar` + `angular-datatables` + `ng2-charts` entfernt + - Eigenes Layout (Sidebar + Topbar) ohne CoreUI + - Alle Templates: `bsModal` → native HTML-Dialog, `datatable` → `*ngFor` + - Feature-Routes: `article.routes.ts`, `mobile.routes.ts`, `admin.routes.ts`, `dashboard.routes.ts` + - `styles.scss` komplett neu — volles `abler.tirol` Design-System + +--- + +## Was als nächstes zu tun ist 🔄 + +### P0 — Für erste lauffähige Version nötig + +1. **`npm install` ohne `--ignore-scripts`** auf Zielmaschine ausführen (sqlite3 native binary) + +2. **Bug: `GET /slipsheets/:id` gibt `undefined`** zurück + - Datei: `apps/server/src/app/models/bills/controllers/slipsheet.controller.ts` ~Zeile 102 + - Problem: falsches Array-Indexing + - Fix: Return-Wert prüfen, korrektes Element zurückgeben + +3. **Bug: `GET /bills/:id/pdf` hat Side-Effects** + - Datei: `apps/server/src/app/models/bills/controllers/bill.controller.ts` ~Zeile 82 + - Problem: Rechnung wird bei jedem GET neu generiert + - Fix: PDF nur lesen wenn bereits vorhanden, separate POST-Route für Neugenerierung + +4. **Auth komplett aktivieren oder entfernen** + - Aktuell: Guards sind auskommentiert, API ist offen + - Option A: JWT Guard global einbauen (empfohlen für Produktion) + - Option B: Auth-Flows aus Frontend entfernen wenn nicht benötigt + - Relevante Dateien: `apps/server/src/app/models/users/`, `apps/sims/src/app/helpers/auth.guard.ts` + +5. **Login-Page implementieren** + - Aktuell: Stub-Component, leitet nur weiter + - `apps/sims/src/app/views/login/login.component.ts` + +### P1 — Wichtige Verbesserungen + +6. **Race Condition: Nummernvergabe transaktional machen** + - Dateien: `apps/server/src/app/models/bills/bill.service.ts` ~Zeile 114 + - Dateien: `apps/server/src/app/models/bills/slipsheet.service.ts` ~Zeile 43 + - Fix: DB-Transaktion + Lock beim `SELECT MAX(number) + 1` + +7. **`inventoryDate` korrigieren** + - Datei: `apps/server/src/app/models/article/entities/article.entity.ts` ~Zeile 38 + - Problem: `@CreateDateColumn()` statt normales `@Column({ type: 'datetime' })` + - Fix: Column-Typ ändern, Migration erstellen + +8. **Inventurbuchung awaiten** + - Datei: `apps/server/src/app/models/article/article.service.ts` ~Zeile 227 + - Problem: `await` fehlt, Fehler gehen verloren + +9. **CustomerDetail: Rechnung aus ausgewählten Lieferscheinen erstellen** + - `apps/sims/src/app/views/admin/customer-detail/customer-detail.component.ts` + - Die Checkbox-Selektion (`selectedSlipIds`) ist implementiert + - `makeBill(selectedSlipIds)` ruft `billService.generate(ids)` auf + - Fehlt noch: visuelles Feedback nach Erstellung, Liste aktualisieren + +10. **Mobile HTML-Templates prüfen** + - `apps/sims/src/app/views/mobile/mobile-tracking/mobile-tracking.component.html` + - `apps/sims/src/app/views/mobile/inventory/inventory.component.html` + - Wurden nicht aus dem alten Code übernommen — müssen neu erstellt werden + +11. **Article-Edit HTML: ng-select Binding überprüfen** + - `apps/sims/src/app/views/article/article-edit/article-edit.component.html` + - `formControlName="articleGroup"` mit `ng-select` — könnte Typ-Mismatch haben + +### P2 — Sauberkeit & Betrieb + +12. **Frontend/Backend API-Contract Matrix vervollständigen** + - `DELETE /articles/:id` im Backend fehlt (Frontend ruft es auf) + - `POST /bills/:id` → Backend hat das als recreate implementiert + - `state`-Filter von Frontend wird im Backend ignoriert + +13. **CORS härten** + - Aktuell: `cors: true` (alles erlaubt) + - Fix: erlaubte Origins per ENV-Variable + +14. **Docker-Setup verifizieren** + - `docker-compose.dev.yml` + `dockerfiles/` vorhanden + - Noch nicht mit neuer Monorepo-Struktur getestet + +15. **Migrations-Workflow einrichten** + - `apps/server/src/datasource.ts` vorhanden + - `npm run migration:generate --name=InitialSchema` + - `npm run migration:run` + +16. **SIMS Farbpalette finalisieren** + - Laut Design-Dokument ist `sims` noch "muss noch definiert werden" + - Aktuell: Steel-Blue (`#1e3a5f` / `#3b82f6`) — bei Bedarf anpassen + +--- + +## NX-Befehle Referenz + +```bash +# Builds +npx nx build server +npx nx build sims +npx nx build sims --configuration=development + +# Dev-Server +npx nx serve server # Backend :9000 +npx nx serve sims # Frontend :4200 + +# Migrations (TypeORM) +npm run migration:generate --name=MigrationName +npm run migration:run +npm run migration:revert +npm run migration:show + +# Tests +npx nx test server +npx nx test sims +``` + +--- + +## Wichtige Dateipfade + +``` +apps/server/src/main.ts → NestJS Bootstrap +apps/server/src/app/app.module.ts → Root Module +apps/server/src/app/models/ → Entities, Services, Controllers +apps/server/src/app/common/services/pdfmaker.service.ts → PDF-Generierung +apps/server/src/datasource.ts → TypeORM DataSource (für Migrations) +apps/server/.env → Umgebungsvariablen (gitignored) +apps/server/.env.example → Vorlage + +apps/sims/src/main.ts → Angular Bootstrap +apps/sims/src/app/app.config.ts → App-Konfiguration (Provider) +apps/sims/src/app/app.routes.ts → Root-Routing +apps/sims/src/app/containers/default-layout/ → Haupt-Layout (Sidebar+Topbar) +apps/sims/src/app/services/ → Angular Services +apps/sims/src/app/models/ → TypeScript Daten-Modelle +apps/sims/src/styles.scss → Globales Stylesheet (Design-System) +apps/sims/proxy.conf.json → Dev-Proxy → :9000 +apps/sims/src/environments/ → API-URL Konfiguration +``` + +--- + +*Erstellt: 2026-03-15 · Branch: first-init · Letzter Commit: c656aa6* diff --git a/FINDINGS.md b/FINDINGS.md new file mode 100644 index 0000000..ad51da6 --- /dev/null +++ b/FINDINGS.md @@ -0,0 +1,219 @@ +# FINDINGS + +Stand: 2026-03-19 +Basis: Review der aktuell implementierten Workflows anhand von Frontend, Services und Backend +Hinweis: keine Laufzeittests ausgefuehrt, Findings basieren auf Codeanalyse + +## Hoch + +### 1. Rechnungserzeugung selektierte zuvor die falschen Lieferscheine + +**Problem** + +Das Frontend behandelte jeden Lieferschein ohne `billId` als "offen" und damit als Rechnungskandidat. Das Backend akzeptiert fuer `POST /bills/generate` aber nur Lieferscheine mit `state === CLOSED`. + +**Auswirkung im Workflow** + +Ein Benutzer konnte in der Kundendetailansicht Lieferscheine auswaehlen und auf `Rechnung erzeugen` klicken, obwohl diese serverseitig noch `open` oder `changed` waren. Die Rechnungserzeugung scheiterte dann mit `Lieferscheine noch nicht erstellt`. + +**Codebeleg** + +- `apps/sim-system/src/app/models/bill.model.ts:41` +- `apps/sim-system/src/app/models/bill.model.ts:42` +- `apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts:83` +- `apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts:91` +- `apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts:163` +- `apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts:174` +- `apps/server/src/models/bills/bill.service.ts:47` +- `apps/server/src/models/bills/bill.service.ts:48` + +**Status** + +UI angepasst: + +- Filter `Nur unverrechnete` bleibt bestehen +- nur `closed` und noch nicht verrechnete Lieferscheine sind selektierbar +- andere unverrechnete Eintraege bleiben sichtbar und werden in der Tabelle als `Noch nicht erzeugt` markiert + +### 2. Lieferschein-PDF-Aufruf veraenderte den Serverzustand, aber die UI blieb veraltet + +**Problem** + +`GET /slipsheets/:id/pdf` schliesst standardmaessig den Lieferschein bzw. erzeugt Nummer und PDF, weil `close` per Default `true` ist. Die Frontend-Aktionen zum Oeffnen oder Herunterladen des PDFs aktualisierten den lokalen Datensatz danach zunaechst nicht. + +**Auswirkung im Workflow** + +Nach einem PDF-Aufruf sah der Benutzer im UI weiterhin den alten Zustand. Status, Nummer, Selektionslogik und Folgeaktionen konnten auf veralteten Daten basieren. + +**Codebeleg** + +- `apps/server/src/models/bills/controllers/slipsheet.controller.ts:110` +- `apps/server/src/models/bills/controllers/slipsheet.controller.ts:113` +- `apps/server/src/models/bills/controllers/slipsheet.controller.ts:140` +- `apps/server/src/models/bills/slipsheet.service.ts:89` +- `apps/server/src/models/bills/slipsheet.service.ts:95` +- `apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts:229` +- `apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts:233` +- `apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.ts:192` +- `apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.ts:195` + +**Status** + +Frontend angepasst: + +- nach erfolgreichem `GET /slipsheets/:id/pdf` wird der betroffene Lieferschein erneut ueber `GET /slipsheets/:id` geladen +- die lokale Ansicht wird danach aktualisiert +- kein neuer Endpoint noetig + +### 3. `Neue Bestellung` haengt neue Positionen potentiell an den falschen offenen Lieferschein + +**Problem** + +`/order/new` laedt fuer den ausgewaehlten Kunden alle offenen/geaenderten Lieferscheine und verwendet einfach `slips[0]`. Die Backend-Liste ist an dieser Stelle nicht sortiert oder als eindeutiger "aktueller" Lieferschein definiert. + +**Auswirkung im Workflow** + +Neue Positionen koennen an einem beliebigen vorhandenen Lieferschein landen, wenn mehrere offene oder geaenderte Belege existieren. + +**Codebeleg** + +- `apps/sim-system/src/app/views/order-new/order-new.component.ts:61` +- `apps/sim-system/src/app/views/order-new/order-new.component.ts:63` +- `apps/server/src/models/bills/controllers/slipsheet.controller.ts:80` +- `apps/server/src/models/bills/controllers/slipsheet.controller.ts:81` +- `apps/server/src/models/bills/slipsheet.service.ts:160` +- `apps/server/src/models/bills/slipsheet.service.ts:171` + +**Empfehlung** + +Entweder serverseitig genau einen "aktiven" Lieferschein liefern oder clientseitig explizit sortieren bzw. den Benutzer waehlen lassen. + +## Mittel + +### 4. Rechnungs-PDF konnte im Fehlerfall nicht aus der UI neu erzeugt werden + +**Problem** + +Backend und Angular-Service unterstuetzten die Neugenerierung eines Rechnungs-PDFs bereits, aber die Rechnungsdetailseite bot dafuer zunaechst keine Aktion an. Bei fehlendem PDF zeigte die UI nur eine Fehlermeldung. + +**Auswirkung im Workflow** + +Der Benutzer bekam die Meldung `PDF nicht gefunden. Bitte neu erzeugen.`, konnte diese Neugenerierung im UI aber nicht anstossen. + +**Codebeleg** + +- `apps/server/src/models/bills/controllers/bill.controller.ts:128` +- `apps/server/src/models/bills/controllers/bill.controller.ts:133` +- `apps/sim-system/src/app/services/bill.service.ts:33` +- `apps/sim-system/src/app/views/bill-detail/bill-detail.component.ts:46` +- `apps/sim-system/src/app/views/bill-detail/bill-detail.component.ts:56` +- `apps/sim-system/src/app/views/bill-detail/bill-detail.component.html:52` + +**Status** + +UI angepasst: + +- Rechnungsdetailseite besitzt jetzt einen `PDF neu erzeugen`-Button +- der Button nutzt den vorhandenen `POST /bills/:id` +- nach erfolgreicher Neuerzeugung wird der normale PDF-Download direkt erneut gestartet + +### 5. Dashboard-Deep-Link zur Artikelsuche funktionierte nicht + +**Problem** + +Das Dashboard navigierte mit `queryParams: { code }` zur Artikelliste. Die Artikelliste las diese Query-Parameter zunaechst nicht aus und filterte nur ueber lokale FormControls. + +**Auswirkung im Workflow** + +Ein Klick aus dem Dashboard auf einen Artikel oder Barcode landete zwar auf `/articles`, die erwartete Vorfilterung nach Code passierte aber nicht. + +**Codebeleg** + +- `apps/sim-system/src/app/views/dashboard/dashboard.component.ts:83` +- `apps/sim-system/src/app/views/dashboard/dashboard.component.ts:84` +- `apps/sim-system/src/app/views/articles/articles.component.ts:22` +- `apps/sim-system/src/app/views/articles/articles.component.ts:23` +- `apps/sim-system/src/app/views/articles/articles.component.ts:37` + +**Status** + +Frontend angepasst: + +- `ArticlesComponent` liest jetzt `queryParamMap` +- der Query-Parameter `code` wird in `codeCtrl` uebernommen +- der Deep-Link vom Dashboard filtert die Artikelliste damit direkt vor + +### 6. Login-Workflow ist im Frontend vorbereitet, aber als Gesamtablauf nicht lauffaehig + +**Problem** + +Es gibt `LoginComponent`, `AuthenticationService` und `authGuard`, aber: + +- die Route `/login` ist nicht registriert +- das Frontend ruft `users/login` und `users/me/refresh` auf +- das Backend bietet diese Endpunkte nicht an + +**Auswirkung im Workflow** + +Sobald Auth aktiviert oder ein Guard benutzt wird, endet der Ablauf auf einer fehlenden Route oder in 404-API-Fehlern. + +**Codebeleg** + +- `apps/sim-system/src/app/app.routes.ts` +- `apps/sim-system/src/app/helpers/auth.guard.ts:12` +- `apps/sim-system/src/app/services/authentication.service.ts:23` +- `apps/sim-system/src/app/services/authentication.service.ts:40` +- `apps/server/src/models/users/users.controller.ts:44` +- `apps/server/src/models/users/users.controller.ts:59` +- `apps/server/src/models/users/users.controller.ts:71` +- `apps/server/src/models/users/users.controller.ts:89` + +**Empfehlung** + +Entweder Auth-Endpunkte und Login-Route vollstaendig implementieren oder den toten Auth-Pfad bis zur echten Einfuehrung konsequent entfernen. + +### 7. Kundenformular validiert das einzig serverseitig verpflichtende Feld nicht + +**Problem** + +`customerNumber` ist im Backend per DTO Pflichtfeld, im Frontend-Formular aber ohne Validator konfiguriert. + +**Auswirkung im Workflow** + +Benutzer koennen formal "gueltig" absenden, bekommen aber erst vom Backend einen Validierungsfehler zurueck. + +**Codebeleg** + +- `apps/server/src/models/customer/dto/create-customer.dto.ts:25` +- `apps/server/src/models/customer/dto/create-customer.dto.ts:26` +- `apps/sim-system/src/app/views/customer-edit/customer-edit.component.ts:23` +- `apps/sim-system/src/app/views/customer-edit/customer-edit.component.ts:29` +- `apps/sim-system/src/app/views/customer-edit/customer-edit.component.ts:71` + +**Empfehlung** + +`customerNumber` im Angular-Formular mit `Validators.required` versehen und den Fehler direkt im UI anzeigen. + +## Niedrig + +### 8. Ein Lieferschein mit nur Annotationen kann geloescht werden + +**Problem** + +Die Generierung behandelt Annotationen als relevanten Inhalt. Die Loeschlogik dagegen blockiert nur Lieferscheine mit Positionen. Im Frontend ist `canDelete()` ebenfalls nur an `orderEntries.length === 0` gekoppelt. + +**Auswirkung im Workflow** + +Ein Benutzer kann einen Lieferschein loeschen, obwohl bereits Kommissionen/Annotationen darauf erfasst wurden. + +**Codebeleg** + +- `apps/server/src/models/bills/slipsheet.service.ts:46` +- `apps/server/src/models/bills/slipsheet.service.ts:50` +- `apps/server/src/models/bills/slipsheet.service.ts:81` +- `apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.ts:62` +- `apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.ts:64` + +**Empfehlung** + +Loeschlogik an dieselbe inhaltliche Definition koppeln wie die PDF-/Close-Logik, also Positionen und Annotationen gemeinsam betrachten. diff --git a/Next-steps.md b/Next-steps.md new file mode 100644 index 0000000..bbf7b22 --- /dev/null +++ b/Next-steps.md @@ -0,0 +1,1705 @@ +# SIMS — Agent-Anleitung: CompanySettings Feature + +> Dieses Dokument ist eine vollständige Schritt-für-Schritt-Anleitung für einen Coding-Agent. +> Jede Phase ist in sich abgeschlossen und testbar. Gib dem Agent immer **eine Phase auf einmal**. + +--- + +## Kontext (vor jeder Phase mitgeben) + +``` +Repository: github.com/simonabler/sim-system +Branch: first-init +Stack: + Backend: NestJS + TypeORM + SQLite (better-sqlite3), Monorepo unter apps/server/ + Frontend: Angular (standalone components, signals, ReactiveFormsModule), unter apps/sim-system/ + PDF-Generierung: pdfmake im Backend, PdfMakerService in apps/server/src/common/services/pdfmaker.service.ts + +Ziel dieser Arbeit: + Alle hardcoded Firmendaten (Adresse, Kontakt, Bankdaten, Logo-Pfade, MwSt-Satz, Ausstellungsort) + aus dem PdfMakerService in eine konfigurierbare Datenbank-Entity auslagern. + Eine Angular-Seite /settings ermöglicht die Verwaltung dieser Daten inkl. Logo-Upload. +``` + +--- + +## Phase 1 — Backend: CompanySettings Entity + Module + +**Prompt für den Agent:** + +``` +Arbeite im Repository sim-system, Branch first-init. +Erstelle das CompanySettings-Feature im Backend (apps/server/src/). + +Orientiere dich beim Code-Stil an den bestehenden Modulen — insbesondere UsersModule und CustomerModule. +Das Muster ist immer: Entity → Interface → Repository → Service → Controller → Module → AppModule. + +SCHRITT 1 — Interface +Erstelle die Datei: + apps/server/src/models/settings/interfaces/company-settings.interface.ts + +Inhalt: +export interface ICompanySettings { + id: number; + companyName: string | null; + street: string | null; + zip: string | null; + city: string | null; + country: string | null; + phone: string | null; + email: string | null; + website: string | null; + firmenbuchnummer: string | null; + vatId: string | null; + issueCity: string | null; + vatRate: number; + paymentTermDays: number; + paymentFooterText: string | null; + bankAccounts: Array<{ name: string; iban: string; bic: string }>; + logoPath: string | null; + badge1Path: string | null; + badge2Path: string | null; + updatedAt: Date; +} + +--- + +SCHRITT 2 — Entity +Erstelle die Datei: + apps/server/src/models/settings/entities/company-settings.entity.ts + +Inhalt: +import { + Entity, + Column, + UpdateDateColumn, + PrimaryColumn, +} from 'typeorm'; +import { ICompanySettings } from '../interfaces/company-settings.interface'; + +@Entity({ name: 'company_settings' }) +export class CompanySettings implements ICompanySettings { + @PrimaryColumn({ default: 1 }) + id: number; + + @Column({ nullable: true, default: null }) + companyName: string | null; + + @Column({ nullable: true, default: null }) + street: string | null; + + @Column({ nullable: true, default: null }) + zip: string | null; + + @Column({ nullable: true, default: null }) + city: string | null; + + @Column({ nullable: true, default: 'Österreich' }) + country: string | null; + + @Column({ nullable: true, default: null }) + phone: string | null; + + @Column({ nullable: true, default: null }) + email: string | null; + + @Column({ nullable: true, default: null }) + website: string | null; + + @Column({ nullable: true, default: null }) + firmenbuchnummer: string | null; + + @Column({ nullable: true, default: null }) + vatId: string | null; + + @Column({ nullable: true, default: null }) + issueCity: string | null; + + @Column({ default: 20 }) + vatRate: number; + + @Column({ default: 14 }) + paymentTermDays: number; + + @Column({ nullable: true, default: null, type: 'text' }) + paymentFooterText: string | null; + + @Column({ type: 'simple-json', nullable: true, default: '[]' }) + bankAccounts: Array<{ name: string; iban: string; bic: string }>; + + @Column({ nullable: true, default: null }) + logoPath: string | null; + + @Column({ nullable: true, default: null }) + badge1Path: string | null; + + @Column({ nullable: true, default: null }) + badge2Path: string | null; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; +} + +--- + +SCHRITT 3 — Serializer +Erstelle die Datei: + apps/server/src/models/settings/serializers/company-settings.serializer.ts + +Inhalt: +import { Expose } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { ICompanySettings } from '../interfaces/company-settings.interface'; + +export const defaultSettingsGroupsForSerializing: string[] = ['default', 'settings.default']; + +export class CompanySettingsEntity extends ModelEntity implements ICompanySettings { + @Expose({ groups: ['default'] }) companyName: string | null; + @Expose({ groups: ['default'] }) street: string | null; + @Expose({ groups: ['default'] }) zip: string | null; + @Expose({ groups: ['default'] }) city: string | null; + @Expose({ groups: ['default'] }) country: string | null; + @Expose({ groups: ['default'] }) phone: string | null; + @Expose({ groups: ['default'] }) email: string | null; + @Expose({ groups: ['default'] }) website: string | null; + @Expose({ groups: ['default'] }) firmenbuchnummer: string | null; + @Expose({ groups: ['default'] }) vatId: string | null; + @Expose({ groups: ['default'] }) issueCity: string | null; + @Expose({ groups: ['default'] }) vatRate: number; + @Expose({ groups: ['default'] }) paymentTermDays: number; + @Expose({ groups: ['default'] }) paymentFooterText: string | null; + @Expose({ groups: ['default'] }) bankAccounts: Array<{ name: string; iban: string; bic: string }>; + @Expose({ groups: ['default'] }) logoPath: string | null; + @Expose({ groups: ['default'] }) badge1Path: string | null; + @Expose({ groups: ['default'] }) badge2Path: string | null; + @Expose({ groups: ['default'] }) updatedAt: Date; +} + +--- + +SCHRITT 4 — DTOs +Erstelle die Datei: + apps/server/src/models/settings/dto/update-settings.dto.ts + +Inhalt: +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString, IsNumber, IsArray, ValidateNested, Min, Max } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class BankAccountDto { + @IsString() name: string; + @IsString() iban: string; + @IsString() bic: string; +} + +export class UpdateSettingsDto { + @IsOptional() @IsString() companyName?: string; + @IsOptional() @IsString() street?: string; + @IsOptional() @IsString() zip?: string; + @IsOptional() @IsString() city?: string; + @IsOptional() @IsString() country?: string; + @IsOptional() @IsString() phone?: string; + @IsOptional() @IsString() email?: string; + @IsOptional() @IsString() website?: string; + @IsOptional() @IsString() firmenbuchnummer?: string; + @IsOptional() @IsString() vatId?: string; + @IsOptional() @IsString() issueCity?: string; + @IsOptional() @IsNumber() @Min(0) @Max(100) vatRate?: number; + @IsOptional() @IsNumber() @Min(0) paymentTermDays?: number; + @IsOptional() @IsString() paymentFooterText?: string; + @IsOptional() @IsArray() @ValidateNested({ each: true }) @Type(() => BankAccountDto) + bankAccounts?: BankAccountDto[]; +} + +--- + +SCHRITT 5 — Repository +Erstelle die Datei: + apps/server/src/models/settings/company-settings.repository.ts + +Inhalt: +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ModelRepository } from '../model.repository'; +import { CompanySettings } from './entities/company-settings.entity'; +import { + CompanySettingsEntity, + defaultSettingsGroupsForSerializing, +} from './serializers/company-settings.serializer'; + +@Injectable() +export class CompanySettingsRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(CompanySettings, dataSource.createEntityManager()); + } + + transform(model: CompanySettings): CompanySettingsEntity { + const transformOptions = { groups: defaultSettingsGroupsForSerializing }; + return plainToInstance( + CompanySettingsEntity, + instanceToPlain(model, transformOptions), + transformOptions, + ); + } + + transformMany(models: CompanySettings[]): CompanySettingsEntity[] { + return models.map((m) => this.transform(m)); + } +} + +--- + +SCHRITT 6 — Service +Erstelle die Datei: + apps/server/src/models/settings/company-settings.service.ts + +Inhalt: +import { Injectable } from '@nestjs/common'; +import { CompanySettingsRepository } from './company-settings.repository'; +import { CompanySettingsEntity } from './serializers/company-settings.serializer'; +import { UpdateSettingsDto } from './dto/update-settings.dto'; +import { CompanySettings } from './entities/company-settings.entity'; + +@Injectable() +export class CompanySettingsService { + constructor(private readonly repo: CompanySettingsRepository) {} + + async get(): Promise { + return this.repo.get(1, [], false); + } + + async upsert(dto: UpdateSettingsDto): Promise { + const existing = await this.repo.findOne({ where: { id: 1 } }); + if (existing) { + return this.repo.updateEntity(1, dto as any); + } + const created = await this.repo.save({ id: 1, ...dto } as CompanySettings); + return this.repo.transform(created); + } + + async updateLogoPath(field: 'logoPath' | 'badge1Path' | 'badge2Path', path: string): Promise { + const existing = await this.repo.findOne({ where: { id: 1 } }); + if (existing) { + return this.repo.updateEntity(1, { [field]: path } as any); + } + const created = await this.repo.save({ id: 1, [field]: path } as CompanySettings); + return this.repo.transform(created); + } +} + +--- + +SCHRITT 7 — Controller +Erstelle die Datei: + apps/server/src/models/settings/company-settings.controller.ts + +Inhalt: +import { + Get, Put, Post, Body, Controller, + UseInterceptors, SerializeOptions, ClassSerializerInterceptor, + ValidationPipe, UsePipes, UploadedFile, +} from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { diskStorage } from 'multer'; +import { extname, join } from 'path'; +import { ApiBearerAuth, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { CompanySettingsService } from './company-settings.service'; +import { + CompanySettingsEntity, + defaultSettingsGroupsForSerializing, +} from './serializers/company-settings.serializer'; +import { UpdateSettingsDto } from './dto/update-settings.dto'; +import { ReS } from '../../common/res.model'; + +const UPLOAD_DEST = join(process.cwd(), 'uploads', 'settings'); + +function storageConfig(fieldName: string) { + return diskStorage({ + destination: UPLOAD_DEST, + filename: (_req, file, cb) => { + cb(null, `${fieldName}${extname(file.originalname)}`); + }, + }); +} + +@ApiBearerAuth() +@Controller('settings') +@ApiTags('settings') +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ groups: defaultSettingsGroupsForSerializing, excludeExtraneousValues: true }) +export class CompanySettingsController { + constructor(private readonly settingsService: CompanySettingsService) {} + + @Get('/') + @ApiOperation({ summary: 'Einstellungen laden' }) + async get(): Promise> { + return ReS.FromData(await this.settingsService.get()); + } + + @Put('/') + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + @ApiOperation({ summary: 'Einstellungen speichern' }) + async upsert(@Body() dto: UpdateSettingsDto): Promise> { + return ReS.FromData(await this.settingsService.upsert(dto)); + } + + @Post('/logo') + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Logo hochladen (SVG oder PNG)' }) + @UseInterceptors(FileInterceptor('file', { storage: storageConfig('logo') })) + async uploadLogo(@UploadedFile() file: Express.Multer.File): Promise> { + const relativePath = join('uploads', 'settings', file.filename); + return ReS.FromData(await this.settingsService.updateLogoPath('logoPath', relativePath)); + } + + @Post('/badge1') + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Badge links hochladen' }) + @UseInterceptors(FileInterceptor('file', { storage: storageConfig('badge1') })) + async uploadBadge1(@UploadedFile() file: Express.Multer.File): Promise> { + const relativePath = join('uploads', 'settings', file.filename); + return ReS.FromData(await this.settingsService.updateLogoPath('badge1Path', relativePath)); + } + + @Post('/badge2') + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Badge rechts hochladen' }) + @UseInterceptors(FileInterceptor('file', { storage: storageConfig('badge2') })) + async uploadBadge2(@UploadedFile() file: Express.Multer.File): Promise> { + const relativePath = join('uploads', 'settings', file.filename); + return ReS.FromData(await this.settingsService.updateLogoPath('badge2Path', relativePath)); + } +} + +--- + +SCHRITT 8 — Module +Erstelle die Datei: + apps/server/src/models/settings/company-settings.module.ts + +Inhalt: +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CompanySettings } from './entities/company-settings.entity'; +import { CompanySettingsRepository } from './company-settings.repository'; +import { CompanySettingsService } from './company-settings.service'; +import { CompanySettingsController } from './company-settings.controller'; +import { MulterModule } from '@nestjs/platform-express'; +import { join } from 'path'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([CompanySettings]), + MulterModule.register({ dest: join(process.cwd(), 'uploads', 'settings') }), + ], + controllers: [CompanySettingsController], + providers: [CompanySettingsRepository, CompanySettingsService], + exports: [CompanySettingsService], +}) +export class CompanySettingsModule {} + +--- + +SCHRITT 9 — AppModule registrieren +Bearbeite die Datei: + apps/server/src/app.module.ts + +Füge den Import von CompanySettingsModule hinzu: + import { CompanySettingsModule } from './models/settings/company-settings.module'; + +Und in der imports-Array in @Module: + CompanySettingsModule, + +--- + +SCHRITT 10 — Static File Serving für Uploads +Bearbeite die Datei: + apps/server/src/main.ts + +Füge nach dem import-Block hinzu: + import { NestExpressApplication } from '@nestjs/platform-express'; + import { join } from 'path'; + +Ändere NestFactory.create zu: + const app = await NestFactory.create(AppModule); + +Füge nach app.enableCors(...) hinzu: + app.useStaticAssets(join(process.cwd(), 'uploads'), { prefix: '/uploads' }); + +--- + +SCHRITT 11 — Verzeichnis sicherstellen +Erstelle die Datei: + apps/server/uploads/settings/.gitkeep + +(Leere Datei, damit das Verzeichnis im Git landet) + +Füge in apps/server/.gitignore (falls vorhanden) hinzu: + uploads/settings/* + !uploads/settings/.gitkeep + +--- + +ABSCHLIESSEND: Prüfe ob @nestjs/platform-express und multer als Dependencies vorhanden sind: + package.json im Root prüfen. + Falls nicht vorhanden: Hinweis ausgeben dass folgendes installiert werden muss: + npm install @nestjs/platform-express multer + npm install --save-dev @types/multer +``` + +--- + +## Phase 2 — Backend: PdfMakerService auf Settings umstellen + +**Prompt für den Agent:** + +``` +Arbeite im Repository sim-system, Branch first-init. +Stelle den PdfMakerService auf dynamische CompanySettings um. +Phase 1 (CompanySettingsModule) ist bereits abgeschlossen. + +Ziel: Alle hardcoded Firmendaten aus apps/server/src/common/services/pdfmaker.service.ts +entfernen und durch Werte aus CompanySettingsService ersetzen. + +--- + +SCHRITT 1 — SharedModule erweitern +Bearbeite die Datei: + apps/server/src/common/shared.module.ts + +Das Modul muss CompanySettingsModule importieren, damit PdfMakerService auf +CompanySettingsService zugreifen kann. + +Füge hinzu: + import { CompanySettingsModule } from '../models/settings/company-settings.module'; + +In @Module: + imports: [AppConfigModule, CompanySettingsModule], + +Das ist korrekt weil CompanySettingsModule den CompanySettingsService exportiert. + +--- + +SCHRITT 2 — PdfMakerService komplett ersetzen + +Ersetze den gesamten Inhalt von: + apps/server/src/common/services/pdfmaker.service.ts + +Durch folgenden Code: + +import { Injectable } from '@nestjs/common'; +const PdfPrinter = require('pdfmake'); +import { Content, ContentTable, TDocumentDefinitions } from 'pdfmake/interfaces'; +import { SlipsheetEntity } from '../../models/bills/serializers/slipsheet.serializer'; +import { createWriteStream, existsSync, readFileSync } from 'fs'; +import { join } from 'path'; +import moment from 'moment'; +import { BillEntity } from '../../models/bills/serializers/bill.serializer'; +import { OrderEntryEntity } from '../../models/bills/serializers/order-entry.serializer'; +import { AnnotationEntity } from '../../models/bills/serializers/annotation.serializer'; +import { CompanySettingsService } from '../../models/settings/company-settings.service'; +import { CompanySettingsEntity } from '../../models/settings/serializers/company-settings.serializer'; + +// Fallback-Pfade auf die bestehenden hardcoded Dateien +const FALLBACK_LOGO = join(__dirname, '..', 'pdfAnnotation', 'logo.svg'); +const FALLBACK_BADGE1 = join(__dirname, '..', 'pdfAnnotation', 'adler.svg'); +const FALLBACK_BADGE2 = join(__dirname, '..', 'pdfAnnotation', 'gdfort.jpg'); + +@Injectable() +export class PdfMakerService { + + private readonly fonts = { + Courier: { + normal: 'Courier', bold: 'Courier-Bold', + italics: 'Courier-Oblique', bolditalics: 'Courier-BoldOblique', + }, + Helvetica: { + normal: 'Helvetica', bold: 'Helvetica-Bold', + italics: 'Helvetica-Oblique', bolditalics: 'Helvetica-BoldOblique', + }, + Times: { + normal: 'Times-Roman', bold: 'Times-Bold', + italics: 'Times-Italic', bolditalics: 'Times-BoldItalic', + }, + Arial: { + normal: join(__dirname, '..', 'pdfAnnotation', 'fonts', 'arial.ttf'), + bold: join(__dirname, '..', 'pdfAnnotation', 'fonts', 'arialbd.ttf'), + italics: join(__dirname, '..', 'pdfAnnotation', 'fonts', 'ariali.ttf'), + bolditalics: join(__dirname, '..', 'pdfAnnotation', 'fonts', 'arialbi.ttf'), + }, + Symbol: { normal: 'Symbol' }, + ZapfDingbats: { normal: 'ZapfDingbats' }, + }; + + private readonly _printer; + + constructor(private readonly settingsService: CompanySettingsService) { + this._printer = new PdfPrinter(this.fonts); + } + + // ─── Public API ──────────────────────────────────────────────────────────── + + public async generateDeliverySlip(slip: SlipsheetEntity): Promise { + const settings = await this.getSettings(); + const docDefinition = await this.buildDocDefinition(settings); + const content: Array = []; + content.push(this.buildHead(slip, slip.printDate, settings)); + content.push({ text: 'Lieferschein #' + slip.slipsheetnumber, style: 'header' }); + const annotation = this.buildSlipAnnotations(slip); + if (annotation) content.push(annotation); + content.push(this.buildOrdersDelivery(slip)); + docDefinition.content = content; + return this._printer.createPdfKitDocument(docDefinition); + } + + public async generateBill(bill: BillEntity): Promise { + const settings = await this.getSettings(); + const docDefinition = await this.buildDocDefinition(settings); + const content: Array = []; + content.push(this.buildHead(bill.slipsheets[0], bill.billDate, settings)); + content.push({ text: 'Rechnung #' + bill.billNumber, style: 'header' }); + content.push(this.buildOrders(bill, settings)); + docDefinition.content = content; + return this._printer.createPdfKitDocument(docDefinition); + } + + public savePDFToFileSystem(doc: PDFKit.PDFDocument, filepath: string): Promise { + return new Promise((resolve, reject) => { + if (!this.saveStreamtoFileSystem(doc, filepath, (_err, _pages, path) => resolve(path))) { + reject(new Error('PDF konnte nicht gespeichert werden')); + } + }); + } + + public saveStreamtoFileSystem( + doc: PDFKit.PDFDocument, + filepath: string, + cb: Function, + ): boolean { + try { + if (filepath) { + doc.pipe(createWriteStream(filepath)); + doc.on('end', () => cb(null, null, [filepath])); + doc.end(); + return true; + } + } catch (error) { + console.error('PdfMakerService.saveStreamtoFileSystem:', error); + } + return false; + } + + // ─── Private Helpers ─────────────────────────────────────────────────────── + + private async getSettings(): Promise { + try { + return await this.settingsService.get(); + } catch { + return null; + } + } + + private readFileSafe(filePath: string | null | undefined, fallback: string): string { + try { + if (filePath && existsSync(filePath)) return readFileSync(filePath).toString(); + } catch { /* fall through */ } + return readFileSync(fallback).toString(); + } + + private readFileBuffer(filePath: string | null | undefined, fallback: string): Buffer { + try { + if (filePath && existsSync(filePath)) return readFileSync(filePath); + } catch { /* fall through */ } + return readFileSync(fallback); + } + + private isSvg(path: string | null | undefined): boolean { + return (path ?? '').toLowerCase().endsWith('.svg'); + } + + private buildLogoContent(settings: CompanySettingsEntity | null) { + const logoPath = settings?.logoPath ?? null; + const usePath = (logoPath && existsSync(logoPath)) ? logoPath : FALLBACK_LOGO; + const svg = readFileSync(usePath).toString(); + return { svg, fit: [170, 170], margin: [60, 30, 0, 0] }; + } + + private buildFooterBadge1(settings: CompanySettingsEntity | null): Content { + const p = settings?.badge1Path; + const usePath = (p && existsSync(p)) ? p : FALLBACK_BADGE1; + const isSvg = usePath.toLowerCase().endsWith('.svg'); + const base: any = { alignment: 'right', width: 40, margin: [0, 0, 15, 0], color: '#e9582a' }; + return isSvg + ? { ...base, svg: readFileSync(usePath).toString() } + : { ...base, image: usePath }; + } + + private buildFooterBadge2(settings: CompanySettingsEntity | null): Content { + const p = settings?.badge2Path; + const usePath = (p && existsSync(p)) ? p : FALLBACK_BADGE2; + return { image: usePath, width: 40, margin: [25, 0, 0, 0], color: '#e9582a' }; + } + + private buildFooterText(settings: CompanySettingsEntity | null): string { + const paymentText = settings?.paymentFooterText + ?? `Zahlung innerhalb von ${settings?.paymentTermDays ?? 14} Tagen netto Kassa`; + + const bankLines = (settings?.bankAccounts ?? []) + .map(b => `${b.name} · IBAN: ${b.iban} · BIC: ${b.bic}`) + .join(' · '); + + const city = settings?.issueCity ?? 'Landeck'; + const base = `Zahlbar und klagbar in ${city}`; + + return [paymentText, base, bankLines].filter(Boolean).join(' · '); + } + + private buildHeaderStack(settings: CompanySettingsEntity | null): Content[] { + const s = settings; + const lines: Content[] = []; + + if (s?.street) lines.push({ text: s.street, style: 'header' }); + if (s?.zip || s?.city) lines.push({ text: `${s.zip ?? ''} ${s.city ?? ''}`.trim(), style: 'header' }); + if (s?.phone) lines.push({ text: ` `, style: 'subheader' }, + { text: `Mobile ${s.phone}`, style: 'subheader' }); + if (s?.email) lines.push({ text: `E-Mail: ${s.email}`, style: 'subheader' }); + if (s?.website) lines.push({ text: s.website, style: 'subheader' }); + if (s?.firmenbuchnummer) lines.push({ text: s.firmenbuchnummer, style: 'subheader' }); + + // Fallback wenn noch keine Einstellungen gesetzt + if (lines.length === 0) { + lines.push( + { text: 'Fliesserau 384 b', style: 'header' }, + { text: '6500 Landeck', style: 'header' }, + { text: ' ', style: 'subheader' }, + { text: 'Mobile +43 699 10 63 63 45', style: 'subheader' }, + { text: 'E-Mail: office@holz-abler.com', style: 'subheader' }, + { text: 'www.holz-abler.com', style: 'subheader' }, + { text: 'FN.: 303902s, ATU63848368', style: 'subheader' }, + ); + } + return lines; + } + + private async buildDocDefinition( + settings: CompanySettingsEntity | null, + ): Promise { + const self = this; + + return { + pageOrientation: 'portrait', + pageMargins: [60, 150, 60, 100], + + header: () => [{ + columns: [ + self.buildLogoContent(settings), + { + alignment: 'right', + margin: [0, 25, 70, 0], + stack: self.buildHeaderStack(settings), + }, + ], + }, { + canvas: [{ type: 'line', x1: 50, y1: 5, x2: 595 - 50, y2: 5, lineWidth: 1 }], + }], + + footer: () => [{ + canvas: [{ type: 'line', x1: 50, y1: 0, x2: 595 - 50, y2: 0, lineWidth: 1 }], + margin: [0, 30, 0, 5], + }, { + table: { + widths: [120, '*', 120], + body: [[ + self.buildFooterBadge1(settings), + { text: self.buildFooterText(settings), style: 'footerText' }, + self.buildFooterBadge2(settings), + ]], + }, + layout: 'noBorders', + }], + + content: [], + styles: this.buildStyles(), + defaultStyle: { font: 'Arial', fontSize: 12 }, + }; + } + + private buildStyles() { + return { + header: { fontSize: 12, color: 'black' }, + subheader: { fontSize: 9, color: 'black' }, + footerText: { fontSize: 8, margin: [0, 10, 0, 0], alignment: 'center', color: 'black' }, + tableExample: { margin: [0, 5, 0, 15] }, + tableHeader: { bold: true, fontSize: 7, color: '#e9582a' }, + tableSum: { color: 'black', bold: true }, + tableSumHeader: { bold: true, fontSize: 10, color: 'black' }, + tableCell: { fontSize: 7 }, + slipCell: { color: '#e9582a' }, + slipAnnotation: { fontSize: 9, color: 'black' }, + }; + } + + private buildHead( + slip: SlipsheetEntity, + date: Date | undefined, + settings: CompanySettingsEntity | null, + ): Content { + const city = settings?.issueCity ?? 'Landeck'; + return { + alignment: 'justify', + margin: [0, 10, 10, 40], + columns: [ + { + width: 'auto', + text: [ + 'An\n', + (slip.customer.companyName || '') + '\n', + slip.customer.lastName + ' ' + slip.customer.firstName + '\n', + slip.customer.address + '\n', + slip.customer.postcode + ' ' + slip.customer.country + '\n', + ], + }, + { + alignment: 'right', + margin: [0, 60, 0, 0], + text: + `${city}, am ${moment(date ?? new Date()).format('DD.MM.YYYY')}` + + (slip.customer.customerNumber ? '\nKunde: ' + slip.customer.customerNumber : '\n') + + (slip.customer.uid ? '\nIhre UID: ' + slip.customer.uid : '\nIhre UID:\n'), + }, + ], + }; + } + + private buildSlipAnnotations(slip: SlipsheetEntity): Content | null { + const items: Content[] = []; + slip?.annotations?.forEach((element: AnnotationEntity) => { + items.push({ text: element.text, style: 'slipAnnotation' }); + }); + return items.length === 0 ? null : items; + } + + private buildOrders(bill: BillEntity, settings: CompanySettingsEntity | null): Content { + const vatRate = settings?.vatRate ?? 20; + + const isDiscount = bill.slipsheets.some(c => c.orderEntries.some(o => o.articleGroupRabatt && o.articleGroupRabatt !== 0)); + const isDiscountSpecial = bill.slipsheets.some(c => c.orderEntries.some(o => o.customerRabatt && o.customerRabatt !== 0)); + + const table: any = { + style: 'tableExample', + table: { + headerRows: 1, + widths: [60, 'auto', '*', 'auto', 'auto', 'auto', 'auto', 'auto'], + body: [[ + { text: 'Pos', style: 'tableHeader', margin: [0, 0, 5, 0] }, + { text: 'Art.Num.', style: 'tableHeader' }, + { text: 'Artikel', style: 'tableHeader' }, + { text: 'Menge', style: 'tableHeader' }, + { text: 'Preis', style: 'tableHeader' }, + { text: isDiscount ? 'Rabatt' : '', style: 'tableHeader' }, + { text: isDiscountSpecial ? 'Sonder\nRabatt' : '', style: 'tableHeader' }, + { text: 'Gesamt', style: 'tableHeader' }, + ]], + }, + layout: this.getTableDefaultLayout(), + }; + + let sum = 0; + + for (const slip of bill.slipsheets) { + table.table.body.push([ + '', + { text: 'Lieferschein von ' + moment(slip.createdAt).format('DD.MM.YYYY') + ' L' + slip.slipsheetnumber, colSpan: 6, style: 'slipCell' }, + '', '', '', '', '', '', + ]); + + const annotation = this.buildSlipAnnotations(slip); + if (annotation) { + const annotationCell = { ...annotation, colSpan: 6 }; + table.table.body.push(['', annotationCell, '', '', '', '', '', '']); + } + + for (let i = 0; i < slip.orderEntries.length; i++) { + const el: OrderEntryEntity = slip.orderEntries[i]; + const amount = el.amountCounted || el.amount; + const discount = el.articleGroupRabatt ?? 0; + const discountSpecial = el.customerRabatt ?? 0; + const total = amount * el.price * (100 - discount) / 100 * (100 - discountSpecial) / 100; + sum += total; + + table.table.body.push([ + { text: i + 1, style: 'tableCell' }, + { text: el.article?.artNumber ?? '', style: 'tableCell' }, + { text: el.text, style: 'tableCell' }, + { text: amount, style: 'tableCell', alignment: 'right' }, + { text: '€' + el.price.toFixed(2), style: 'tableCell', alignment: 'right' }, + { text: isDiscount ? discount.toFixed(0) + '%' : '', style: 'tableCell', alignment: 'right' }, + { text: isDiscountSpecial ? discountSpecial.toFixed(0) + '%' : '', style: 'tableCell', alignment: 'right' }, + { text: '€' + total.toFixed(2), style: 'tableCell', alignment: 'right' }, + ]); + } + } + + const vatLabel = `${vatRate}% MwSt.`; + const empty = { text: '', border: [0, 0, 0, 0] }; + + table.table.body.push( + [empty, empty, empty, empty, { text: 'Summe', colSpan: 2, style: 'tableCell', alignment: 'right' }, '', '', { text: '€' + sum.toFixed(2), style: 'tableCell', alignment: 'right' }], + [empty, empty, empty, empty, { text: vatLabel, colSpan: 2, style: 'tableCell', alignment: 'right' }, '', '', { text: '€' + (sum * vatRate / 100).toFixed(2), style: 'tableCell', alignment: 'right' }], + [empty, empty, empty, empty, { text: 'Gesamt', colSpan: 2, style: 'tableSumHeader', alignment: 'right' }, '', '', { text: '€' + (sum * (1 + vatRate / 100)).toFixed(2), style: 'tableSumHeader', alignment: 'right' }], + ); + + return table; + } + + private buildOrdersDelivery(slip: SlipsheetEntity): Content { + const table: ContentTable = { + style: 'tableExample', + table: { + headerRows: 1, + widths: [100, '*', '*', 'auto'], + body: [[ + { text: 'Pos', style: 'tableHeader', margin: [0, 0, 5, 0] }, + { text: 'Artikel', style: 'tableHeader' }, + { text: 'Typ', style: 'tableHeader' }, + { text: 'Menge', style: 'tableHeader' }, + ]], + }, + layout: this.getSlipTableLayout(), + }; + + for (let i = 0; i < slip.orderEntries.length; i++) { + const el = slip.orderEntries[i]; + table.table.body.push([ + { text: i + 1, style: 'tableCell' }, + { text: el.text, style: 'tableCell' }, + { text: el.article?.artNumber ?? '', style: 'tableCell' }, + { text: el.amount, style: 'tableCell' }, + ]); + } + table.table.body.push(['', '', '', '']); + return table; + } + + private getTableDefaultLayout() { + return { + hLineWidth: (i: number, node: any) => { + if (i === 0) return 0; + if (i === node.table.body.length) return 2; + if (i === node.table.body.length - 1) return 1; + return 1; + }, + vLineWidth: () => 0, + hLineColor: (i: number, node: any) => { + if (i === node.table.body.length - 3) return 'black'; + return (i === 1 || i === node.table.body.length - 1 || i === node.table.body.length) ? 'black' : '#aaa'; + }, + paddingLeft: (i: number) => (i <= 1 ? 0 : 5), + paddingRight: (i: number, node: any) => (i === node.table.widths.length - 1 ? 0 : 5), + paddingTop: () => 4, + paddingBottom: () => 4, + fillColor: () => null, + }; + } + + private getSlipTableLayout() { + return { + hLineWidth: (i: number, node: any) => { + if (i === 0) return 0; + if (i === node.table.body.length) return 2; + if (i === node.table.body.length - 1) return 1; + return 1; + }, + vLineWidth: () => 0, + hLineColor: (i: number, node: any) => + (i === 1 || i === node.table.body.length - 1 || i === node.table.body.length) ? 'black' : '#aaa', + paddingLeft: (i: number) => (i <= 1 ? 0 : 5), + paddingRight: (i: number, node: any) => (i === node.table.widths.length - 1 ? 0 : 5), + paddingTop: () => 4, + paddingBottom: () => 4, + fillColor: () => null, + }; + } +} + +--- + +WICHTIG nach dem Ersetzen: +Die Methoden generateDeliverySlip und generateBill sind jetzt async (geben Promise zurück). +Prüfe alle Aufrufer dieser Methoden: + - apps/server/src/models/bills/bill.service.ts → Methode generateBill + - apps/server/src/models/bills/slipsheet.service.ts → Methode generateSlipsheet + +In bill.service.ts: + Suche nach: const retpdf = this.pdfMakerService.generateBill(firstBillEntity); + Ändere zu: const retpdf = await this.pdfMakerService.generateBill(firstBillEntity); + + Suche nach: const retpdf = this.pdfMakerService.generateBill(bill); (in regenerateBillPdf) + Ändere zu: const retpdf = await this.pdfMakerService.generateBill(bill); + +In slipsheet.service.ts: + Suche nach: const retpdf = this.pdfMakerService.generateDeliverySlip(slip); + Ändere zu: const retpdf = await this.pdfMakerService.generateDeliverySlip(slip); + + Stelle sicher, dass die umgebenden Methoden (generateSlipsheet etc.) ebenfalls async sind. +``` + +--- + +## Phase 3 — Frontend: SettingsService + +**Prompt für den Agent:** + +``` +Arbeite im Repository sim-system, Branch first-init. +Erstelle den Angular-Service für die Settings-API. +Phase 1 und 2 (Backend) sind abgeschlossen. + +Orientiere dich am Stil von apps/sim-system/src/app/services/customer.service.ts. + +--- + +SCHRITT 1 — Model +Erstelle die Datei: + apps/sim-system/src/app/models/settings.model.ts + +Inhalt: +export interface BankAccount { + name: string; + iban: string; + bic: string; +} + +export class CompanySettings { + id?: number; + companyName: string = ''; + street: string = ''; + zip: string = ''; + city: string = ''; + country: string = 'Österreich'; + phone: string = ''; + email: string = ''; + website: string = ''; + firmenbuchnummer: string = ''; + vatId: string = ''; + issueCity: string = ''; + vatRate: number = 20; + paymentTermDays: number = 14; + paymentFooterText: string = ''; + bankAccounts: BankAccount[] = []; + logoPath: string | null = null; + badge1Path: string | null = null; + badge2Path: string | null = null; + updatedAt?: Date; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} + +--- + +SCHRITT 2 — Service +Erstelle die Datei: + apps/sim-system/src/app/services/settings.service.ts + +Inhalt: +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { CompanySettings } from '../models/settings.model'; + +@Injectable({ providedIn: 'root' }) +export class SettingsService { + constructor(private http: HttpClient) {} + + get(): Observable { + return this.http.get('settings').pipe( + map(o => o.success ? new CompanySettings(o.data ?? {}) : new CompanySettings()), + ); + } + + save(settings: Partial): Observable { + return this.http.put('settings', settings).pipe( + map(o => { + if (o.success) return new CompanySettings(o.data); + throw new Error(o.message || 'Fehler beim Speichern'); + }), + ); + } + + uploadLogo(file: File): Observable { + return this._upload('settings/logo', file); + } + + uploadBadge1(file: File): Observable { + return this._upload('settings/badge1', file); + } + + uploadBadge2(file: File): Observable { + return this._upload('settings/badge2', file); + } + + private _upload(endpoint: string, file: File): Observable { + const fd = new FormData(); + fd.append('file', file); + return this.http.post(endpoint, fd).pipe( + map(o => { + if (o.success) return new CompanySettings(o.data); + throw new Error(o.message || 'Upload fehlgeschlagen'); + }), + ); + } +} +``` + +--- + +## Phase 4 — Frontend: Settings-Komponente + +**Prompt für den Agent:** + +``` +Arbeite im Repository sim-system, Branch first-init. +Erstelle die Angular Settings-Seite unter /settings. +Phase 1–3 sind abgeschlossen. + +Orientiere dich beim Stil exakt an apps/sim-system/src/app/views/customer-edit/. +Verwende standalone components, signals, ReactiveFormsModule, ChangeDetectionStrategy.OnPush. +CSS-Klassen: sims-card, sims-label, sims-input, btn-sims-primary, btn-sims-ghost, + sims-page-header, eyebrow, page-title — genau wie in anderen Views. + +--- + +SCHRITT 1 — Komponente TypeScript +Erstelle die Datei: + apps/sim-system/src/app/views/settings/settings.component.ts + +Inhalt: +import { + ChangeDetectionStrategy, Component, inject, signal, OnInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormGroup, FormControl, FormArray } from '@angular/forms'; +import { SettingsService } from '../../services/settings.service'; +import { CompanySettings } from '../../models/settings.model'; + +@Component({ + selector: 'app-settings', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './settings.component.html', + styleUrl: './settings.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SettingsComponent implements OnInit { + private settingsService = inject(SettingsService); + + readonly activeTab = signal<'firma' | 'zahlung' | 'logos'>('firma'); + readonly saving = signal(false); + readonly loading = signal(true); + readonly error = signal(''); + readonly saveSuccess = signal(false); + + readonly logoPreviewUrl = signal(null); + readonly badge1PreviewUrl = signal(null); + readonly badge2PreviewUrl = signal(null); + + readonly form = new FormGroup({ + // Tab 1 — Firma + companyName: new FormControl(''), + street: new FormControl(''), + zip: new FormControl(''), + city: new FormControl(''), + country: new FormControl('Österreich'), + phone: new FormControl(''), + email: new FormControl(''), + website: new FormControl(''), + firmenbuchnummer: new FormControl(''), + vatId: new FormControl(''), + issueCity: new FormControl(''), + // Tab 2 — Zahlung + vatRate: new FormControl(20), + paymentTermDays: new FormControl(14), + paymentFooterText: new FormControl(''), + bankAccounts: new FormArray([]), + }); + + get bankAccountsArray(): FormArray { + return this.form.get('bankAccounts') as FormArray; + } + + ngOnInit() { + this.settingsService.get().subscribe({ + next: (s) => { + this.patchForm(s); + this.loading.set(false); + }, + error: () => { + this.loading.set(false); + }, + }); + } + + private patchForm(s: CompanySettings) { + this.form.patchValue({ + companyName: s.companyName, + street: s.street, + zip: s.zip, + city: s.city, + country: s.country, + phone: s.phone, + email: s.email, + website: s.website, + firmenbuchnummer: s.firmenbuchnummer, + vatId: s.vatId, + issueCity: s.issueCity, + vatRate: s.vatRate, + paymentTermDays: s.paymentTermDays, + paymentFooterText: s.paymentFooterText, + }); + this.bankAccountsArray.clear(); + (s.bankAccounts ?? []).forEach(b => this.bankAccountsArray.push(this.newBankGroup(b))); + if (s.logoPath) this.logoPreviewUrl.set('/uploads/' + s.logoPath.split('uploads/').pop()); + if (s.badge1Path) this.badge1PreviewUrl.set('/uploads/' + s.badge1Path.split('uploads/').pop()); + if (s.badge2Path) this.badge2PreviewUrl.set('/uploads/' + s.badge2Path.split('uploads/').pop()); + } + + private newBankGroup(init?: { name: string; iban: string; bic: string }): FormGroup { + return new FormGroup({ + name: new FormControl(init?.name ?? ''), + iban: new FormControl(init?.iban ?? ''), + bic: new FormControl(init?.bic ?? ''), + }); + } + + addBank() { this.bankAccountsArray.push(this.newBankGroup()); } + removeBank(i: number) { this.bankAccountsArray.removeAt(i); } + + save() { + if (this.form.invalid) { this.form.markAllAsTouched(); return; } + this.saving.set(true); + this.error.set(''); + this.settingsService.save(this.form.getRawValue() as any).subscribe({ + next: (s) => { + this.saving.set(false); + this.saveSuccess.set(true); + setTimeout(() => this.saveSuccess.set(false), 3000); + }, + error: (err) => { + this.saving.set(false); + this.error.set(err.message || 'Fehler beim Speichern'); + }, + }); + } + + onFileChange(event: Event, field: 'logo' | 'badge1' | 'badge2') { + const input = event.target as HTMLInputElement; + const file = input.files?.[0]; + if (!file) return; + + const upload$ = + field === 'logo' ? this.settingsService.uploadLogo(file) : + field === 'badge1' ? this.settingsService.uploadBadge1(file) : + this.settingsService.uploadBadge2(file); + + upload$.subscribe({ + next: (s) => { + const reader = new FileReader(); + reader.onload = (e) => { + const url = e.target?.result as string; + if (field === 'logo') this.logoPreviewUrl.set(url); + if (field === 'badge1') this.badge1PreviewUrl.set(url); + if (field === 'badge2') this.badge2PreviewUrl.set(url); + }; + reader.readAsDataURL(file); + }, + error: (err) => this.error.set(err.message || 'Upload fehlgeschlagen'), + }); + } + + setTab(tab: 'firma' | 'zahlung' | 'logos') { this.activeTab.set(tab); } +} + +--- + +SCHRITT 2 — Template HTML +Erstelle die Datei: + apps/sim-system/src/app/views/settings/settings.component.html + +Inhalt: +
+
+
System
+

Einstellungen

+
+
+ +@if (error()) { +
{{ error() }}
+} +@if (saveSuccess()) { +
✓ Einstellungen gespeichert
+} + + +
+ + + +
+ +@if (loading()) { +
+} + +@if (!loading()) { +
+ + + @if (activeTab() === 'firma') { +
+
Firmenstammdaten
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ } + + + @if (activeTab() === 'zahlung') { +
+
Zahlungskonditionen
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
Bankverbindungen
+ +
+ @for (bank of bankAccountsArray.controls; track $index) { +
+
+ Bank {{ $index + 1 }} + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ } +
+ + +
+ } + + + @if (activeTab() === 'logos') { +
+
Uploads
+
+ +
+ +
+ @if (logoPreviewUrl()) { + Logo + } @else { +
+ 🏷️ + Logo hochladen + SVG bevorzugt · max. 1 MB +
+ } +
+ +
+ +
+ +
+ @if (badge1PreviewUrl()) { + Badge 1 + } @else { +
+ 🦅 + Badge hochladen + SVG oder PNG +
+ } +
+ +
+ +
+ +
+ @if (badge2PreviewUrl()) { + Badge 2 + } @else { +
+ 🏅 + Badge hochladen + PNG · max. 500 KB +
+ } +
+ +
+ +
+

Uploads werden sofort gespeichert. Das Logo wird beim nächsten PDF-Erstellen verwendet.

+
+ } + + +
+ +
+ +
+} + +--- + +SCHRITT 3 — SCSS +Erstelle die Datei: + apps/sim-system/src/app/views/settings/settings.component.scss + +Inhalt: +.mb-2 { margin-bottom: 0.5rem; } +.mb-3 { margin-bottom: 1rem; } +.mt-2 { margin-top: 0.75rem; } + +.error-card { + color: var(--status-empty); + border-left: 3px solid var(--status-empty); +} +.success-card { + color: var(--status-ok); + border-left: 3px solid var(--status-ok); +} + +.form-eyebrow { + font-family: var(--mono); + font-size: 0.6rem; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--ink-3); + margin-bottom: 1.25rem; +} + +/* TABS */ +.settings-tabs { + display: flex; + gap: 2px; + background: var(--border); + padding: 2px; + border-radius: var(--radius-md); + width: fit-content; +} +.settings-tab { + font-family: var(--sans); + font-size: 0.85rem; + padding: 0.45rem 1rem; + border: none; + border-radius: calc(var(--radius-md) - 2px); + background: transparent; + color: var(--ink-3); + cursor: pointer; + transition: background 0.15s, color 0.15s; + white-space: nowrap; +} +.settings-tab.active { + background: white; + color: var(--ink); + box-shadow: 0 1px 3px rgba(15,23,42,0.08); +} +.settings-tab:hover:not(.active) { + color: var(--ink-2); +} + +/* BANK */ +.bank-entry { + background: var(--bg-subtle); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 1rem; +} +.bank-entry-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; +} +.bank-label { + font-weight: 600; + font-size: 0.88rem; + color: var(--ink); +} +.btn-sm { + font-size: 0.78rem; + padding: 0.25rem 0.7rem; +} + +/* UPLOADS */ +.uploads-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1.25rem; + margin-bottom: 1rem; +} +.upload-item { display: flex; flex-direction: column; gap: 0.3rem; } +.upload-zone { + border: 2px dashed var(--border); + border-radius: var(--radius-md); + background: var(--bg-subtle); + min-height: 120px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: border-color 0.15s, background 0.15s; + overflow: hidden; +} +.upload-zone:hover { + border-color: var(--accent-highlight); + background: var(--accent-light); +} +.upload-placeholder { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + color: var(--ink-3); + font-size: 0.85rem; + text-align: center; + padding: 1rem; +} +.upload-icon { font-size: 1.75rem; } +.upload-preview { + max-width: 100%; + max-height: 120px; + object-fit: contain; + padding: 0.5rem; +} +.upload-hint-text { + font-size: 0.8rem; + color: var(--ink-3); + margin-bottom: 0; +} + +/* STICKY */ +.sticky-actions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; + position: sticky; + bottom: 1rem; + padding: 0.75rem 0; +} + +/* GRID HELPERS */ +.row { display: flex; flex-wrap: wrap; margin: -0.375rem; } +.row.g-3 > * { padding: 0.375rem; } +[class^="col-"] { width: 100%; } +@media (min-width: 768px) { + .col-md-3 { width: 25%; } + .col-md-4 { width: 33.333%; } + .col-md-6 { width: 50%; } +} +.col-6 { width: 50%; } +.col-12 { width: 100%; } +``` + +--- + +## Phase 5 — Frontend: Route + Navigation verdrahten + +**Prompt für den Agent:** + +``` +Arbeite im Repository sim-system, Branch first-init. +Verdrahte die Settings-Komponente mit dem Router und der Sidebar-Navigation. +Phasen 1–4 sind abgeschlossen. + +--- + +SCHRITT 1 — Route hinzufügen +Bearbeite die Datei: + apps/sim-system/src/app/app.routes.ts + +Füge vor der Wildcard-Route (path: '**') folgenden Eintrag hinzu: + + { + path: 'settings', + loadComponent: () => + import('./views/settings/settings.component').then(m => m.SettingsComponent) + }, + +--- + +SCHRITT 2 — Navigation erweitern +Bearbeite die Datei: + apps/sim-system/src/app/app.ts + +In der navItems-Array gibt es eine Sektion 'Werkzeuge'. Füge dort einen Settings-Eintrag hinzu: + +Suche den Block: + { label: 'Werkzeuge', children: [ + { label: 'Inventur', route: '/inventory', icon: 'inventory' }, + ]}, + +Ändere ihn zu: + { label: 'Werkzeuge', children: [ + { label: 'Inventur', route: '/inventory', icon: 'inventory' }, + { label: 'Einstellungen', route: '/settings', icon: 'settings' }, + ]}, + +--- + +SCHRITT 3 — Settings-Icon definieren +In derselben Datei (apps/sim-system/src/app/app.ts) gibt es das ICONS-Objekt. +Füge folgenden Eintrag hinzu: + + settings: ` + + + `, + +--- + +SCHRITT 4 — Prüfen +Stelle sicher, dass in apps/sim-system/src/app/app.ts die bottomNavItems-Array +NICHT um Settings erweitert wird — die mobile Bottom-Navigation soll nur die 5 +wichtigsten Einträge zeigen (Dashboard, Artikel, Kunden, Belege, Inventur). +``` + +--- + +## Abschluss-Check (nach allen Phasen) + +**Prompt für den Agent:** + +``` +Führe nach Abschluss aller Phasen folgende Prüfungen durch: + +1. BACKEND KOMPILIERUNG + Führe aus: cd apps/server && npx tsc --noEmit + Erwartetes Ergebnis: Keine Fehler. + Häufige Fehler: + - "Object is possibly null" bei settings?.field → mit ?? '' oder ?? 0 absichern + - "Property does not exist" → CompanySettingsEntity-Felder prüfen + - Async-Fehler bei generateBill/generateDeliverySlip → await in allen Aufrufern prüfen + +2. CIRCULAR DEPENDENCY CHECK + Prüfe manuell: Importiert CompanySettingsModule irgendwo SharedModule? + Das wäre eine Circular Dependency. Es darf nur in eine Richtung gehen: + SharedModule → CompanySettingsModule → (keine Rückimporte) + +3. FRONTEND KOMPILIERUNG + Führe aus: cd apps/sim-system && npx ng build --configuration=development 2>&1 | head -50 + Erwartetes Ergebnis: Keine Fehler. + +4. UPLOADS-VERZEICHNIS + Stelle sicher dass apps/server/uploads/settings/.gitkeep existiert. + +5. MULTER DEPENDENCY + Prüfe in package.json (Root) ob vorhanden: + - @nestjs/platform-express + - multer + - @types/multer (devDependencies) + Falls nicht: Ausgabe mit Installationsbefehl. + +6. FALLBACK-VERHALTEN + Überprüfe in pdfmaker.service.ts: + - FALLBACK_LOGO zeigt auf den existierenden Pfad apps/server/src/common/pdfAnnotation/logo.svg + - Der join-Pfad nutzt __dirname korrekt (relativ zum dist-Verzeichnis nach Kompilierung) + - Empfehlung: Pfad-Konstanten am Service-Anfang klar dokumentieren +``` + +--- + +## Hinweise für den Agent + +- **Reihenfolge einhalten**: Phase 1 → 2 → 3 → 4 → 5. Phase 2 baut auf Phase 1 auf. +- **Bestehenden Code nicht löschen**: Die Dateien in `pdfAnnotation/` bleiben als Fallback erhalten. +- **SQLite synchronize**: Da `SQLITE_RUN_SYNCHRONIZE=true` in der `.env` gesetzt ist, erstellt TypeORM die neue Tabelle `company_settings` automatisch beim nächsten Start. +- **Keine Migrations nötig**: Nur für Produktionsumgebungen relevant, nicht für den aktuellen Stand. +- **Pfadtrenner**: Der bestehende Code nutzt `\\` (Windows-Pfade). Phase 2 verwendet `join()` aus `path` — das ist korrekt und cross-platform. \ No newline at end of file diff --git a/OPEN_SOURCE_NOTICES.md b/OPEN_SOURCE_NOTICES.md new file mode 100644 index 0000000..35d6050 --- /dev/null +++ b/OPEN_SOURCE_NOTICES.md @@ -0,0 +1,18 @@ +# Open Source Notices + +This project builds upon the following open-source libraries: + +- Angular - MIT License, Copyright (c) Google LLC. +- NestJS - MIT License, Copyright (c) 2017-2025 Kamil Mysliwiec and Contributors. +- Nx - MIT License, Copyright (c) Nx, Inc. +- Bootstrap - MIT License, Copyright (c) The Bootstrap Authors. +- Lucide (including lucide-angular) - ISC License, Copyright (c) Lucide Contributors. +- RxJS - Apache License 2.0, Copyright (c) Google LLC and contributors. +- sharp - Apache License 2.0, Copyright (c) Lovell Fuller and contributors. +- bwip-js - MIT License, Copyright (c) Metafloor. +- qrcode - MIT License, Copyright (c) Soldair (Ryan Day). +- sanitize-html - MIT License, Copyright (c) Apostrophe Technologies. +- marked - MIT License, Copyright (c) Christopher Jeffrey. +- uuid - MIT License, Copyright (c) 2010-2025 Robert Kieffer and Contributors. + +Additional dependencies are documented in the project package manifests. diff --git a/README.md b/README.md index e69de29..d8664d9 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,240 @@ +# SIMS — sims.abler.tirol + +Inventory Management System für das **abler.tirol** Ökosystem. +NX Monorepo mit Angular 21 Frontend und NestJS 11 Backend. + +--- + +## Projektstruktur + +``` +sim-system/ +├── apps/ +│ ├── server/ → NestJS 11 REST API (Port 3000) +│ └── sim-system/ → Angular 21 Frontend (Port 4200) +├── package.json → Root — alle Dependencies +├── nx.json +└── tsconfig.base.json +``` + +--- + +## Tech Stack + +| Bereich | Technologie | +|---|---| +| Frontend | Angular 21, Standalone Components, Signals | +| Backend | NestJS 11, TypeORM 0.3, better-sqlite3 | +| Monorepo | NX 22 | +| PDF | pdfmake 0.2 | +| API-Docs | Swagger (`GET /api/v1/doc`) | +| Design | abler.tirol Design-System (Teal) | + +--- + +## Setup + +### Voraussetzungen + +- Node.js 20+ +- `npm install` **ohne** `--ignore-scripts` (kompiliert `better-sqlite3` native binary) + +```bash +npm install +``` + +### Umgebungsvariablen + +Datei `apps/server/.env` anlegen (Vorlage: `apps/server/.env.example`): + +```env +NODE_ENV=development +APP_PORT=3000 +CORS_ORIGIN=http://localhost:4200 + +SQLITE_PATH=sim.db +SQLITE_RUN_MIGRATION=false +SQLITE_RUN_SYNCHRONIZE=true +SQLITE_ENTITIES=dist/**/*.entity.js + +PDF_SLIP_PATH=./pdfs/slips +PDF_BILL_PATH=./pdfs/bills +``` + +--- + +## Dev-Server starten + +```bash +# Backend (Port 3000) +npx nx serve server + +# Frontend (Port 4200 → Proxy → 3000) +npx nx serve sim-system +``` + +Frontend: `http://localhost:4200` +API-Docs: `http://localhost:3000/api/v1/doc` + +--- + +## NX-Befehle + +```bash +# Builds +npx nx build server +npx nx build sim-system +npx nx build sim-system --configuration=production + +# Tests +npx nx test server +npx nx test sim-system + +# TypeORM Migrations +npm run migration:generate --name=MigrationName +npm run migration:run +npm run migration:revert +npm run migration:show +``` + +--- + +## API-Routen (`/api/v1/`) + +### Artikel +| Method | Route | Beschreibung | +|---|---|---| +| GET | `/articles` | Alle Artikel | +| GET | `/articles?code=X` | Artikel nach Barcode | +| POST | `/articles` | Artikel anlegen | +| PATCH | `/articles/:id` | Artikel updaten | +| DELETE | `/articles/:id` | Artikel löschen | +| POST | `/articles/:id/inventory` | Inventurbuchung | +| POST | `/articles/import` | CSV-Import | +| GET | `/articlegroups` | Artikelgruppen | + +### Kunden +| Method | Route | Beschreibung | +|---|---|---| +| GET | `/customers` | Alle Kunden | +| GET | `/customers/:id` | Kunde by ID | +| POST | `/customers` | Kunde anlegen | +| PATCH | `/customers/:id` | Kunde updaten | +| DELETE | `/customers/:id` | Kunde löschen | +| GET | `/customers/:id/slipsheets` | Lieferscheine des Kunden | +| GET | `/customers/:id/bills` | Rechnungen des Kunden | +| PUT | `/customers/:id/discounts` | Rabatt setzen | +| DELETE | `/customers/:id/discounts/:dId` | Rabatt löschen | + +### Lieferscheine +| Method | Route | Beschreibung | +|---|---|---| +| GET | `/slipsheets` | Alle Lieferscheine | +| GET | `/slipsheets/:id` | Lieferschein by ID | +| POST | `/slipsheets` | Lieferschein anlegen | +| PUT | `/slipsheets/:id` | Lieferschein updaten | +| POST | `/slipsheets/:id` | Order-Entry hinzufügen | +| GET | `/slipsheets/:id/pdf` | PDF generieren | +| POST | `/slipsheets/:id/print` | Drucken | + +### Rechnungen +| Method | Route | Beschreibung | +|---|---|---| +| GET | `/bills` | Alle Rechnungen | +| POST | `/bills/generate` | Rechnung aus Lieferscheinen erzeugen | +| PUT | `/bills/:id` | Rechnung updaten | +| POST | `/bills/:id` | Rechnung neu erstellen (recreate) | +| GET | `/bills/:id/pdf` | PDF generieren | + +### Sonstiges +| Method | Route | Beschreibung | +|---|---|---| +| PUT | `/order-entries/:id` | Order-Entry updaten | +| DELETE | `/order-entries/:id` | Order-Entry löschen | +| GET | `/dashboard/summary` | Dashboard KPIs | + +--- + +## Frontend-Routing + +``` +/ → /dashboard +/dashboard → Dashboard (KPIs) +/articles → Artikelliste +/articles/:id → Artikeldetail +/inventory → Inventurbuchung (Mobile-optimiert) +/customers → Kundenliste +/customers/new → Kunde anlegen +/customers/:id → Kundendetail + Lieferschein/Rechnungsansicht +/customers/:id/edit → Kunde bearbeiten +/slipsheets → Lieferscheinliste +/slipsheets/:id → Lieferscheindetail +/order/new → Neue Bestellung / Lieferschein anlegen +/bills → Rechnungsliste +/bills/:id → Rechnungsdetail +/login → Login +``` + +--- + +## Design-System + +Akzentfarben (Teal): + +```css +--accent-primary: #134e4a +--accent-secondary: #0f3d3a +--accent-highlight: #14b8a6 +--accent-light: #f0fdfa +``` + +Fonts (geladen von `api.abler.tirol`): +- Headlines: `DM Serif Display` +- Interface: `Inter` +- Code / Labels / Tags: `DM Mono` + +Regeln: +- Buttons: `border-radius: 999px` (Pill-Form) +- Cards: `border-radius: 6px` +- Kein reines Schwarz — immer `var(--ink)` +- Keine System-Fonts + +--- + +## Bekannte offene Punkte + +### Bugs +- `GET /slipsheets/:id` gibt durch falsches Array-Indexing `undefined` zurück — Workaround: Daten immer über `GET /customers/:id/slipsheets` laden +- `GET /bills/:id/pdf` hat Side-Effects (erzeugt Rechnung bei jedem Aufruf neu) +- `generateBill()` löscht im Fehlerfall bestehende Rechnungen +- Nummernvergabe für Rechnungen/Lieferscheine ohne DB-Lock (Race Condition) +- `inventoryDate` ist `@CreateDateColumn` statt normalem `@Column` + +### Offen +- Auth / JWT Guards sind noch auskommentiert — API aktuell offen +- Login-Page ist ein Stub (leitet nur weiter) +- Docker-Setup (`docker-compose.dev.yml`) noch nicht mit Monorepo-Struktur getestet + +--- + +## Wichtige Dateipfade + +``` +apps/server/src/main.ts → NestJS Bootstrap +apps/server/src/app/app.module.ts → Root Module +apps/server/src/app/models/ → Entities, Services, Controller +apps/server/src/common/services/ → PDF-Generierung etc. +apps/server/src/datasource.ts → TypeORM DataSource (Migrations) +apps/server/.env → Umgebungsvariablen (gitignored) +apps/server/.env.example → Vorlage + +apps/sim-system/src/main.ts → Angular Bootstrap +apps/sim-system/src/app/app.config.ts → Provider-Konfiguration +apps/sim-system/src/app/app.routes.ts → Root-Routing +apps/sim-system/src/app/services/ → Angular Services +apps/sim-system/src/app/models/ → TypeScript Datenmodelle +apps/sim-system/src/app/views/ → Seiten-Komponenten +apps/sim-system/src/app/components/ → Shared Components (SlipsheetEditor) +apps/sim-system/src/styles/ → Design-System CSS +apps/sim-system/src/environments/ → API-URL Konfiguration +``` diff --git a/WORKFLOW.md b/WORKFLOW.md new file mode 100644 index 0000000..284ddf5 --- /dev/null +++ b/WORKFLOW.md @@ -0,0 +1,372 @@ +# WORKFLOW + +Stand: 2026-03-19 +Quelle: aus aktuellem Frontend, Services und Backend-API abgeleitet +Hinweis: beschreibt den Ist-Zustand des Repos, nicht einen Wunsch-Sollzustand + +## Zielbild der Software + +Die Software ist eine interne Betriebsanwendung fuer Artikelverwaltung, Lagerfuehrung, Kundenpflege sowie die Erstellung und Nachverfolgung von Lieferscheinen und Rechnungen. + +## Abgeleitete Rollen + +### 1. Sachbearbeitung Verkauf + +Arbeitet mit Kunden, offenen Lieferscheinen, Belegen und Rechnungen. + +### 2. Lager / Inventur + +Pflegt Lagerstaende, sucht Artikel ueber Code und bucht Inventurdifferenzen. + +### 3. Stammdatenpflege / Administration + +Pflegt Artikel, Artikelgruppen und Kundenstammdaten. + +### 4. Buchhaltung / Verrechnung + +Erstellt Rechnungen aus vorhandenen Lieferscheinen und oeffnet Rechnungs-PDFs. + +## User Stories + +### Dashboard + +- Als Mitarbeiter moechte ich ein Dashboard mit KPIs sehen, damit ich offene Lieferscheine, offene Rechnungen und kritische Lagerstaende sofort erkenne. +- Als Mitarbeiter moechte ich nach Zeitraum, Schwellenwert und Artikelgruppe filtern, damit ich nur fuer meinen aktuellen Blick relevante Kennzahlen sehe. +- Als Mitarbeiter moechte ich aus dem Dashboard direkt zu Artikeln, Kunden, Rechnungen oder Inventur springen, damit ich ohne Umwege weiterarbeiten kann. + +### Artikelverwaltung + +- Als Stammdatenpfleger moechte ich alle Artikel sehen und nach Name oder Code filtern, damit ich einen Artikel schnell finde. +- Als Stammdatenpfleger moechte ich einen neuen Artikel anlegen, damit er fuer Bestellungen und Inventur verfuegbar ist. +- Als Stammdatenpfleger moechte ich einen bestehenden Artikel bearbeiten, damit Preis, Einheit, Warengruppe oder Lagerlogik aktuell bleiben. +- Als Stammdatenpfleger moechte ich einen Artikel loeschen, damit veraltete Stammdaten entfernt werden koennen. +- Als Mitarbeiter moechte ich Artikelgruppen laden koennen, damit Artikel korrekt zugeordnet werden. +- Als Mitarbeiter moechte ich Artikel per CSV importieren koennen, damit groessere Stammdatenmengen schneller eingespielt werden. + +### Inventur + +- Als Lagermitarbeiter moechte ich einen Artikel per Code scannen oder eingeben, damit ich schnell den Sollbestand sehe. +- Als Lagermitarbeiter moechte ich den Istbestand erfassen und buchen, damit der Lagerstand korrigiert wird. +- Als Lagermitarbeiter moechte ich die letzte Reihe meiner Inventurbuchungen sehen, damit ich unmittelbar Rueckmeldung ueber meine Eingaben habe. + +### Kundenverwaltung + +- Als Sachbearbeiter moechte ich alle Kunden sehen und suchen, damit ich einen Kunden schnell auswaehlen kann. +- Als Sachbearbeiter moechte ich einen neuen Kunden anlegen, damit ich fuer ihn Bestellungen und Rechnungen erfassen kann. +- Als Sachbearbeiter moechte ich einen Kunden bearbeiten, damit Stammdaten und Standardrabatt aktuell bleiben. +- Als Sachbearbeiter moechte ich einen Kunden mit seinen Lieferscheinen und Rechnungen sehen, damit ich seinen gesamten Bearbeitungsstand an einem Ort habe. +- Als Sachbearbeiter moechte ich kundenbezogene Rabatte pflegen, damit Preise bei Auftraegen automatisch beruecksichtigt werden. + +### Auftrag / Lieferschein + +- Als Sachbearbeiter moechte ich fuer einen Kunden einen offenen Lieferschein aufbauen, damit ich Bestellpositionen laufend sammeln kann. +- Als Sachbearbeiter moechte ich beim Hinzufuegen eines Artikels nur den Code und die Menge brauchen, damit die Eingabe schnell bleibt. +- Als Sachbearbeiter moechte ich auch Freitextpositionen anlegen, damit nicht katalogisierte Leistungen oder Hinweise erfasst werden koennen. +- Als Sachbearbeiter moechte ich Positionen aendern oder loeschen, damit ein offener Lieferschein korrigierbar bleibt. +- Als Sachbearbeiter moechte ich Annotationen zu einem Lieferschein erfassen, damit Zusatzinformationen dokumentiert sind. +- Als Sachbearbeiter moechte ich ein Lieferschein-PDF oeffnen oder herunterladen, damit ich den Beleg nutzen oder pruefen kann. +- Als Sachbearbeiter moechte ich einen leeren offenen Lieferschein loeschen, damit Fehlanlagen wieder verschwinden. +- Als Sachbearbeiter moechte ich alle Lieferscheine sehen und auf offene eingrenzen, damit ich den Dokumentenstapel priorisieren kann. + +### Rechnung + +- Als Sachbearbeiter moechte ich auf Kundenseite mehrere offene Lieferscheine auswaehlen und daraus eine Rechnung erzeugen, damit die Verrechnung gesammelt erfolgen kann. +- Als Mitarbeiter moechte ich alle Rechnungen sehen, damit ich offene und abgeschlossene Rechnungen nachvollziehen kann. +- Als Mitarbeiter moechte ich eine Rechnungsdetailansicht oeffnen und das PDF herunterladen, damit ich den Beleg pruefen oder weitergeben kann. +- Als Mitarbeiter moechte ich ein Rechnungs-PDF bei Bedarf neu erzeugen koennen, damit fehlende Dateien wiederhergestellt werden koennen. + +### Authentifizierung + +- Als Benutzer moechte ich mich anmelden, damit ich nur berechtigten Zugriff auf das System habe. +- Dieser Workflow ist im Frontend vorbereitet, aber im aktuellen Gesamtsystem nicht lauffaehig, weil die benoetigten Backend-Endpunkte fehlen und keine Login-Route registriert ist. + +## Hauptworkflows + +## 1. Dashboard beobachten und weiter navigieren + +### Ziel + +Operativen Zustand des Systems schnell erfassen und direkt in den naechsten Arbeitsschritt springen. + +### Ablauf + +1. Benutzer oeffnet `/dashboard`. +2. System laedt `dashboard/summary`. +3. Benutzer filtert optional nach Tagen, Schwellenwert oder Artikelgruppe. +4. Benutzer springt aus KPI- oder Listenbereichen direkt zu: + - Artikeln + - Kunden + - Rechnungen + - Inventur + +### Ergebnis + +Dashboard ist ein Einstiegspunkt und Verteiler fuer Folgeprozesse. + +## 2. Artikel anlegen oder pflegen + +### Ziel + +Artikelstammdaten verfuegbar und korrekt halten. + +### Ablauf + +1. Benutzer oeffnet `/articles`. +2. System zeigt alle Artikel und erlaubt Filter nach Name und Code. +3. Benutzer oeffnet einen bestehenden Artikel oder navigiert zu einem neuen Artikel. +4. Benutzer pflegt Felder wie: + - Name + - Code + - Artikelnummer + - Lieferant + - Typ + - Preis + - Einheit + - Warengruppe + - `singlePos` + - `trackStock` + - `noDiscount` +5. Benutzer speichert oder loescht den Artikel. + +### Ergebnis + +Artikel stehen fuer Lager, Lieferschein und Rechnung bereit. + +## 3. Inventur buchen + +### Ziel + +Realen Lagerbestand gegen Sollbestand abgleichen und berichtigen. + +### Ablauf + +1. Benutzer oeffnet `/inventory`. +2. Benutzer scannt oder tippt einen Artikelcode ein. +3. System laedt den Artikel und zeigt den aktuellen Sollbestand. +4. Benutzer erfasst den Istbestand. +5. System berechnet die Differenz. +6. Benutzer bucht die Inventur. +7. System aktualisiert den Lagerstand und fuehrt die Buchung im Kurzprotokoll. + +### Ergebnis + +Bestand und Inventurhistorie werden fortgeschrieben. + +## 4. Kunden anlegen oder bearbeiten + +### Ziel + +Verrechenbare Kundenstammdaten pflegen. + +### Ablauf + +1. Benutzer oeffnet `/customers`. +2. System zeigt die Kundenliste mit Suche. +3. Benutzer erstellt einen neuen Kunden oder oeffnet einen bestehenden Datensatz. +4. Benutzer pflegt u. a.: + - Firmenname + - Vorname / Nachname + - E-Mail + - Kundennummer + - Telefonnummern + - Adresse + - UID + - Kundenrabatt +5. Benutzer speichert den Datensatz. + +### Ergebnis + +Kunden koennen fuer offene Lieferscheine und Rechnungen verwendet werden. + +## 5. Neue Bestellung bzw. offenen Lieferschein starten + +### Ziel + +Fuer einen Kunden einen offenen Arbeitsbeleg erzeugen oder fortsetzen. + +### Ablauf + +1. Benutzer oeffnet `/order/new`. +2. System zeigt eine Kundensuche. +3. Benutzer waehlt einen Kunden aus. +4. System versucht, einen offenen Lieferschein fuer diesen Kunden zu laden. +5. Falls keiner existiert, wird beim ersten Hinzufuegen einer Position ein neuer Lieferschein erzeugt. + +### Ergebnis + +Ein Kunde ist aktiv ausgewaehlt und ein offener Lieferschein steht zur Bearbeitung bereit. + +## 6. Artikelposition zu Lieferschein hinzufuegen + +### Ziel + +Standardartikel schnell auf einen offenen Lieferschein bringen. + +### Ablauf + +1. Benutzer arbeitet im `SlipsheetEditor`. +2. Benutzer gibt Artikelcode und Menge ein. +3. System laedt den Artikel per Code. +4. Benutzer bestaetigt das Hinzufuegen. +5. Wenn bereits ein offener Lieferschein existiert: + - Position wird auf bestehendem Lieferschein angelegt oder aktualisiert. +6. Wenn noch kein Lieferschein existiert: + - System erzeugt einen offenen Lieferschein und fuegt die erste Position direkt hinzu. + +### Ergebnis + +Der offene Lieferschein enthaelt die neue Artikelposition. + +## 7. Freitextposition zu Lieferschein hinzufuegen + +### Ziel + +Nicht katalogisierte Leistungen oder manuelle Positionen erfassen. + +### Ablauf + +1. Benutzer oeffnet im `SlipsheetEditor` den Bereich fuer Textpositionen. +2. Benutzer erfasst Text, Menge und Preis. +3. System legt die Position auf dem offenen Lieferschein an oder erzeugt vorher einen neuen offenen Lieferschein. + +### Ergebnis + +Auch manuelle Positionen koennen in Lieferschein und spaeter Rechnung einfliessen. + +## 8. Offenen Lieferschein korrigieren + +### Ziel + +Offene Belege waehrend der Bearbeitung anpassen. + +### Ablauf + +1. Benutzer oeffnet einen offenen Lieferschein direkt oder ueber die Kundendetailseite. +2. Benutzer aendert Mengen bestehender Positionen. +3. Benutzer loescht Positionen durch Setzen der Menge auf `0`. +4. Benutzer fuegt bei Bedarf Annotationen hinzu. +5. Benutzer laedt bei Bedarf das PDF herunter. +6. Ein leerer offener Lieferschein kann geloescht werden. + +### Ergebnis + +Der Lieferschein bleibt bis zur Verrechnung editierbar. + +## 9. Kundenansicht als Arbeitszentrale fuer Belege + +### Ziel + +Alle Belege eines Kunden an einer Stelle bearbeiten und verrechnen. + +### Ablauf + +1. Benutzer oeffnet `/customers/:id`. +2. System laedt: + - Kundendaten + - Lieferscheine des Kunden + - Rechnungen des Kunden +3. Benutzer kann: + - offene Lieferscheine filtern + - einen Lieferschein aktiv waehlen + - einen Lieferschein im Editor bearbeiten + - Lieferschein-PDF oeffnen + - Rechnungs-PDF oeffnen + - zu Rechnungsdetails springen + +### Ergebnis + +Kundenansicht ist der wichtigste operative Sammelpunkt fuer Auftrags- und Belegarbeit. + +## 10. Rechnung aus offenen Lieferscheinen erzeugen + +### Ziel + +Mehrere offene oder bearbeitete Lieferscheine gesammelt abrechnen. + +### Ablauf + +1. Benutzer oeffnet die Kundendetailansicht. +2. Benutzer waehlt offene Lieferscheine aus. +3. System summiert Anzahl und Gesamtwert der Auswahl. +4. Benutzer startet `Rechnung erstellen`. +5. Frontend ruft `POST /bills/generate` mit den ausgewaehlten Lieferschein-IDs auf. +6. Backend erzeugt die Rechnung und verknuepft die Lieferscheine. +7. Frontend aktualisiert Lieferschein- und Rechnungsliste. + +### Ergebnis + +Aus mehreren Lieferscheinen wird eine Rechnung. + +## 11. Rechnungen pruefen und PDF beziehen + +### Ziel + +Erzeugte Rechnungen einsehen und weiterverwenden. + +### Ablauf + +1. Benutzer oeffnet `/bills`. +2. System zeigt alle Rechnungen. +3. Benutzer oeffnet eine Rechnung ueber `/bills/:id`. +4. Benutzer laedt das Rechnungs-PDF herunter. +5. Falls die PDF-Datei fehlt, meldet das Frontend den Fehler und verweist implizit auf eine Neugenerierung. + +### Ergebnis + +Rechnungen sind nachvollziehbar und als PDF nutzbar. + +## Nebenworkflows und Querschnittslogik + +### 1. Statuslogik bei Lieferscheinen + +- Offene Lieferscheine sind aktiv bearbeitbar. +- Nicht mehr offene Lieferscheine gelten im UI als verrechnet oder abgeschlossen. +- Kundenansicht und Lieferscheinlisten arbeiten stark mit dieser Unterscheidung. + +### 2. Rabatte + +- Kundenspezifische Rabatte und artikelgruppenbezogene Rabatte sind im Datenmodell vorgesehen. +- Beim Hinzufuegen von Artikelpositionen werden Rabatte serverseitig auf Positionen geschrieben. +- Pflege eines Rabatts ist im Service vorgesehen; Loeschen ist im Frontend vorgesehen, aber im Backend aktuell nicht umgesetzt. + +### 3. PDF-Nutzung + +- Lieferscheine koennen direkt im Browser geoeffnet oder heruntergeladen werden. +- Rechnungen werden heruntergeladen; in der Kundendetailansicht werden sie in neuem Tab geoeffnet. + +## Nicht vollstaendig lauffaehige oder inkonsistente Workflows + +### 1. Login / Session + +- Frontend besitzt `AuthenticationService`, `authGuard` und `LoginComponent`. +- Aktuell fehlt: + - Route `/login` + - Backend-Endpunkt `POST /users/login` + - Backend-Endpunkt `GET /users/me/refresh` +- Daraus folgt: + - Auth-Workflow ist fachlich erkennbar, technisch aber nicht abgeschlossen. + +### 2. Kunde loeschen + +- Frontend besitzt `customerService.delete(...)`. +- Backend besitzt im aktuellen `CustomerController` keinen `DELETE /customers/:id`. +- Daraus folgt: + - Story erkennbar, Workflow derzeit nicht durchgaengig nutzbar. + +### 3. Rabatt loeschen + +- Frontend besitzt `deleteDiscount(...)`. +- Backend besitzt aktuell keinen passenden Delete-Endpunkt fuer Kundenrabatte. +- Daraus folgt: + - Rabattpflege ist nur teilweise umgesetzt. + +## Priorisierte Kernprozesse aus Produktsicht + +Wenn man das aktuelle Repo auf seine tragenden Geschaeftsprozesse reduziert, sind das diese vier: + +1. Artikel pflegen und verfuegbar machen +2. Lagerbestand per Inventur korrigieren +3. Fuer Kunden offene Lieferscheine aufbauen und bearbeiten +4. Aus Lieferscheinen Rechnungen erzeugen und PDFs bereitstellen + +Diese vier Prozesse sind der fachliche Kern der Software. diff --git a/apps/server-e2e/eslint.config.mjs b/apps/server-e2e/eslint.config.mjs new file mode 100644 index 0000000..b7f6277 --- /dev/null +++ b/apps/server-e2e/eslint.config.mjs @@ -0,0 +1,3 @@ +import baseConfig from '../../eslint.config.mjs'; + +export default [...baseConfig]; diff --git a/apps/server-e2e/jest.config.cts b/apps/server-e2e/jest.config.cts new file mode 100644 index 0000000..3da0016 --- /dev/null +++ b/apps/server-e2e/jest.config.cts @@ -0,0 +1,18 @@ +export default { + displayName: 'server-e2e', + preset: '../../jest.preset.js', + globalSetup: '/src/support/global-setup.ts', + globalTeardown: '/src/support/global-teardown.ts', + setupFiles: ['/src/support/test-setup.ts'], + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/server-e2e', +}; diff --git a/apps/server-e2e/project.json b/apps/server-e2e/project.json new file mode 100644 index 0000000..49a9e47 --- /dev/null +++ b/apps/server-e2e/project.json @@ -0,0 +1,17 @@ +{ + "name": "server-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "implicitDependencies": ["server"], + "targets": { + "e2e": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"], + "options": { + "jestConfig": "apps/server-e2e/jest.config.cts", + "passWithNoTests": true + }, + "dependsOn": ["server:build", "server:serve"] + } + } +} diff --git a/apps/server-e2e/src/server/api-prefix.spec.ts b/apps/server-e2e/src/server/api-prefix.spec.ts new file mode 100644 index 0000000..efffea8 --- /dev/null +++ b/apps/server-e2e/src/server/api-prefix.spec.ts @@ -0,0 +1,120 @@ +/** + * REQ-001 — Globaler API-Prefix + * + * Spec: test-req/REQ-001-global-api-prefix.md + * Source: apps/server/src/main.ts — app.setGlobalPrefix('api/v1') + * + * Voraussetzungen: + * - Server läuft auf PORT (default 3000, für dieses Projekt: 9000) + * - DB muss erreichbar sein (leere DB ist erlaubt) + * - Starten: PORT=9000 npx nx e2e server-e2e + * + * ACHTUNG: Alle Requests verwenden `validateStatus: () => true` damit + * axios bei 4xx/5xx nicht wirft und wir den Status direkt prüfen können. + */ + +import axios from 'axios'; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** Axios-Request der niemals wirft — gibt immer die Response zurück. */ +async function request( + method: 'get' | 'post' | 'put' | 'patch' | 'delete', + url: string, + data?: unknown, +) { + return axios.request({ + method, + url, + data, + validateStatus: () => true, // niemals Ausnahme auf 4xx/5xx + }); +} + +// --------------------------------------------------------------------------- +// REQ-001 — Globaler API-Prefix +// --------------------------------------------------------------------------- + +describe('REQ-001 — Globaler API-Prefix', () => { + // ------------------------------------------------------------------------- + // TC-001-001: Korrekter Prefix → Endpunkt erreichbar + // ------------------------------------------------------------------------- + describe('TC-001-001: GET /api/v1/articles — korrekter Prefix', () => { + it('gibt HTTP 200 zurück', async () => { + const res = await request('get', '/api/v1/articles'); + expect(res.status).toBe(200); + }); + + it('Body enthält success: true', async () => { + const res = await request('get', '/api/v1/articles'); + expect(res.data).toHaveProperty('success', true); + }); + + it('Body enthält Feld data als Array', async () => { + const res = await request('get', '/api/v1/articles'); + expect(res.data).toHaveProperty('data'); + expect(Array.isArray(res.data.data)).toBe(true); + }); + }); + + // ------------------------------------------------------------------------- + // TC-001-002: Kein Prefix → 404 + // ------------------------------------------------------------------------- + describe('TC-001-002: GET /articles — kein Prefix', () => { + it('gibt HTTP 404 zurück (Route nicht registriert)', async () => { + const res = await request('get', '/articles'); + expect(res.status).toBe(404); + }); + }); + + // ------------------------------------------------------------------------- + // TC-001-003: Falscher Prefix /api/articles — ohne Version + // ------------------------------------------------------------------------- + describe('TC-001-003: GET /api/articles — falscher Prefix (ohne Version)', () => { + it('gibt HTTP 404 zurück', async () => { + const res = await request('get', '/api/articles'); + expect(res.status).toBe(404); + }); + }); + + // ------------------------------------------------------------------------- + // TC-001-004: Falscher Prefix /v1/articles — ohne api/ + // ------------------------------------------------------------------------- + describe('TC-001-004: GET /v1/articles — falscher Prefix (ohne api/)', () => { + it('gibt HTTP 404 zurück', async () => { + const res = await request('get', '/v1/articles'); + expect(res.status).toBe(404); + }); + }); + + // ------------------------------------------------------------------------- + // TC-001-005: Falscher Prefix /api/v2/articles — falsche Version + // ------------------------------------------------------------------------- + describe('TC-001-005: GET /api/v2/articles — falsche Versionsnummer', () => { + it('gibt HTTP 404 zurück (keine zweite API-Version registriert)', async () => { + const res = await request('get', '/api/v2/articles'); + expect(res.status).toBe(404); + }); + }); + + // ------------------------------------------------------------------------- + // TC-001-006: POST unter korrektem Prefix — routing-seitig erreichbar + // Leerer Body erzeugt 400 (Validation) — das bestätigt, dass Routing OK ist. + // HTTP 404 wäre ein Routing-Fehler und würde den Test failen lassen. + // ------------------------------------------------------------------------- + describe('TC-001-006: POST /api/v1/customers — Routing funktioniert für POST', () => { + it('gibt NICHT HTTP 404 zurück (Routing ist korrekt)', async () => { + const res = await request('post', '/api/v1/customers', {}); + expect(res.status).not.toBe(404); + }); + + it('gibt HTTP 400 zurück (Validation schlägt an, nicht Routing)', async () => { + const res = await request('post', '/api/v1/customers', {}); + // ValidationPipe wirft 400 bei fehlendem Pflichtfeld customerNumber + // Wenn dieser Test fehlschlägt (z.B. 201), ist Validierung deaktiviert → Potential Defect + expect(res.status).toBe(400); + }); + }); +}); diff --git a/apps/server-e2e/src/server/cors.spec.ts b/apps/server-e2e/src/server/cors.spec.ts new file mode 100644 index 0000000..61ab156 --- /dev/null +++ b/apps/server-e2e/src/server/cors.spec.ts @@ -0,0 +1,242 @@ +/** + * REQ-005 — CORS Herkunftsbeschränkung [E2E] + * + * Spec: test-req/REQ-005-cors.md + * Source: apps/server/src/main.ts — app.enableCors({ origin, methods, credentials }) + * + * Alle Tests laufen gegen den laufenden Server. + * Voraussetzungen: PORT=9000 npx nx e2e server-e2e + * + * TC-Zuordnung: + * TC-005-001 — Default-Origin (http://localhost:4200) → ACAO-Header vorhanden + * TC-005-002 — credentials: true → ACAC-Header vorhanden + * TC-005-003 — OPTIONS-Preflight → CORS-Header + Status 204/200 + * TC-005-004 — OPTIONS-Preflight → ACAM-Header enthält alle konfigurierten Methoden + * TC-005-005 — Non-Allowed Origin → KEIN ACAO-Header für http://attacker.com + * TC-005-006 — Multi-Origin via CORS_ORIGIN ENV → BLOCKED (ENV-Setup fehlt) + * TC-005-007 — Request ohne Origin-Header → HTTP 200 (kein CORS-Block) + * + * Hinweise: + * - Axios gibt Response-Header in Kleinbuchstaben zurück (Node.js HTTP-Spezifikation): + * z.B. 'access-control-allow-origin', nicht 'Access-Control-Allow-Origin' + * - CORS-Enforcement findet browserseitig statt. Der Server sendet (oder sendet nicht) + * die CORS-Header — der Test prüft nur das Vorhandensein/Fehlen dieser Header. + * - TC-005-005: Der HTTP-Status kann trotzdem 200 sein; entscheidend ist das Fehlen + * des ACAO-Headers für nicht-erlaubte Ursprünge. + * + * Potential Defects (dokumentiert in test-req/REQ-005-cors.md, GAP-005-2 + GAP-005-3): + * - CORS_ORIGIN mit Leerzeichen nach Komma: split(',') ohne trim() → Mismatch + * - CORS_ORIGIN="" (leerer String): ?? wird nicht ausgelöst → [""] statt Default + */ + +import axios, { AxiosResponse } from 'axios'; + +// --------------------------------------------------------------------------- +// Helper — wirft niemals bei 4xx/5xx, sendet Origin-Header +// --------------------------------------------------------------------------- +async function req( + method: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'options', + url: string, + headers?: Record, + data?: unknown, +): Promise { + return axios.request({ method, url, headers, data, validateStatus: () => true }); +} + +// --------------------------------------------------------------------------- +// TC-005-001 — Default-Origin: ACAO-Header vorhanden +// --------------------------------------------------------------------------- + +describe('REQ-005 — TC-005-001: Default-Origin http://localhost:4200 → ACAO-Header', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/articles', { Origin: 'http://localhost:4200' }); + }); + + it('gibt HTTP 200 zurück', () => { + expect(res.status).toBe(200); + }); + + it('Response enthält Access-Control-Allow-Origin-Header', () => { + // Axios liefert Header-Namen in lowercase + expect(res.headers['access-control-allow-origin']).toBeDefined(); + }); + + it('Access-Control-Allow-Origin ist http://localhost:4200', () => { + // Evidenz: main.ts Zeile 15 — Default-Origin wenn CORS_ORIGIN nicht gesetzt + expect(res.headers['access-control-allow-origin']).toBe('http://localhost:4200'); + }); +}); + +// --------------------------------------------------------------------------- +// TC-005-002 — Credentials-Header: ACAC: true +// --------------------------------------------------------------------------- + +describe('REQ-005 — TC-005-002: credentials:true → Access-Control-Allow-Credentials: true', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/articles', { Origin: 'http://localhost:4200' }); + }); + + it('Response enthält Access-Control-Allow-Credentials-Header', () => { + expect(res.headers['access-control-allow-credentials']).toBeDefined(); + }); + + it('Access-Control-Allow-Credentials ist "true" (String im Header)', () => { + // HTTP-Header sind Strings — "true" nicht boolean true + // Evidenz: main.ts Zeile 17 — credentials: true → Express setzt den Header + expect(res.headers['access-control-allow-credentials']).toBe('true'); + }); +}); + +// --------------------------------------------------------------------------- +// TC-005-003 — OPTIONS-Preflight: CORS-Header + akzeptabler Status +// --------------------------------------------------------------------------- + +describe('REQ-005 — TC-005-003: OPTIONS-Preflight → CORS-Header + Status 204/200', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('options', '/api/v1/articles', { + Origin: 'http://localhost:4200', + 'Access-Control-Request-Method': 'POST', + 'Access-Control-Request-Headers': 'Content-Type', + }); + }); + + it('gibt HTTP 204 oder 200 zurück (kein 404, kein 403)', () => { + // NestJS/Express CORS-Middleware antwortet auf Preflight mit 204 (No Content) + // Manchmal auch 200 — beides ist valide + expect([200, 204]).toContain(res.status); + }); + + it('Response enthält Access-Control-Allow-Origin-Header', () => { + expect(res.headers['access-control-allow-origin']).toBe('http://localhost:4200'); + }); + + it('Response enthält Access-Control-Allow-Methods-Header', () => { + expect(res.headers['access-control-allow-methods']).toBeDefined(); + }); + + it('Access-Control-Allow-Methods enthält POST', () => { + const methods: string = res.headers['access-control-allow-methods'] ?? ''; + expect(methods.toUpperCase()).toContain('POST'); + }); +}); + +// --------------------------------------------------------------------------- +// TC-005-004 — OPTIONS-Preflight: Alle erlaubten Methoden im ACAM-Header +// --------------------------------------------------------------------------- + +describe('REQ-005 — TC-005-004: OPTIONS-Preflight → ACAM enthält alle konfigurierten Methoden', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('options', '/api/v1/articles', { + Origin: 'http://localhost:4200', + 'Access-Control-Request-Method': 'GET', + }); + }); + + it('ACAM-Header ist vorhanden', () => { + expect(res.headers['access-control-allow-methods']).toBeDefined(); + }); + + // Evidenz: main.ts Zeile 16 — methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] + it.each(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])( + 'ACAM enthält Methode: %s', + (method) => { + const allowed: string = res.headers['access-control-allow-methods'] ?? ''; + expect(allowed.toUpperCase()).toContain(method); + }, + ); +}); + +// --------------------------------------------------------------------------- +// TC-005-005 — Non-Allowed Origin: KEIN ACAO-Header +// --------------------------------------------------------------------------- + +describe('REQ-005 — TC-005-005: Non-Allowed Origin → kein ACAO-Header für http://attacker.com', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/articles', { Origin: 'http://attacker.com' }); + }); + + it( + 'Access-Control-Allow-Origin ist NICHT "http://attacker.com"' + + ' (Browser würde Request blockieren)', + () => { + // Express/NestJS CORS-Middleware sendet keinen ACAO-Header wenn Origin nicht erlaubt ist. + // Der HTTP-Status kann trotzdem 200 sein — CORS ist ein Browser-Mechanismus. + // Entscheidend ist das Fehlen des Headers (oder ein anderer Wert als der Angreifer-Origin). + const acao = res.headers['access-control-allow-origin']; + expect(acao).not.toBe('http://attacker.com'); + }, + ); + + it('ACAO-Header fehlt vollständig (kein wildcard, kein attacker.com)', () => { + // Falls '*' gesetzt wäre → credentials:true + origin:'*' ist ungültig (Browser-Fehler) + // → dieser Test würde auch bei falscher '*'-Konfiguration helfen + const acao = res.headers['access-control-allow-origin']; + expect(acao).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// TC-005-006 — Multi-Origin via CORS_ORIGIN ENV — BLOCKED +// --------------------------------------------------------------------------- + +describe('REQ-005 — TC-005-006: Multi-Origin via CORS_ORIGIN ENV [BLOCKED]', () => { + /** + * BLOCKED: Dieser Test erfordert, dass der Server mit einer spezifischen + * CORS_ORIGIN-Umgebungsvariable gestartet wurde (z.B. CORS_ORIGIN=http://app1.com,http://app2.com). + * + * Da die E2E-Tests gegen einen extern gestarteten Server laufen (PORT=9000), + * kann die ENV-Variable nicht innerhalb des Tests gesetzt werden. + * + * Blocker: Test-Setup-Strategie für ENV-Variablen ist nicht definiert. + * Quelle: test-req/REQ-005-cors.md — GAP-005-1 + * + * Wenn die Infrastruktur vorhanden ist (z.B. Docker mit ENV), können folgende + * Assertions eingesetzt werden: + * - GET /api/v1/articles mit Origin: http://app1.com → ACAO: http://app1.com + * - GET /api/v1/articles mit Origin: http://app2.com → ACAO: http://app2.com + */ + it.todo('TC-005-006a: CORS_ORIGIN=http://app1.com,http://app2.com → app1.com erlaubt'); + it.todo('TC-005-006b: CORS_ORIGIN=http://app1.com,http://app2.com → app2.com erlaubt'); +}); + +// --------------------------------------------------------------------------- +// TC-005-007 — Request ohne Origin-Header → HTTP 200 +// --------------------------------------------------------------------------- + +describe('REQ-005 — TC-005-007: Request ohne Origin-Header → HTTP 200 (kein CORS-Block)', () => { + let res: AxiosResponse; + + beforeAll(async () => { + // Kein Origin-Header → simuliert Server-zu-Server oder direkten curl-Aufruf + // CORS gilt nur für Browser-Cross-Origin-Requests + res = await req('get', '/api/v1/articles'); + }); + + it('gibt HTTP 200 zurück', () => { + expect(res.status).toBe(200); + }); + + it('success ist true', () => { + expect(res.data.success).toBe(true); + }); + + it('Response enthält Feld data', () => { + expect(res.data).toHaveProperty('data'); + }); + + it('KEIN ACAO-Header wenn kein Origin-Header gesendet wurde', () => { + // INFERIERT: CORS-Middleware setzt ACAO nur wenn Origin-Header vorhanden + // Requests ohne Origin sind nicht cross-origin → kein ACAO notwendig + expect(res.headers['access-control-allow-origin']).toBeUndefined(); + }); +}); diff --git a/apps/server-e2e/src/server/error-filter.spec.ts b/apps/server-e2e/src/server/error-filter.spec.ts new file mode 100644 index 0000000..c85fe87 --- /dev/null +++ b/apps/server-e2e/src/server/error-filter.spec.ts @@ -0,0 +1,192 @@ +/** + * REQ-004 — Global ErrorFilter [E2E] + * + * Spec: test-req/REQ-004-error-filter.md + * Source: apps/server/src/common/filters/errors.filter.ts + * apps/server/src/main.ts — app.useGlobalFilters(new ErrorFilter()) + * + * Alle Tests laufen gegen den laufenden Server. + * Voraussetzungen: PORT=9000 npx nx e2e server-e2e + * + * TC-Zuordnung (E2E-Ebene): + * TC-004-001 — NotFoundException (404) → vollständige ReE-Struktur + * TC-004-002 — BadRequestException (400) aus ValidationPipe → ReE + * TC-004-007 — Globaler Scope: ErrorFilter gilt für alle Controller (CustomerController) + * + * Unit-Tests (TC-004-003 bis TC-004-006) sind in: + * apps/server/src/common/filters/errors.filter.spec.ts + */ + +import axios, { AxiosResponse } from 'axios'; + +// --------------------------------------------------------------------------- +// Helper — wirft niemals bei 4xx/5xx +// --------------------------------------------------------------------------- +async function req( + method: 'get' | 'post' | 'put' | 'patch' | 'delete', + url: string, + data?: unknown, +): Promise { + return axios.request({ method, url, data, validateStatus: () => true }); +} + +// --------------------------------------------------------------------------- +// TC-004-001 — HttpException: NotFoundException → ReE 404 +// --------------------------------------------------------------------------- + +describe('REQ-004 — TC-004-001: NotFoundException → ReE mit statusCode 404', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/articles/99999'); + }); + + it('gibt HTTP 404 zurück', () => { + expect(res.status).toBe(404); + }); + + it('success ist false (boolean)', () => { + expect(res.data.success).toBe(false); + expect(typeof res.data.success).toBe('boolean'); + }); + + it('statusCode ist 404 (number)', () => { + expect(res.data.statusCode).toBe(404); + expect(typeof res.data.statusCode).toBe('number'); + }); + + it('message ist ein Array', () => { + expect(Array.isArray(res.data.message)).toBe(true); + }); + + it('message hat mindestens 1 Element', () => { + expect(res.data.message.length).toBeGreaterThanOrEqual(1); + }); + + it('jedes message-Element ist ein String', () => { + (res.data.message as unknown[]).forEach((m) => { + expect(typeof m).toBe('string'); + }); + }); + + it('error ist ein nicht-leerer String', () => { + expect(typeof res.data.error).toBe('string'); + expect(res.data.error.length).toBeGreaterThan(0); + }); + + it('Body enthält KEIN Feld data (ReE hat kein data-Feld)', () => { + // ReE.FromData() setzt nur success, statusCode, message, error + // kein data-Feld → wäre Defekt wenn vorhanden + expect(res.data.data).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// TC-004-002 — BadRequestException (ValidationPipe) → ReE 400 +// --------------------------------------------------------------------------- + +describe('REQ-004 — TC-004-002: BadRequestException (ValidationPipe) → ReE 400', () => { + let res: AxiosResponse; + + beforeAll(async () => { + // POST mit leerem Body → ValidationPipe wirft BadRequestException + // ErrorFilter fängt sie ab und formatiert als ReE + res = await req('post', '/api/v1/articles', {}); + }); + + it('gibt HTTP 400 zurück', () => { + expect(res.status).toBe(400); + }); + + it('success ist false', () => { + expect(res.data.success).toBe(false); + }); + + it('statusCode ist 400 (number)', () => { + expect(res.data.statusCode).toBe(400); + expect(typeof res.data.statusCode).toBe('number'); + }); + + it('message ist ein Array (ValidationPipe erzeugt Array mit einem Eintrag pro Constraint)', () => { + expect(Array.isArray(res.data.message)).toBe(true); + expect(res.data.message.length).toBeGreaterThanOrEqual(1); + }); + + it('jedes message-Element ist ein String (kein Array-in-Array = kein double-wrap)', () => { + // Beweist TC-004-003: message-Array wird nicht erneut eingewickelt + (res.data.message as unknown[]).forEach((m) => { + expect(typeof m).toBe('string'); + }); + }); + + it('error ist ein nicht-leerer String', () => { + expect(typeof res.data.error).toBe('string'); + expect(res.data.error.length).toBeGreaterThan(0); + }); +}); + +// --------------------------------------------------------------------------- +// TC-004-007 — Globaler Scope: ErrorFilter gilt für ALLE Controller +// --------------------------------------------------------------------------- + +describe('REQ-004 — TC-004-007: Globaler Scope — ErrorFilter gilt für CustomerController', () => { + // Beweis: app.useGlobalFilters(new ErrorFilter()) registriert den Filter + // für alle Controller, nicht nur für einen spezifischen. + // CustomerController ist ein anderer Controller als ArticleController. + + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/customers/99999'); + }); + + it('gibt HTTP 404 zurück', () => { + expect(res.status).toBe(404); + }); + + it('success ist false — ErrorFilter greift auch im CustomerController', () => { + expect(res.data.success).toBe(false); + }); + + it('statusCode ist 404', () => { + expect(res.data.statusCode).toBe(404); + }); + + it('message ist ein Array', () => { + expect(Array.isArray(res.data.message)).toBe(true); + }); + + it('error ist ein nicht-leerer String', () => { + expect(typeof res.data.error).toBe('string'); + expect(res.data.error.length).toBeGreaterThan(0); + }); + + it('Body enthält KEIN Feld data', () => { + expect(res.data.data).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// REQ-004 — ReE-Struktur-Vollständigkeit (alle Pflichtfelder vorhanden) +// --------------------------------------------------------------------------- + +describe('REQ-004 — ReE-Vollständigkeit: alle 4 Pflichtfelder bei jedem Fehler', () => { + // Prüft dass kein Feld aus dem ReE-Format fehlt (success, statusCode, message, error) + // Basiert auf ReE.FromData(statusCode, name, message) aus res.model.ts + + it('404-Response hat alle 4 ReE-Felder: success, statusCode, message, error', async () => { + const res = await req('get', '/api/v1/articles/99999'); + expect(res.data).toHaveProperty('success'); + expect(res.data).toHaveProperty('statusCode'); + expect(res.data).toHaveProperty('message'); + expect(res.data).toHaveProperty('error'); + }); + + it('400-Response hat alle 4 ReE-Felder: success, statusCode, message, error', async () => { + const res = await req('post', '/api/v1/articles', {}); + expect(res.data).toHaveProperty('success'); + expect(res.data).toHaveProperty('statusCode'); + expect(res.data).toHaveProperty('message'); + expect(res.data).toHaveProperty('error'); + }); +}); diff --git a/apps/server-e2e/src/server/response-wrapper.spec.ts b/apps/server-e2e/src/server/response-wrapper.spec.ts new file mode 100644 index 0000000..963f012 --- /dev/null +++ b/apps/server-e2e/src/server/response-wrapper.spec.ts @@ -0,0 +1,254 @@ +/** + * REQ-002 — Response-Wrapper (ReS / ReE) [E2E] + * + * Spec: test-req/REQ-002-response-wrapper.md + * Source: apps/server/src/common/res.model.ts + * + * Testet das äußere Response-Format (ReS / ReE) auf API-Ebene. + * TC-002-006 (non-HttpException → 500) ist in der Unit-Test-Datei: + * apps/server/src/common/filters/errors.filter.spec.ts + * + * Voraussetzungen: + * PORT=9000 npx nx e2e server-e2e + * + * Deviation von Spec (TC-002-002): + * Spec nennt DELETE /api/v1/articles/:id. Da ArticleCreate eine + * articleGroup-Referenz (FK) benötigt, wird stattdessen der + * Customer-DELETE-Endpunkt verwendet. Beide Controller geben + * ReS.FromData(null) zurück — das zu prüfende Verhalten ist identisch. + */ + +import axios, { AxiosResponse } from 'axios'; + +// --------------------------------------------------------------------------- +// Helper — wirft niemals bei 4xx/5xx +// --------------------------------------------------------------------------- +async function req( + method: 'get' | 'post' | 'put' | 'patch' | 'delete', + url: string, + data?: unknown, +): Promise { + return axios.request({ method, url, data, validateStatus: () => true }); +} + +// --------------------------------------------------------------------------- +// REQ-002 — ReS (Success-Wrapper) +// --------------------------------------------------------------------------- + +describe('REQ-002 — ReS Success-Wrapper', () => { + // ------------------------------------------------------------------------- + // TC-002-001: Success-Response Grundstruktur + // ------------------------------------------------------------------------- + describe('TC-002-001: GET /api/v1/articles — Success-Struktur', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/articles'); + }); + + it('gibt HTTP 200 zurück', () => { + expect(res.status).toBe(200); + }); + + it('Body ist gültiges JSON-Objekt', () => { + expect(typeof res.data).toBe('object'); + expect(res.data).not.toBeNull(); + }); + + it('enthält success: true (boolean)', () => { + expect(res.data.success).toBe(true); + expect(typeof res.data.success).toBe('boolean'); + }); + + it('enthält Feld data als Array', () => { + expect(res.data).toHaveProperty('data'); + expect(Array.isArray(res.data.data)).toBe(true); + }); + }); + + // ------------------------------------------------------------------------- + // TC-002-002: data ist null nach DELETE + // + // Deviation: Spec nennt Article-DELETE. Hier wird Customer-DELETE verwendet + // da Article-Create eine articleGroup-FK benötigt die u.U. nicht existiert. + // Das geprüfte Verhalten (ReS.FromData(null)) ist identisch. + // ------------------------------------------------------------------------- + describe('TC-002-002: DELETE — data: null im Response', () => { + let testCustomerId: number | null = null; + + beforeAll(async () => { + // Erstelle einen Testkunden — customerNumber ist einziges Pflichtfeld + const create = await req('post', '/api/v1/customers', { + customerNumber: `REQ002-TC002-${Date.now()}`, + }); + if (create.status === 201 && create.data?.data?.id) { + testCustomerId = create.data.data.id; + } + }); + + it('Setup hat einen Kunden erstellt', () => { + // Schlägt hier der Test fehl, ist das Setup gebrochen — nicht TC-002-002 + expect(testCustomerId).not.toBeNull(); + }); + + it('gibt HTTP 200 zurück', async () => { + if (!testCustomerId) return; // Abhängig von Setup + const res = await req('delete', `/api/v1/customers/${testCustomerId}`); + expect(res.status).toBe(200); + }); + + it('Body enthält success: true', async () => { + // Eigene DELETE-Anfrage mit neuem Kunden um Idempotenz zu garantieren + const create = await req('post', '/api/v1/customers', { + customerNumber: `REQ002-TC002b-${Date.now()}`, + }); + const id: number = create.data?.data?.id; + expect(id).toBeDefined(); + + const res = await req('delete', `/api/v1/customers/${id}`); + expect(res.data.success).toBe(true); + }); + + it('Body.data ist null', async () => { + const create = await req('post', '/api/v1/customers', { + customerNumber: `REQ002-TC002c-${Date.now()}`, + }); + const id: number = create.data?.data?.id; + expect(id).toBeDefined(); + + const res = await req('delete', `/api/v1/customers/${id}`); + + // Potential Defect: ClassSerializerInterceptor könnte null-Felder + // aus @Expose()-Properties herausfiltern. Wenn data fehlt statt null + // ist → ist das ein Defekt im Code, nicht im Test. + expect(res.data.data).toBeNull(); + }); + }); + + // ------------------------------------------------------------------------- + // TC-002-007: Keine ReE-Felder in Success-Response + // ------------------------------------------------------------------------- + describe('TC-002-007: Success-Response enthält keine Error-Felder', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/articles'); + }); + + it('enthält KEIN Feld statusCode', () => { + expect(res.data.statusCode).toBeUndefined(); + }); + + it('enthält KEIN Feld error', () => { + expect(res.data.error).toBeUndefined(); + }); + + it('enthält KEIN Feld message', () => { + expect(res.data.message).toBeUndefined(); + }); + }); +}); + +// --------------------------------------------------------------------------- +// REQ-002 — ReE (Error-Wrapper) +// --------------------------------------------------------------------------- + +describe('REQ-002 — ReE Error-Wrapper', () => { + // ------------------------------------------------------------------------- + // TC-002-003: 404 ReE Grundstruktur + // ------------------------------------------------------------------------- + describe('TC-002-003: GET /api/v1/articles/99999 — 404 ReE-Struktur', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/articles/99999'); + }); + + it('gibt HTTP 404 zurück', () => { + expect(res.status).toBe(404); + }); + + it('enthält success: false (boolean)', () => { + expect(res.data.success).toBe(false); + expect(typeof res.data.success).toBe('boolean'); + }); + + it('enthält statusCode: 404 (number)', () => { + expect(res.data.statusCode).toBe(404); + expect(typeof res.data.statusCode).toBe('number'); + }); + + it('enthält message als Array mit mindestens 1 Element', () => { + expect(Array.isArray(res.data.message)).toBe(true); + expect(res.data.message.length).toBeGreaterThanOrEqual(1); + }); + + it('jedes message-Element ist ein String', () => { + res.data.message.forEach((m: unknown) => { + expect(typeof m).toBe('string'); + }); + }); + + it('enthält error als String (nicht leer)', () => { + expect(typeof res.data.error).toBe('string'); + expect(res.data.error.length).toBeGreaterThan(0); + }); + + it('enthält KEIN Feld data', () => { + // ReE-Klasse hat kein data-Feld — wäre ein Defekt wenn vorhanden + expect(res.data.data).toBeUndefined(); + }); + }); + + // ------------------------------------------------------------------------- + // TC-002-004: message ist immer Array (nie String) + // ------------------------------------------------------------------------- + describe('TC-002-004: message ist immer Array, nie String', () => { + it('message bei 404 ist Array', async () => { + const res = await req('get', '/api/v1/articles/99999'); + expect(Array.isArray(res.data.message)).toBe(true); + // Explizit kein String + expect(typeof res.data.message).not.toBe('string'); + }); + + it('message bei 400 (Customers 404) ist Array', async () => { + const res = await req('get', '/api/v1/customers/99999'); + expect(Array.isArray(res.data.message)).toBe(true); + }); + }); + + // ------------------------------------------------------------------------- + // TC-002-005: ValidationPipe-Fehler → message Array mit mehreren Elementen + // ------------------------------------------------------------------------- + describe('TC-002-005: POST /api/v1/articles mit {} — ValidationPipe message[]', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('post', '/api/v1/articles', {}); + }); + + it('gibt HTTP 400 zurück', () => { + expect(res.status).toBe(400); + }); + + it('success ist false', () => { + expect(res.data.success).toBe(false); + }); + + it('message ist Array', () => { + expect(Array.isArray(res.data.message)).toBe(true); + }); + + it('message hat mindestens 1 Element (ein Constraint pro fehlendem Pflichtfeld)', () => { + // CreateArticleDto hat mehrere Pflichtfelder (name, code, price, type, unit, artNumber, articleGroup) + // Erwartung: mindestens 1 Message. Wenn ValidationPipe korrekt konfiguriert: mehrere. + expect(res.data.message.length).toBeGreaterThanOrEqual(1); + }); + + it('jedes message-Element ist ein String', () => { + res.data.message.forEach((m: unknown) => { + expect(typeof m).toBe('string'); + }); + }); + }); +}); diff --git a/apps/server-e2e/src/server/server.spec.ts b/apps/server-e2e/src/server/server.spec.ts new file mode 100644 index 0000000..51717c7 --- /dev/null +++ b/apps/server-e2e/src/server/server.spec.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +describe('GET /', () => { + it('should return a message', async () => { + const res = await axios.get(`/`); + + expect(res.status).toBe(200); + expect(res.data).toEqual({ message: 'Hello API' }); + }); +}); diff --git a/apps/server-e2e/src/server/validation-pipe.spec.ts b/apps/server-e2e/src/server/validation-pipe.spec.ts new file mode 100644 index 0000000..b94e100 --- /dev/null +++ b/apps/server-e2e/src/server/validation-pipe.spec.ts @@ -0,0 +1,371 @@ +/** + * REQ-003 — Global ValidationPipe (whitelist + transform) [E2E] + * + * Spec: test-req/REQ-003-validation-pipe.md + * Source: apps/server/src/main.ts — ValidationPipe({ whitelist: true, transform: true }) + * + * Alle Tests laufen gegen den laufenden Server. + * Voraussetzungen: PORT=9000 npx nx e2e server-e2e + * + * Hinweise: + * - TC-003-001 erstellt einen Kunden. CustomerController hat kein DELETE → + * timestamp-basierte customerNumber, um Kollisionen zwischen Testläufen zu vermeiden. + * - TC-003-005 prüft Transform indirekt: korrekte 404-Antwort beweist, dass + * die ID als Number verarbeitet wurde. + * + * Potential Defect (dokumentiert, Test NICHT gebogen): + * - CreateArticleDto.price hat @IsNotEmpty() aber kein @IsNumber(). Preis "abc" + * (String) würde deshalb keine 400 erzeugen → Matrix-Row "Typfehler" kann failen. + */ + +import axios, { AxiosResponse } from 'axios'; + +// --------------------------------------------------------------------------- +// Helper — wirft niemals bei 4xx/5xx +// --------------------------------------------------------------------------- +async function req( + method: 'get' | 'post' | 'put' | 'patch' | 'delete', + url: string, + data?: unknown, +): Promise { + return axios.request({ method, url, data, validateStatus: () => true }); +} + +// Vollständiger, valider Article-Body (ohne name) für Pflichtfeld-Tests +const ARTICLE_BODY_WITHOUT_NAME = { + code: 'REQ003-NONAME', + price: 9.99, + type: 'Test', + unit: 'Stk', + artNumber: 'REQ003-001', + articleGroup: { id: 1 }, +}; + +// --------------------------------------------------------------------------- +// REQ-003a — Whitelist: Extra-Felder werden still entfernt +// --------------------------------------------------------------------------- + +describe('REQ-003a — Whitelist: Extra-Felder werden still entfernt', () => { + // ------------------------------------------------------------------------- + // TC-003-001: POST mit bekannten + unbekannten Feldern → 201, kein Extra-Feld + // ------------------------------------------------------------------------- + describe('TC-003-001: POST /api/v1/customers — unbekannte Felder silent strip', () => { + const testCustomerNumber = `REQ003-TC001-${Date.now()}`; + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('post', '/api/v1/customers', { + customerNumber: testCustomerNumber, + hackerField: 'sollte-ignoriert-werden', + anotherUnknown: 12345, + }); + }); + + it('gibt HTTP 201 zurück (kein 400 wegen Extra-Feldern)', () => { + expect(res.status).toBe(201); + }); + + it('success ist true', () => { + expect(res.data.success).toBe(true); + }); + + it('bekannte Felder sind erhalten: customerNumber', () => { + expect(res.data.data.customerNumber).toBe(testCustomerNumber); + }); + + it('unbekanntes Feld hackerField ist NICHT in der Response', () => { + expect(res.data.data?.hackerField).toBeUndefined(); + }); + + it('unbekanntes Feld anotherUnknown ist NICHT in der Response', () => { + expect(res.data.data?.anotherUnknown).toBeUndefined(); + }); + }); + + // ------------------------------------------------------------------------- + // TC-003-002: POST mit ausschließlich unbekannten Feldern → 400 + // Whitelist entfernt alle Felder → customerNumber fehlt → Validation 400 + // ------------------------------------------------------------------------- + describe('TC-003-002: POST /api/v1/customers — nur unbekannte Felder → 400', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('post', '/api/v1/customers', { unknownField: 'x', anotherField: 42 }); + }); + + it('gibt HTTP 400 zurück', () => { + expect(res.status).toBe(400); + }); + + it('success ist false', () => { + expect(res.data.success).toBe(false); + }); + + it('message ist ein Array', () => { + expect(Array.isArray(res.data.message)).toBe(true); + }); + + it('message enthält Hinweis auf customerNumber (Pflichtfeld)', () => { + // NestJS erzeugt eine Message pro Constraint-Verletzung + // @IsNotEmpty() auf customerNumber → "customerNumber should not be empty" + const messages: string[] = res.data.message; + const mentionsCustomerNumber = messages.some( + (m) => m.toLowerCase().includes('customernumber'), + ); + expect(mentionsCustomerNumber).toBe(true); + }); + }); +}); + +// --------------------------------------------------------------------------- +// REQ-003b — Pflichtfeld-Validierung +// --------------------------------------------------------------------------- + +describe('REQ-003b — Pflichtfeld-Validierung', () => { + // ------------------------------------------------------------------------- + // TC-003-003: POST /articles ohne name → 400 + // ------------------------------------------------------------------------- + describe('TC-003-003: POST /api/v1/articles ohne Pflichtfeld name → 400', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('post', '/api/v1/articles', ARTICLE_BODY_WITHOUT_NAME); + }); + + it('gibt HTTP 400 zurück', () => { + expect(res.status).toBe(400); + }); + + it('success ist false', () => { + expect(res.data.success).toBe(false); + }); + + it('message ist ein Array', () => { + expect(Array.isArray(res.data.message)).toBe(true); + }); + + it('message enthält Hinweis auf das fehlende Feld name', () => { + // @IsNotEmpty() auf CreateArticleDto.name → + // NestJS erzeugt z.B. "name should not be empty" + const messages: string[] = res.data.message; + const mentionsName = messages.some((m) => m.toLowerCase().includes('name')); + expect(mentionsName).toBe(true); + }); + }); + + // ------------------------------------------------------------------------- + // TC-003-004: POST /articles mit leerem Body → 400 mit mehreren Messages + // ------------------------------------------------------------------------- + describe('TC-003-004: POST /api/v1/articles mit {} → 400 + mehrere Fehlermeldungen', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('post', '/api/v1/articles', {}); + }); + + it('gibt HTTP 400 zurück', () => { + expect(res.status).toBe(400); + }); + + it('success ist false', () => { + expect(res.data.success).toBe(false); + }); + + it('message ist ein Array', () => { + expect(Array.isArray(res.data.message)).toBe(true); + }); + + it('message enthält mehr als ein Element (je ein Hinweis pro fehlendem Pflichtfeld)', () => { + // CreateArticleDto hat 7 Pflichtfelder (@IsNotEmpty()): + // name, code, price, type, unit, artNumber, articleGroup + // Mindestens 2 Messages werden erwartet. + expect(res.data.message.length).toBeGreaterThan(1); + }); + + it('jedes message-Element ist ein String', () => { + (res.data.message as unknown[]).forEach((m) => { + expect(typeof m).toBe('string'); + }); + }); + }); +}); + +// --------------------------------------------------------------------------- +// REQ-003c — Nested DTO Validierung +// --------------------------------------------------------------------------- + +describe('REQ-003c — Nested DTO Validierung', () => { + // ------------------------------------------------------------------------- + // TC-003-007: POST /articles mit articleGroup: {} (id fehlt) → 400 + // ------------------------------------------------------------------------- + describe('TC-003-007: POST /api/v1/articles mit leerer articleGroup → 400', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('post', '/api/v1/articles', { + name: 'TestArtikel', + code: 'REQ003-NESTED', + price: 9.99, + type: 'Test', + unit: 'Stk', + artNumber: 'REQ003-002', + articleGroup: {}, // id fehlt absichtlich + }); + }); + + it('gibt HTTP 400 zurück', () => { + expect(res.status).toBe(400); + }); + + it('success ist false', () => { + expect(res.data.success).toBe(false); + }); + + it('message ist ein Array', () => { + expect(Array.isArray(res.data.message)).toBe(true); + }); + + it('message enthält Hinweis auf articleGroup-Verschachtelung', () => { + // NestJS ValidateNested + @Type → Fehler sind z.B.: + // "articleGroup.id must be a number conforming to the specified constraints" + // "articleGroup.id should not be empty" + const messages: string[] = res.data.message; + const mentionsArticleGroup = messages.some((m) => + m.toLowerCase().includes('articlegroup'), + ); + expect(mentionsArticleGroup).toBe(true); + }); + }); +}); + +// --------------------------------------------------------------------------- +// REQ-003d — Transform: Path-Param & Query-Param +// --------------------------------------------------------------------------- + +describe('REQ-003d — Transform: Typen-Konvertierung', () => { + // ------------------------------------------------------------------------- + // TC-003-005: GET /api/v1/articles/:id — Path-Param String → Number + // + // Indirekter Test: Eine valide numerische ID im Pfad muss als Number verarbeitet + // werden. Ergebnis 404 (nicht gefunden) beweist, dass die ID korrekt als Number + // an den Service übergeben wurde und eine DB-Abfrage stattfand. + // Ein Ergebnis von 500 oder ein TypeORM-String-Fehler würde bedeuten, dass + // der Transform nicht funktioniert hat. + // ------------------------------------------------------------------------- + describe('TC-003-005: GET /api/v1/articles/99999 — Path-Param wird als Number verarbeitet', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/articles/99999'); + }); + + it('gibt HTTP 404 zurück (kein 500 oder 400 durch Typ-Fehler)', () => { + // 404 beweist: ID wurde als Number verarbeitet, Service hat DB abgefragt, + // Artikel nicht gefunden → NotFoundException → 404 + // 500 würde auf Transform-Fehler oder DB-Typ-Mismatch hinweisen + expect(res.status).toBe(404); + }); + + it('gibt NICHT HTTP 500 zurück', () => { + expect(res.status).not.toBe(500); + }); + + it('gibt NICHT HTTP 400 zurück', () => { + expect(res.status).not.toBe(400); + }); + + it('Response ist im ReE-Format (success: false)', () => { + expect(res.data.success).toBe(false); + }); + }); + + // ------------------------------------------------------------------------- + // TC-003-006: GET /api/v1/dashboard/summary?days=30 — Query-Param → Number + // + // @Type(() => Number) auf DashboardSummaryQueryDto.days transformiert den + // Query-String "30" zu Number 30. @IsInt + @Min(1) validiert dann den Number. + // Beweis: HTTP 200 (kein 400 wegen Typ-Fehler) + filters.days als Number. + // ------------------------------------------------------------------------- + describe('TC-003-006: GET /api/v1/dashboard/summary?days=30 — Query-Param als Number', () => { + let res: AxiosResponse; + + beforeAll(async () => { + res = await req('get', '/api/v1/dashboard/summary?days=30'); + }); + + it('gibt HTTP 200 zurück (kein 400 durch Typ-Mismatch)', () => { + // @IsInt() würde 400 geben wenn "30" als String ankäme und nicht transformiert wird + expect(res.status).toBe(200); + }); + + it('success ist true', () => { + expect(res.data.success).toBe(true); + }); + + it('Response enthält Feld data', () => { + expect(res.data).toHaveProperty('data'); + }); + + it('data.filters.days ist 30 als Number (INFERIERT: Derived-from-code)', () => { + // DashboardService gibt filters-Objekt zurück. + // Wenn dieser Test failt weil filters nicht in data ist: + // → Endpoint-Response-Struktur hat sich geändert (nicht Transform-Bug) + // Wenn filters.days === "30" (String): → Transform hat nicht funktioniert → Potential Defect + const filtersDay = res.data?.data?.filters?.days; + if (filtersDay !== undefined) { + expect(typeof filtersDay).toBe('number'); + expect(filtersDay).toBe(30); + } else { + // filters.days nicht im Response → kann nicht verifiziert werden + // Test gilt als nicht-anwendbar für diese Assertion (skip via pass) + // Primäre Assertion (HTTP 200) oben gilt weiterhin + } + }); + + it('gibt NICHT HTTP 400 zurück wenn days=0 (ungültiger Wert < @Min(1))', async () => { + // Zusatztest: days=0 sollte 400 geben (@Min(1)) + // Das beweist, dass der @IsInt() + @Min(1) Validator greift + // und damit der Transform korrekt zu Number konvertiert hat + const invalid = await req('get', '/api/v1/dashboard/summary?days=0'); + expect(invalid.status).toBe(400); + }); + }); +}); + +// --------------------------------------------------------------------------- +// REQ-003e — Potential Defect Dokumentation (kein Greenwashing) +// --------------------------------------------------------------------------- + +describe('REQ-003e — Potential Defect: price @IsNotEmpty() ohne @IsNumber()', () => { + /** + * Potential Defect: + * CreateArticleDto.price ist mit @IsNotEmpty() annotiert aber NICHT mit @IsNumber(). + * Das bedeutet: price: "nicht-eine-zahl" (String) würde die Validierung bestehen, + * weil es nicht leer ist. Der Typfehler-Test aus der Matrix kann daher fehlschlagen. + * + * Quelle: apps/server/src/models/article/dto/create-article.dto.ts Zeile 25-26 + * Evidenz: @IsNotEmpty() auf price ohne @IsNumber() + * + * Dieser Test dokumentiert das tatsächliche Verhalten OHNE Anpassung der Erwartung. + * Wenn der Test grün wird (400 erhalten), wurde der Defekt behoben. + * Wenn der Test rot wird (kein 400), ist der Defekt bestätigt. + */ + it('[POTENTIAL DEFECT] POST /articles mit price: "abc" gibt HTTP 400 zurück', async () => { + const res = await req('post', '/api/v1/articles', { + name: 'TestArtikel', + code: 'REQ003-PRICETEST', + price: 'nicht-eine-zahl', // String statt Number + type: 'Test', + unit: 'Stk', + artNumber: 'REQ003-003', + articleGroup: { id: 1 }, + }); + + // Erwartetes Verhalten laut REQ-003: Typfehler → 400 + // Tatsächliches Verhalten: UNKLAR (kein @IsNumber() im DTO) + // Test wird als Potential Defect markiert: + // - Falls 400 → DTO wurde korrigiert oder TypeORM fängt es ab + // - Falls 201 → Defekt bestätigt: String-Price wird akzeptiert + expect(res.status).toBe(400); + }); +}); diff --git a/apps/server-e2e/src/support/global-setup.ts b/apps/server-e2e/src/support/global-setup.ts new file mode 100644 index 0000000..76a5879 --- /dev/null +++ b/apps/server-e2e/src/support/global-setup.ts @@ -0,0 +1,16 @@ +import { waitForPortOpen } from '@nx/node/utils'; + +/* eslint-disable */ +var __TEARDOWN_MESSAGE__: string; + +module.exports = async function () { + // Start services that that the app needs to run (e.g. database, docker-compose, etc.). + console.log('\nSetting up...\n'); + + const host = process.env.HOST ?? 'localhost'; + const port = process.env.PORT ? Number(process.env.PORT) : 3000; + await waitForPortOpen(port, { host }); + + // Hint: Use `globalThis` to pass variables to global teardown. + globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n'; +}; diff --git a/apps/server-e2e/src/support/global-teardown.ts b/apps/server-e2e/src/support/global-teardown.ts new file mode 100644 index 0000000..a28dd11 --- /dev/null +++ b/apps/server-e2e/src/support/global-teardown.ts @@ -0,0 +1,10 @@ +import { killPort } from '@nx/node/utils'; +/* eslint-disable */ + +module.exports = async function () { + // Put clean up logic here (e.g. stopping services, docker-compose, etc.). + // Hint: `globalThis` is shared between setup and teardown. + const port = process.env.PORT ? Number(process.env.PORT) : 3000; + await killPort(port); + console.log(globalThis.__TEARDOWN_MESSAGE__); +}; diff --git a/apps/server-e2e/src/support/test-setup.ts b/apps/server-e2e/src/support/test-setup.ts new file mode 100644 index 0000000..c185541 --- /dev/null +++ b/apps/server-e2e/src/support/test-setup.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +import axios from 'axios'; + +module.exports = async function () { + // Configure axios for tests to use. + const host = process.env.HOST ?? 'localhost'; + const port = process.env.PORT ?? '3000'; + axios.defaults.baseURL = `http://${host}:${port}`; +}; diff --git a/apps/server-e2e/tsconfig.json b/apps/server-e2e/tsconfig.json new file mode 100644 index 0000000..ed633e1 --- /dev/null +++ b/apps/server-e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/apps/server-e2e/tsconfig.spec.json b/apps/server-e2e/tsconfig.spec.json new file mode 100644 index 0000000..d7f9cf2 --- /dev/null +++ b/apps/server-e2e/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.ts"] +} diff --git a/apps/server/.env.example b/apps/server/.env.example new file mode 100644 index 0000000..24bf855 --- /dev/null +++ b/apps/server/.env.example @@ -0,0 +1,14 @@ +# Application +NODE_ENV=development +APP_PORT=3000 +CORS_ORIGIN=http://localhost:4200 + +# SQLite Database +SQLITE_PATH=sim.db +SQLITE_RUN_MIGRATION=false +SQLITE_RUN_SYNCHRONIZE=true +SQLITE_ENTITIES=dist/**/*.entity.js + +# PDF Storage Paths +PDF_SLIP_PATH=./pdfs/slips +PDF_BILL_PATH=./pdfs/bills diff --git a/apps/server/eslint.config.mjs b/apps/server/eslint.config.mjs new file mode 100644 index 0000000..b7f6277 --- /dev/null +++ b/apps/server/eslint.config.mjs @@ -0,0 +1,3 @@ +import baseConfig from '../../eslint.config.mjs'; + +export default [...baseConfig]; diff --git a/apps/server/jest.config.cts b/apps/server/jest.config.cts new file mode 100644 index 0000000..c805f35 --- /dev/null +++ b/apps/server/jest.config.cts @@ -0,0 +1,10 @@ +module.exports = { + displayName: 'server', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/server', +}; diff --git a/apps/server/project.json b/apps/server/project.json new file mode 100644 index 0000000..1ce9048 --- /dev/null +++ b/apps/server/project.json @@ -0,0 +1,86 @@ +{ + "name": "server", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/server/src", + "projectType": "application", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "outputPath": "dist/apps/server", + "main": "apps/server/src/main.ts", + "tsConfig": "apps/server/tsconfig.app.json", + "assets": [ + "apps/server/src/assets", + { + "glob": "**/*", + "input": "apps/server/src/common/pdfAnnotation", + "output": "src/common/pdfAnnotation" + } + ] + }, + "configurations": { + "development": { + "sourceMap": true + }, + "production": { + "sourceMap": false + } + } + }, + "prune-lockfile": { + "dependsOn": ["build"], + "cache": true, + "executor": "@nx/js:prune-lockfile", + "outputs": [ + "{workspaceRoot}/dist/apps/server/package.json", + "{workspaceRoot}/dist/apps/server/package-lock.json" + ], + "options": { + "buildTarget": "build" + } + }, + "copy-workspace-modules": { + "dependsOn": ["build"], + "cache": true, + "outputs": ["{workspaceRoot}/dist/apps/server/workspace_modules"], + "executor": "@nx/js:copy-workspace-modules", + "options": { + "buildTarget": "build" + } + }, + "prune": { + "dependsOn": ["prune-lockfile", "copy-workspace-modules"], + "executor": "nx:noop" + }, + "serve": { + "continuous": true, + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "dependsOn": ["build"], + "options": { + "buildTarget": "server:build", + "runBuildTargetDependencies": false + }, + "configurations": { + "development": { + "buildTarget": "server:build:development" + }, + "production": { + "buildTarget": "server:build:production" + } + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/server/jest.config.cts", + "passWithNoTests": true + } + } + } +} diff --git a/apps/server/src/app.controller.ts b/apps/server/src/app.controller.ts new file mode 100644 index 0000000..c70d51b --- /dev/null +++ b/apps/server/src/app.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getVersion(): string { + return this.appService.getVersion(); + } +} diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts new file mode 100644 index 0000000..1322bb4 --- /dev/null +++ b/apps/server/src/app.module.ts @@ -0,0 +1,31 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { SharedModule } from './common/shared.module'; +import { AppConfigModule } from './config/app/config.module'; +import { SqliteConfigModule } from './config/database/sqlite/config.module'; +import { ArticleModule } from './models/article/article.module'; +import { BillModule } from './models/bills/bill.module'; +import { CustomerModule } from './models/customer/customer.module'; +import { CompanySettingsModule } from './models/settings/company-settings.module'; +import { SqliteDatabaseProviderModule } from './providers/database/sqlite/provider.module'; +import { UsersModule } from './models/users/users.module'; // Bug #7 fix: add UsersModule + +const ENV = process.env.NODE_ENV; +@Module({ + imports: [ + AppConfigModule, + SqliteConfigModule, + SqliteDatabaseProviderModule, + ArticleModule, + CustomerModule, + BillModule, + CompanySettingsModule, + SharedModule, + UsersModule, // Bug #7 fix + ], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/apps/server/src/app.service.ts b/apps/server/src/app.service.ts new file mode 100644 index 0000000..30169a3 --- /dev/null +++ b/apps/server/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getVersion(): string { + return 'V1'; + } +} diff --git a/apps/server/src/assets/.gitkeep b/apps/server/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/server/src/common/base.service.ts b/apps/server/src/common/base.service.ts new file mode 100644 index 0000000..95bd339 --- /dev/null +++ b/apps/server/src/common/base.service.ts @@ -0,0 +1,78 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { ModelRepository } from '../models/model.repository'; +import { DeepPartial } from 'typeorm'; +import { ModelEntity } from './serializers/model.serializer'; + +@Injectable() +export class BaseService { + + constructor( + private readonly repository: ModelRepository + ) {} + + get( + id: number, + relations: string[] = [], + throwsException = true, + ): Promise { + return this.repository.get(id, relations, throwsException); + } + + getAll( + relations: string[] = [], + throwsException = true, + ): Promise { + return this.repository.findAll({ + relations, + throwsException, + }); + } + + async delete(id: number, throwsException = true): Promise { + return this.repository + .delete(id) + .then((result) => { + if (throwsException && (!result.affected || result.affected === 0)) { + return Promise.reject(new NotFoundException('Model not found.')); + } + return Promise.resolve(true); + }) + .catch((error) => Promise.reject(error)); + } + + async getByName( + name: string, + relations: string[] = [], + throwsException = false, + ): Promise { + return this.repository + .findOne({ + where: { name: name } as any, + relations, + }) + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + + return Promise.resolve( + entity ? this.repository.transform(entity) : null, + ); + }) + .catch((error) => Promise.reject(error)); + } + + async create(inputs: DeepPartial): Promise { + return await this.repository.createEntity(inputs); + } + + async update( + id: number, + inputs: DeepPartial, + ): Promise { + return await this.repository.updateEntity( + id, + inputs as any, + ); + } +} diff --git a/apps/server/src/common/decorators/apires.decorator.ts b/apps/server/src/common/decorators/apires.decorator.ts new file mode 100644 index 0000000..26e123c --- /dev/null +++ b/apps/server/src/common/decorators/apires.decorator.ts @@ -0,0 +1,51 @@ +import { Type, applyDecorators } from '@nestjs/common'; +import { + ApiCreatedResponse, + ApiOkResponse, + getSchemaPath, +} from '@nestjs/swagger'; +import { ReS } from '../res.model'; + +export const ApiReS = >( + model: TModel, + description: string, + status?: number, +) => { + if (status === 201) { + return applyDecorators( + ApiCreatedResponse({ + description, + schema: { + allOf: [ + { $ref: getSchemaPath(ReS) }, + { + properties: { + data: { + $ref: getSchemaPath(model), + }, + }, + }, + ], + }, + }), + ); + } + + return applyDecorators( + ApiOkResponse({ + description, + schema: { + allOf: [ + { $ref: getSchemaPath(ReS) }, + { + properties: { + data: { + $ref: getSchemaPath(model), + }, + }, + }, + ], + }, + }), + ); +}; diff --git a/apps/server/src/common/decorators/isset.decorator.ts b/apps/server/src/common/decorators/isset.decorator.ts new file mode 100644 index 0000000..06cec2d --- /dev/null +++ b/apps/server/src/common/decorators/isset.decorator.ts @@ -0,0 +1,24 @@ +import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'; + +export function IsSet( + condition: (object: any, value: any) => boolean, + validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + registerDecorator({ + name: 'isSet', + target: object.constructor, + propertyName: propertyName, + constraints: [condition], + options: validationOptions, + validator: { + validate(value: any, args: ValidationArguments) { + const [relatedPropertyName] = args.constraints; + console.log(value); + console.log(args.object); + const relatedValue = relatedPropertyName(args.object); + return !relatedValue; + }, + }, + }); + }; +} diff --git a/apps/server/src/common/dto/id.dto.ts b/apps/server/src/common/dto/id.dto.ts new file mode 100644 index 0000000..aaf0bb1 --- /dev/null +++ b/apps/server/src/common/dto/id.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber } from 'class-validator'; + +export class IdDto { + @ApiProperty({ + example: '1', + description: 'ID', + }) + @IsNotEmpty() + @IsNumber() + id: number; +} diff --git a/apps/server/src/common/dto/time-range.dto.ts b/apps/server/src/common/dto/time-range.dto.ts new file mode 100644 index 0000000..1da8b5b --- /dev/null +++ b/apps/server/src/common/dto/time-range.dto.ts @@ -0,0 +1,11 @@ +import { IsDateString } from 'class-validator'; + +export class TimeRangeDto { + + @IsDateString() + from: string; + + @IsDateString() + to: string; + +} diff --git a/apps/server/src/common/enums/errorcodes.enum.ts b/apps/server/src/common/enums/errorcodes.enum.ts new file mode 100644 index 0000000..3e2d899 --- /dev/null +++ b/apps/server/src/common/enums/errorcodes.enum.ts @@ -0,0 +1,3 @@ +export declare enum ErrorCodes { + UserNotInHeader = 'user not in token', +} diff --git a/apps/server/src/common/filters/errors.filter.spec.ts b/apps/server/src/common/filters/errors.filter.spec.ts new file mode 100644 index 0000000..19aa9a9 --- /dev/null +++ b/apps/server/src/common/filters/errors.filter.spec.ts @@ -0,0 +1,181 @@ +/** + * REQ-002 + REQ-004 — ErrorFilter (Unit) + * + * Spec: test-req/REQ-002-response-wrapper.md + * test-req/REQ-004-error-filter.md + * Source: apps/server/src/common/filters/errors.filter.ts + * + * Testet ErrorFilter.catch() direkt mit gemocktem ArgumentsHost. + * Dadurch ist der Test deterministisch ohne laufenden Server. + * + * TC-Zuordnung: + * TC-002-006 — Non-HttpException → HTTP 500 + ReE + * TC-004-003 — message-Array passthrough (kein double-wrap) + * TC-004-004 — message-String → einelementiges Array + * TC-004-005 — Non-HttpException → HTTP 500 (message + statusCode) + * TC-004-006 — TypeError → error: "TypeError" + * + * Ausführen: npx nx test server --testFile=errors.filter.spec.ts + */ + +import { ArgumentsHost, BadRequestException, HttpStatus, NotFoundException } from '@nestjs/common'; +import { ErrorFilter } from './errors.filter'; + +// --------------------------------------------------------------------------- +// Mock-Fabrik für ArgumentsHost +// --------------------------------------------------------------------------- + +interface MockResponseCapture { + statusCode: number | null; + body: Record | null; +} + +function createMockHost(): { host: ArgumentsHost; capture: MockResponseCapture } { + const capture: MockResponseCapture = { statusCode: null, body: null }; + + const mockJson = jest.fn((body: Record) => { + capture.body = body; + }); + + // response.status(code) muss ein Objekt mit .json() zurückgeben + const mockStatus = jest.fn((code: number) => { + capture.statusCode = code; + return { json: mockJson }; + }); + + const mockHost = { + switchToHttp: jest.fn().mockReturnValue({ + getResponse: jest.fn().mockReturnValue({ status: mockStatus }), + getRequest: jest.fn().mockReturnValue({}), + }), + } as unknown as ArgumentsHost; + + return { host: mockHost, capture }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('ErrorFilter', () => { + let filter: ErrorFilter; + let consoleErrorSpy: jest.SpyInstance; + + beforeEach(() => { + filter = new ErrorFilter(); + // Unterdrücke console.error während der Tests (errors.filter.ts Zeile 22) + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + }); + + afterEach(() => { + consoleErrorSpy.mockRestore(); + }); + + // ------------------------------------------------------------------------- + // TC-002-006 / TC-004-005: Non-HttpException → HTTP 500 + // ------------------------------------------------------------------------- + describe('TC-002-006 / TC-004-005: Non-HttpException gibt HTTP 500 + ReE zurück', () => { + it('setzt HTTP-Statuscode 500', () => { + const { host, capture } = createMockHost(); + filter.catch(new Error('Unexpected failure'), host); + expect(capture.statusCode).toBe(HttpStatus.INTERNAL_SERVER_ERROR); + }); + + it('Body: success ist false', () => { + const { host, capture } = createMockHost(); + filter.catch(new Error('Unexpected failure'), host); + expect(capture.body?.success).toBe(false); + }); + + it('Body: statusCode ist 500', () => { + const { host, capture } = createMockHost(); + filter.catch(new Error('Unexpected failure'), host); + expect(capture.body?.statusCode).toBe(500); + }); + + it('Body: message ist Array mit error.message', () => { + const { host, capture } = createMockHost(); + filter.catch(new Error('DB connection lost'), host); + expect(capture.body?.message).toEqual(['DB connection lost']); + }); + + it('Body: message ist Array, kein String', () => { + const { host, capture } = createMockHost(); + filter.catch(new Error('some error'), host); + expect(Array.isArray(capture.body?.message)).toBe(true); + }); + + it('Body: error ist error.name ("Error" für generisches Error)', () => { + const { host, capture } = createMockHost(); + filter.catch(new Error('msg'), host); + expect(capture.body?.error).toBe('Error'); + }); + + it('TC-004-006: Body: error ist "TypeError" für TypeError-Instanz', () => { + const { host, capture } = createMockHost(); + filter.catch(new TypeError('type mismatch'), host); + expect(capture.body?.error).toBe('TypeError'); + }); + + it('Body: error ist "RangeError" für RangeError-Instanz', () => { + const { host, capture } = createMockHost(); + filter.catch(new RangeError('out of bounds'), host); + expect(capture.body?.error).toBe('RangeError'); + }); + }); + + // ------------------------------------------------------------------------- + // TC-004-003 + TC-004-004: HttpException — message-Normalisierung + // ------------------------------------------------------------------------- + describe('TC-004-003 / TC-004-004: HttpException-Pfad — message-Normalisierung', () => { + it('TC-004-004: message wird zu Array gewrappt wenn es ein String ist', () => { + const { host, capture } = createMockHost(); + // BadRequestException mit String-Message + filter.catch(new BadRequestException('single string message'), host); + expect(Array.isArray(capture.body?.message)).toBe(true); + expect(capture.body?.message).toEqual(['single string message']); + }); + + it('TC-004-003: message bleibt Array wenn es bereits ein Array ist (passthrough)', () => { + const { host, capture } = createMockHost(); + // NestJS ValidationPipe erzeugt BadRequestException mit Array + const exception = new BadRequestException({ + message: ['field1 must be a string', 'field2 should not be empty'], + error: 'Bad Request', + }); + filter.catch(exception, host); + expect(capture.body?.message).toEqual([ + 'field1 must be a string', + 'field2 should not be empty', + ]); + // KEIN doppeltes Wrapping: nicht [['field1...', 'field2...']] + expect(Array.isArray((capture.body?.message as unknown[])?.[0])).toBe(false); + }); + + it('statusCode stimmt mit HTTP-Status überein (404)', () => { + const { host, capture } = createMockHost(); + filter.catch(new NotFoundException('not found'), host); + expect(capture.statusCode).toBe(404); + expect(capture.body?.statusCode).toBe(404); + }); + + it('statusCode stimmt mit HTTP-Status überein (400)', () => { + const { host, capture } = createMockHost(); + filter.catch(new BadRequestException('bad'), host); + expect(capture.statusCode).toBe(400); + expect(capture.body?.statusCode).toBe(400); + }); + }); + + // ------------------------------------------------------------------------- + // console.error wird aufgerufen (belegt Zeile 22 in errors.filter.ts) + // ------------------------------------------------------------------------- + describe('Logging-Verhalten', () => { + it('ruft console.error mit dem Fehler auf', () => { + const { host } = createMockHost(); + const error = new Error('logged error'); + filter.catch(error, host); + expect(consoleErrorSpy).toHaveBeenCalledWith(error); + }); + }); +}); diff --git a/apps/server/src/common/filters/errors.filter.ts b/apps/server/src/common/filters/errors.filter.ts new file mode 100644 index 0000000..4614eea --- /dev/null +++ b/apps/server/src/common/filters/errors.filter.ts @@ -0,0 +1,37 @@ +import { + ExceptionFilter, + Catch, + HttpException, + ArgumentsHost, + HttpStatus, +} from '@nestjs/common'; +import { ReE } from '../res.model'; + +@Catch() +export class ErrorFilter implements ExceptionFilter { + catch(error: Error, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const statusCode: number = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + let message; + console.error(error); + if (error instanceof HttpException) { + const errorMessage = (error.getResponse() as any)?.message; + message = Array.isArray(errorMessage) ? errorMessage : [errorMessage]; + } else { + message = [error.message]; + } + + const name = + error instanceof HttpException + ? (error.getResponse() as any)?.error || error.name + : error.name; + + response.status(statusCode).json(ReE.FromData(statusCode, name, message)); + } +} diff --git a/apps/server/src/common/pdfAnnotation/adler.svg b/apps/server/src/common/pdfAnnotation/adler.svg new file mode 100644 index 0000000..f32b05e --- /dev/null +++ b/apps/server/src/common/pdfAnnotation/adler.svg @@ -0,0 +1,242 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/server/src/common/pdfAnnotation/fonts/ARIALN.TTF b/apps/server/src/common/pdfAnnotation/fonts/ARIALN.TTF new file mode 100644 index 0000000..94907a3 Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/ARIALN.TTF differ diff --git a/apps/server/src/common/pdfAnnotation/fonts/ARIALNB.TTF b/apps/server/src/common/pdfAnnotation/fonts/ARIALNB.TTF new file mode 100644 index 0000000..62437f0 Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/ARIALNB.TTF differ diff --git a/apps/server/src/common/pdfAnnotation/fonts/ARIALNBI.TTF b/apps/server/src/common/pdfAnnotation/fonts/ARIALNBI.TTF new file mode 100644 index 0000000..d3f019a Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/ARIALNBI.TTF differ diff --git a/apps/server/src/common/pdfAnnotation/fonts/ARIALNI.TTF b/apps/server/src/common/pdfAnnotation/fonts/ARIALNI.TTF new file mode 100644 index 0000000..4acd468 Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/ARIALNI.TTF differ diff --git a/apps/server/src/common/pdfAnnotation/fonts/arial.ttf b/apps/server/src/common/pdfAnnotation/fonts/arial.ttf new file mode 100644 index 0000000..8682d94 Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/arial.ttf differ diff --git a/apps/server/src/common/pdfAnnotation/fonts/arialbd.ttf b/apps/server/src/common/pdfAnnotation/fonts/arialbd.ttf new file mode 100644 index 0000000..a6037e6 Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/arialbd.ttf differ diff --git a/apps/server/src/common/pdfAnnotation/fonts/arialbi.ttf b/apps/server/src/common/pdfAnnotation/fonts/arialbi.ttf new file mode 100644 index 0000000..6a1fa0f Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/arialbi.ttf differ diff --git a/apps/server/src/common/pdfAnnotation/fonts/ariali.ttf b/apps/server/src/common/pdfAnnotation/fonts/ariali.ttf new file mode 100644 index 0000000..3801997 Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/ariali.ttf differ diff --git a/apps/server/src/common/pdfAnnotation/fonts/ariblk.ttf b/apps/server/src/common/pdfAnnotation/fonts/ariblk.ttf new file mode 100644 index 0000000..e7ae345 Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/fonts/ariblk.ttf differ diff --git a/apps/server/src/common/pdfAnnotation/gdfort.jpg b/apps/server/src/common/pdfAnnotation/gdfort.jpg new file mode 100644 index 0000000..839a7bd Binary files /dev/null and b/apps/server/src/common/pdfAnnotation/gdfort.jpg differ diff --git a/apps/server/src/common/pdfAnnotation/logo.svg b/apps/server/src/common/pdfAnnotation/logo.svg new file mode 100644 index 0000000..976868c --- /dev/null +++ b/apps/server/src/common/pdfAnnotation/logo.svg @@ -0,0 +1,53 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/apps/server/src/common/res.model.ts b/apps/server/src/common/res.model.ts new file mode 100644 index 0000000..0ed665f --- /dev/null +++ b/apps/server/src/common/res.model.ts @@ -0,0 +1,41 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose, Transform } from 'class-transformer'; + +export class ReturnHelper { + @ApiProperty({ example: true, description: 'success indicator' }) + @Expose() + public success = true; +} + +export class ReS extends ReturnHelper { + static FromData(arg0: T): ReS { + const ret = new ReS(); + ret.success = true; + ret.data = arg0; + return ret; + } + + @Expose() + @Transform(({ value }) => value) // preserves null when ClassSerializerInterceptor + excludeExtraneousValues:true is active + public data: T; +} + +export class ReE extends ReturnHelper { + static FromData(statusCode: number, error: string, message: string[]): ReE { + const ret = new ReE(); + ret.success = false; + ret.message = message; + ret.error = error; + ret.statusCode = statusCode; + return ret; + } + @ApiProperty({ description: 'statusCode' }) + @Expose() + public statusCode: number; + @ApiProperty({ description: 'detail message' }) + @Expose() + public message: string[]; + @ApiProperty({ description: 'error description' }) + @Expose() + public error: string; +} diff --git a/apps/server/src/common/serializers/model.serializer.ts b/apps/server/src/common/serializers/model.serializer.ts new file mode 100644 index 0000000..e87f410 --- /dev/null +++ b/apps/server/src/common/serializers/model.serializer.ts @@ -0,0 +1,13 @@ +import { Expose } from 'class-transformer'; + +export class ModelEntity { + @Expose({ groups: ['default'] }) + id: number; + [key: string]: any; + + constructor(id?: number) { + if (id) { + this.id = id; + } + } +} diff --git a/apps/server/src/common/services/pdfmaker.service.ts b/apps/server/src/common/services/pdfmaker.service.ts new file mode 100644 index 0000000..4a70b3e --- /dev/null +++ b/apps/server/src/common/services/pdfmaker.service.ts @@ -0,0 +1,636 @@ +import { Injectable } from '@nestjs/common'; +import { mkdir, writeFile } from 'fs/promises'; +import { existsSync, readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import moment from 'moment'; +import { PDFDocument } from 'pdf-lib'; +const PdfPrinter = require('pdfmake'); +import { Content, ContentTable, TDocumentDefinitions } from 'pdfmake/interfaces'; +import { AnnotationEntity } from '../../models/bills/serializers/annotation.serializer'; +import { BillEntity } from '../../models/bills/serializers/bill.serializer'; +import { OrderEntryEntity } from '../../models/bills/serializers/order-entry.serializer'; +import { SlipsheetEntity } from '../../models/bills/serializers/slipsheet.serializer'; +import { CompanySettingsService } from '../../models/settings/company-settings.service'; +import { + CompanySettingsEntity, +} from '../../models/settings/serializers/company-settings.serializer'; +import { LetterheadMode } from '../../models/settings/interfaces/company-settings.interface'; + +const FALLBACK_LOGO = join(__dirname, '..', 'pdfAnnotation', 'logo.svg'); +const FALLBACK_BADGE1 = join(__dirname, '..', 'pdfAnnotation', 'adler.svg'); +const FALLBACK_BADGE2 = join(__dirname, '..', 'pdfAnnotation', 'gdfort.jpg'); + +@Injectable() +export class PdfMakerService { + private readonly fonts = { + Courier: { + normal: 'Courier', + bold: 'Courier-Bold', + italics: 'Courier-Oblique', + bolditalics: 'Courier-BoldOblique', + }, + Helvetica: { + normal: 'Helvetica', + bold: 'Helvetica-Bold', + italics: 'Helvetica-Oblique', + bolditalics: 'Helvetica-BoldOblique', + }, + Times: { + normal: 'Times-Roman', + bold: 'Times-Bold', + italics: 'Times-Italic', + bolditalics: 'Times-BoldItalic', + }, + Arial: { + normal: join(__dirname, '..', 'pdfAnnotation', 'fonts', 'arial.ttf'), + bold: join(__dirname, '..', 'pdfAnnotation', 'fonts', 'arialbd.ttf'), + italics: join(__dirname, '..', 'pdfAnnotation', 'fonts', 'ariali.ttf'), + bolditalics: join(__dirname, '..', 'pdfAnnotation', 'fonts', 'arialbi.ttf'), + }, + Symbol: { normal: 'Symbol' }, + ZapfDingbats: { normal: 'ZapfDingbats' }, + }; + + private readonly printer; + + constructor(private readonly settingsService: CompanySettingsService) { + this.printer = new PdfPrinter(this.fonts); + } + + public async generateDeliverySlip(slip: SlipsheetEntity): Promise { + const settings = await this.getSettings(); + const docDefinition = this.buildDocDefinition(settings); + const content: Array = []; + + content.push(this.buildHead(slip, slip.printDate, settings)); + content.push({ text: 'Lieferschein #' + slip.slipsheetnumber, style: 'header' }); + + const annotation = this.buildSlipAnnotations(slip); + if (annotation) { + content.push({ stack: annotation }); + } + + content.push(this.buildOrdersDelivery(slip)); + docDefinition.content = content; + + return this.renderPdfBuffer(docDefinition, settings); + } + + public async generateBill(bill: BillEntity): Promise { + const settings = await this.getSettings(); + const docDefinition = this.buildDocDefinition(settings); + const content: Array = []; + + content.push(this.buildHead(bill.slipsheets[0], bill.billDate, settings)); + content.push({ text: 'Rechnung #' + bill.billNumber, style: 'header' }); + content.push(this.buildOrders(bill, settings)); + docDefinition.content = content; + + return this.renderPdfBuffer(docDefinition, settings); + } + + public async savePDFToFileSystem(pdf: Buffer, filepath: string): Promise { + await mkdir(dirname(filepath), { recursive: true }); + await writeFile(filepath, pdf); + return filepath; + } + + private async renderPdfBuffer( + docDefinition: TDocumentDefinitions, + settings: CompanySettingsEntity | null, + ): Promise { + const doc = this.printer.createPdfKitDocument(docDefinition); + const contentBuffer = await this.toBuffer(doc); + + if (this.getLetterheadMode(settings) !== 'template_pdf') { + return contentBuffer; + } + + return this.mergeWithTemplate(contentBuffer, settings); + } + + private toBuffer(doc: PDFKit.PDFDocument): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + + doc.on('data', (chunk: Buffer) => chunks.push(Buffer.from(chunk))); + doc.on('end', () => resolve(Buffer.concat(chunks))); + doc.on('error', reject); + doc.end(); + }); + } + + private async mergeWithTemplate( + contentBuffer: Buffer, + settings: CompanySettingsEntity | null, + ): Promise { + const templatePath = this.getExistingPath(settings?.templatePdfPath); + if (!templatePath) { + return contentBuffer; + } + + try { + const contentPdf = await PDFDocument.load(contentBuffer); + const templateBytes = readFileSync(templatePath); + const templatePdf = await PDFDocument.load(templateBytes); + const mergedPdf = await PDFDocument.create(); + const templatePageCount = templatePdf.getPageCount(); + + for (let pageIndex = 0; pageIndex < contentPdf.getPageCount(); pageIndex += 1) { + const contentPage = contentPdf.getPage(pageIndex); + const { width, height } = contentPage.getSize(); + const targetPage = mergedPdf.addPage([width, height]); + const templatePageIndex = templatePageCount === 0 + ? -1 + : Math.min(pageIndex, templatePageCount - 1); + + if (templatePageIndex >= 0) { + const [embeddedTemplatePage] = await mergedPdf.embedPdf(templateBytes, [templatePageIndex]); + targetPage.drawPage(embeddedTemplatePage, { + x: 0, + y: 0, + width, + height, + }); + } + + const [embeddedContentPage] = await mergedPdf.embedPdf(contentBuffer, [pageIndex]); + targetPage.drawPage(embeddedContentPage, { + x: 0, + y: 0, + width, + height, + }); + } + + return Buffer.from(await mergedPdf.save()); + } catch (error) { + console.error('PdfMakerService.mergeWithTemplate:', error); + return contentBuffer; + } + } + + private async getSettings(): Promise { + try { + return await this.settingsService.get(); + } catch { + return null; + } + } + + private getLetterheadMode(settings: CompanySettingsEntity | null): LetterheadMode { + return settings?.letterheadMode ?? 'generated'; + } + + private shouldRenderGeneratedLetterhead(settings: CompanySettingsEntity | null): boolean { + return this.getLetterheadMode(settings) === 'generated'; + } + + private getExistingPath(filePath: string | null | undefined): string | null { + if (!filePath) { + return null; + } + + const candidates = [filePath, join(process.cwd(), filePath)]; + for (const candidate of candidates) { + if (existsSync(candidate)) { + return candidate; + } + } + + return null; + } + + private isSvg(path: string): boolean { + return path.toLowerCase().endsWith('.svg'); + } + + private buildLogoContent(settings: CompanySettingsEntity | null): Content { + const usePath = this.getExistingPath(settings?.logoPath) || FALLBACK_LOGO; + + if (this.isSvg(usePath)) { + return { + svg: readFileSync(usePath).toString(), + fit: [170, 170], + margin: [60, 30, 0, 0], + }; + } + + return { + image: usePath, + fit: [170, 170], + margin: [60, 30, 0, 0], + }; + } + + private buildFooterBadge1(settings: CompanySettingsEntity | null): Content { + const usePath = this.getExistingPath(settings?.badge1Path) || FALLBACK_BADGE1; + const base: any = { + alignment: 'right', + width: 40, + margin: [0, 0, 15, 0], + color: '#e9582a', + }; + + return this.isSvg(usePath) + ? { ...base, svg: readFileSync(usePath).toString() } + : { ...base, image: usePath }; + } + + private buildFooterBadge2(settings: CompanySettingsEntity | null): Content { + const usePath = this.getExistingPath(settings?.badge2Path) || FALLBACK_BADGE2; + + return this.isSvg(usePath) + ? { + svg: readFileSync(usePath).toString(), + width: 40, + margin: [25, 0, 0, 0], + color: '#e9582a', + } + : { image: usePath, width: 40, margin: [25, 0, 0, 0], color: '#e9582a' }; + } + + private buildFooterText(settings: CompanySettingsEntity | null): string { + const paymentText = + settings?.paymentFooterText ?? + `Zahlung innerhalb von ${settings?.paymentTermDays ?? 14} Tagen netto Kassa`; + + const bankLines = (settings?.bankAccounts ?? []) + .map((bank) => `${bank.name} | IBAN: ${bank.iban} | BIC: ${bank.bic}`) + .join(' | '); + + const city = settings?.issueCity ?? 'Landeck'; + const base = `Zahlbar und klagbar in ${city}`; + + return [paymentText, base, bankLines].filter(Boolean).join(' | '); + } + + private buildHeaderStack(settings: CompanySettingsEntity | null): Content[] { + const lines: Content[] = []; + + if (settings?.street) { + lines.push({ text: settings.street, style: 'header' }); + } + if (settings?.zip || settings?.city) { + lines.push({ + text: `${settings?.zip ?? ''} ${settings?.city ?? ''}`.trim(), + style: 'header', + }); + } + if (settings?.phone) { + lines.push({ text: ' ', style: 'subheader' }); + lines.push({ text: `Mobile ${settings.phone}`, style: 'subheader' }); + } + if (settings?.email) { + lines.push({ text: `E-Mail: ${settings.email}`, style: 'subheader' }); + } + if (settings?.website) { + lines.push({ text: settings.website, style: 'subheader' }); + } + if (settings?.firmenbuchnummer) { + lines.push({ text: settings.firmenbuchnummer, style: 'subheader' }); + } + + if (lines.length === 0) { + lines.push( + { text: 'Fliesserau 384 b', style: 'header' }, + { text: '6500 Landeck', style: 'header' }, + { text: ' ', style: 'subheader' }, + { text: 'Mobile +43 699 10 63 63 45', style: 'subheader' }, + { text: 'E-Mail: office@holz-abler.com', style: 'subheader' }, + { text: 'www.holz-abler.com', style: 'subheader' }, + { text: 'FN.: 303902s, ATU63848368', style: 'subheader' }, + ); + } + + return lines; + } + + private buildDocDefinition(settings: CompanySettingsEntity | null): TDocumentDefinitions { + const self = this; + const renderGeneratedLetterhead = this.shouldRenderGeneratedLetterhead(settings); + + return { + pageOrientation: 'portrait', + pageMargins: [60, 150, 60, 100], + header: renderGeneratedLetterhead + ? (() => [ + { + columns: [ + self.buildLogoContent(settings), + { + alignment: 'right', + margin: [0, 25, 70, 0], + stack: self.buildHeaderStack(settings), + }, + ], + }, + { + canvas: [{ type: 'line', x1: 50, y1: 5, x2: 595 - 50, y2: 5, lineWidth: 1 }], + }, + ]) as any + : undefined, + footer: renderGeneratedLetterhead + ? (() => [ + { + canvas: [{ type: 'line', x1: 50, y1: 0, x2: 595 - 50, y2: 0, lineWidth: 1 }], + margin: [0, 30, 0, 5], + }, + { + table: { + widths: [120, '*', 120], + body: [[ + self.buildFooterBadge1(settings), + { text: self.buildFooterText(settings), style: 'footerText' }, + self.buildFooterBadge2(settings), + ]], + }, + layout: 'noBorders', + }, + ]) as any + : undefined, + content: [], + styles: this.buildStyles(), + defaultStyle: { font: 'Arial', fontSize: 12 }, + }; + } + + private buildStyles(): any { + return { + header: { fontSize: 12, color: 'black' }, + subheader: { fontSize: 9, color: 'black' }, + footerText: { + fontSize: 8, + margin: [0, 10, 0, 0], + alignment: 'center' as const, + color: 'black', + }, + tableExample: { margin: [0, 5, 0, 15] }, + tableHeader: { bold: true, fontSize: 7, color: '#e9582a' }, + tableSum: { color: 'black', bold: true }, + tableSumHeader: { bold: true, fontSize: 10, color: 'black' }, + tableCell: { fontSize: 7 }, + slipCell: { color: '#e9582a' }, + slipAnnotation: { fontSize: 9, color: 'black' }, + }; + } + + private buildHead( + slip: SlipsheetEntity, + date: Date | undefined, + settings: CompanySettingsEntity | null, + ): Content { + const city = settings?.issueCity ?? 'Landeck'; + + return { + alignment: 'justify', + margin: [0, 10, 10, 40], + columns: [ + { + width: 'auto', + text: [ + 'An\n', + (slip.customer.companyName || '') + '\n', + slip.customer.lastName + ' ' + slip.customer.firstName + '\n', + slip.customer.address + '\n', + slip.customer.postcode + ' ' + slip.customer.country + '\n', + ], + }, + { + alignment: 'right', + margin: [0, 60, 0, 0], + text: + `${city}, am ${moment(date ?? new Date()).format('DD.MM.YYYY')}` + + (slip.customer.customerNumber ? '\nKunde: ' + slip.customer.customerNumber : '\n') + + (slip.customer.uid ? '\nIhre UID: ' + slip.customer.uid : '\nIhre UID:\n'), + }, + ], + }; + } + + private buildSlipAnnotations(slip: SlipsheetEntity): Content[] | null { + const items: Content[] = []; + slip?.annotations?.forEach((element: AnnotationEntity) => { + items.push({ text: element.text, style: 'slipAnnotation' }); + }); + + return items.length === 0 ? null : items; + } + + private buildOrders(bill: BillEntity, settings: CompanySettingsEntity | null): Content { + const vatRate = settings?.vatRate ?? 20; + const isDiscount = bill.slipsheets.some((cart) => + cart.orderEntries.some((order) => order.articleGroupRabatt && order.articleGroupRabatt !== 0), + ); + const isDiscountSpecial = bill.slipsheets.some((cart) => + cart.orderEntries.some((order) => order.customerRabatt && order.customerRabatt !== 0), + ); + + const table: any = { + style: 'tableExample', + table: { + headerRows: 1, + widths: [60, 'auto', '*', 'auto', 'auto', 'auto', 'auto', 'auto'], + body: [[ + { text: 'Pos', style: 'tableHeader', margin: [0, 0, 5, 0] }, + { text: 'Art.Num.', style: 'tableHeader' }, + { text: 'Artikel', style: 'tableHeader' }, + { text: 'Menge', style: 'tableHeader' }, + { text: 'Preis', style: 'tableHeader' }, + { text: isDiscount ? 'Rabatt' : '', style: 'tableHeader' }, + { text: isDiscountSpecial ? 'Sonder\nRabatt' : '', style: 'tableHeader' }, + { text: 'Gesamt', style: 'tableHeader' }, + ]], + }, + layout: this.getTableDefaultLayout(), + }; + + let sum = 0; + + for (const slip of bill.slipsheets) { + table.table.body.push([ + '', + { + text: + 'Lieferschein von ' + + moment(slip.createdAt).format('DD.MM.YYYY') + + ' L' + + slip.slipsheetnumber, + colSpan: 6, + style: 'slipCell', + }, + '', + '', + '', + '', + '', + '', + ]); + + const annotation = this.buildSlipAnnotations(slip); + if (annotation) { + table.table.body.push(['', { stack: annotation, colSpan: 6 }, '', '', '', '', '', '']); + } + + for (let i = 0; i < slip.orderEntries.length; i += 1) { + const el: OrderEntryEntity = slip.orderEntries[i]; + const amount = el.amountCounted || el.amount; + const discount = el.articleGroupRabatt ?? 0; + const discountSpecial = el.customerRabatt ?? 0; + const total = + (amount * el.price * (100 - discount)) / 100 * ((100 - discountSpecial) / 100); + + sum += total; + + table.table.body.push([ + { text: i + 1, style: 'tableCell' }, + { text: el.article?.artNumber ?? '', style: 'tableCell' }, + { text: el.text, style: 'tableCell' }, + { text: amount, style: 'tableCell', alignment: 'right' }, + { text: 'EUR ' + el.price.toFixed(2), style: 'tableCell', alignment: 'right' }, + { + text: isDiscount ? discount.toFixed(0) + '%' : '', + style: 'tableCell', + alignment: 'right', + }, + { + text: isDiscountSpecial ? discountSpecial.toFixed(0) + '%' : '', + style: 'tableCell', + alignment: 'right', + }, + { text: 'EUR ' + total.toFixed(2), style: 'tableCell', alignment: 'right' }, + ]); + } + } + + const vatLabel = `${vatRate}% MwSt.`; + const empty = { text: '', border: [0, 0, 0, 0] }; + + table.table.body.push( + [ + empty, + empty, + empty, + empty, + { text: 'Summe', colSpan: 2, style: 'tableCell', alignment: 'right' }, + '', + '', + { text: 'EUR ' + sum.toFixed(2), style: 'tableCell', alignment: 'right' }, + ], + [ + empty, + empty, + empty, + empty, + { text: vatLabel, colSpan: 2, style: 'tableCell', alignment: 'right' }, + '', + '', + { text: 'EUR ' + ((sum * vatRate) / 100).toFixed(2), style: 'tableCell', alignment: 'right' }, + ], + [ + empty, + empty, + empty, + empty, + { text: 'Gesamt', colSpan: 2, style: 'tableSumHeader', alignment: 'right' }, + '', + '', + { + text: 'EUR ' + (sum * (1 + vatRate / 100)).toFixed(2), + style: 'tableSumHeader', + alignment: 'right', + }, + ], + ); + + return table; + } + + private buildOrdersDelivery(slip: SlipsheetEntity): Content { + const table: ContentTable = { + style: 'tableExample', + table: { + headerRows: 1, + widths: [100, '*', '*', 'auto'], + body: [[ + { text: 'Pos', style: 'tableHeader', margin: [0, 0, 5, 0] }, + { text: 'Artikel', style: 'tableHeader' }, + { text: 'Typ', style: 'tableHeader' }, + { text: 'Menge', style: 'tableHeader' }, + ]], + }, + layout: this.getSlipTableLayout(), + }; + + for (let i = 0; i < slip.orderEntries.length; i += 1) { + const el = slip.orderEntries[i]; + table.table.body.push([ + { text: i + 1, style: 'tableCell' }, + { text: el.text, style: 'tableCell' }, + { text: el.article?.artNumber ?? '', style: 'tableCell' }, + { text: el.amount, style: 'tableCell' }, + ]); + } + + table.table.body.push(['', '', '', '']); + return table; + } + + private getTableDefaultLayout() { + return { + hLineWidth: (i: number, node: any) => { + if (i === 0) { + return 0; + } + if (i === node.table.body.length) { + return 2; + } + if (i === node.table.body.length - 1) { + return 1; + } + return 1; + }, + vLineWidth: () => 0, + hLineColor: (i: number, node: any) => { + if (i === node.table.body.length - 3) { + return 'black'; + } + return i === 1 || i === node.table.body.length - 1 || i === node.table.body.length + ? 'black' + : '#aaa'; + }, + paddingLeft: (i: number) => (i <= 1 ? 0 : 5), + paddingRight: (i: number, node: any) => (i === node.table.widths.length - 1 ? 0 : 5), + paddingTop: () => 4, + paddingBottom: () => 4, + fillColor: () => null, + }; + } + + private getSlipTableLayout() { + return { + hLineWidth: (i: number, node: any) => { + if (i === 0) { + return 0; + } + if (i === node.table.body.length) { + return 2; + } + if (i === node.table.body.length - 1) { + return 1; + } + return 1; + }, + vLineWidth: () => 0, + hLineColor: (i: number, node: any) => + i === 1 || i === node.table.body.length - 1 || i === node.table.body.length + ? 'black' + : '#aaa', + paddingLeft: (i: number) => (i <= 1 ? 0 : 5), + paddingRight: (i: number, node: any) => (i === node.table.widths.length - 1 ? 0 : 5), + paddingTop: () => 4, + paddingBottom: () => 4, + fillColor: () => null, + }; + } +} diff --git a/apps/server/src/common/services/printer.service.ts b/apps/server/src/common/services/printer.service.ts new file mode 100644 index 0000000..c0ab1ef --- /dev/null +++ b/apps/server/src/common/services/printer.service.ts @@ -0,0 +1,21 @@ +import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { print } from 'pdf-to-printer'; +import { AppConfigService } from '../../config/app/config.service'; + +@Injectable() +export class PrinterService { + + constructor(private appConfigService: AppConfigService) {} + async print(path: string): Promise { + const options = { + printer: this.appConfigService.printer + }; + try { + await print(path, options); + return true; + } catch (error) { + throw new InternalServerErrorException('Drucken fehlgeschlagen'); + } + return false; + } +} diff --git a/apps/server/src/common/shared.module.ts b/apps/server/src/common/shared.module.ts new file mode 100644 index 0000000..305ba7f --- /dev/null +++ b/apps/server/src/common/shared.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { AppConfigModule } from '../config/app/config.module'; +import { CompanySettingsModule } from '../models/settings/company-settings.module'; +import { PdfMakerService } from './services/pdfmaker.service'; +import { PrinterService } from './services/printer.service'; + +@Module({ + imports: [AppConfigModule, CompanySettingsModule], + providers: [PdfMakerService, PrinterService], + exports: [PdfMakerService, PrinterService], +}) +export class SharedModule {} diff --git a/apps/server/src/config/app/config.module.ts b/apps/server/src/config/app/config.module.ts new file mode 100644 index 0000000..e6e75d2 --- /dev/null +++ b/apps/server/src/config/app/config.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import configuration from './configuration'; +import { AppConfigService } from './config.service'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { join } from 'path'; + +const ENV = process.env.NODE_ENV; +@Module({ + imports: [ + ConfigModule.forRoot({ + load: [configuration], + envFilePath: !ENV ? '.env' : `.env.${ENV}`, + }), + ], + providers: [ConfigService, AppConfigService], + exports: [ConfigService, AppConfigService], +}) +export class AppConfigModule {} diff --git a/apps/server/src/config/app/config.service.ts b/apps/server/src/config/app/config.service.ts new file mode 100644 index 0000000..5ff325f --- /dev/null +++ b/apps/server/src/config/app/config.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class AppConfigService { + constructor(private configService: ConfigService) {} + + get name(): string { + return this.configService.get('app.name'); + } + get env(): string { + return this.configService.get('app.env'); + } + get url(): string { + return this.configService.get('app.url', 'http://localhost:9000'); + } + get port(): number { + return Number(this.configService.get('app.port', 3000)); + } + get jwt_secret(): string { + return this.configService.get('app.jwt_secret', 'ACCESS_TOKEN_SECRET'); + } + get jwt_expires(): string { + return this.configService.get('app.jwt_expires', '30d'); + } + + get pdf_slip_path(): string { + return this.configService.get('app.pdf_slip_path', './slips'); + } + + get pdf_bill_path(): string { + return this.configService.get('app.pdf_bill_path', './bills'); + } + + get printer(): string { + return this.configService.get('app.printer', 'Microsoft Print to PDF'); + } +} diff --git a/apps/server/src/config/app/configuration.ts b/apps/server/src/config/app/configuration.ts new file mode 100644 index 0000000..eedc42a --- /dev/null +++ b/apps/server/src/config/app/configuration.ts @@ -0,0 +1,13 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('app', () => ({ + env: process.env.APP_ENV, + name: process.env.APP_NAME, + url: process.env.APP_URL, + port: process.env.APP_PORT, + jwt_secret: process.env.APP_JWT_SECRET, + jwt_expires: process.env.APP_JWT_EXPIRES, + pdf_slip_path: process.env.APP_PDF_SLIP_PATH, + pdf_bill_path: process.env.APP_PDF_BILL_PATH, + printer: process.env.APP_PRINTER, +})); diff --git a/apps/server/src/config/database/sqlite/config.module.ts b/apps/server/src/config/database/sqlite/config.module.ts new file mode 100644 index 0000000..1362c15 --- /dev/null +++ b/apps/server/src/config/database/sqlite/config.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import configuration from './configuration'; +import { SqliteConfigService } from './config.service'; +import { ConfigModule, ConfigService } from '@nestjs/config'; + +const ENV = process.env.NODE_ENV; +@Module({ + imports: [ + ConfigModule.forRoot({ + load: [configuration], + envFilePath: !ENV ? '.env' : `.env.${ENV}`, + }), + ], + providers: [ConfigService, SqliteConfigService], + exports: [ConfigService, SqliteConfigService], +}) +export class SqliteConfigModule {} diff --git a/apps/server/src/config/database/sqlite/config.service.ts b/apps/server/src/config/database/sqlite/config.service.ts new file mode 100644 index 0000000..9429b42 --- /dev/null +++ b/apps/server/src/config/database/sqlite/config.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class SqliteConfigService { + constructor(private configService: ConfigService) {} + + get path(): string { + return this.configService.get('sqlite.path', 'sim.db'); + } + get migrationsRun(): boolean { + return JSON.parse(this.configService.get('sqlite.migrationsRun', 'false')); + } + get synchronizeRun(): boolean { + return JSON.parse(this.configService.get('sqlite.synchronizeRun', 'false')); + } + get entities(): string { + return this.configService.get('sqlite.entities', 'dist/**/*.entity.js'); + } +} diff --git a/apps/server/src/config/database/sqlite/configuration.ts b/apps/server/src/config/database/sqlite/configuration.ts new file mode 100644 index 0000000..f2d242a --- /dev/null +++ b/apps/server/src/config/database/sqlite/configuration.ts @@ -0,0 +1,8 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('sqlite', () => ({ + path: process.env.SQLITE_PATH, + migrationsRun: process.env.SQLITE_RUN_MIGRATION, + synchronizeRun: process.env.SQLITE_RUN_SYNCHRONIZE, + entities: process.env.SQLITE_ENTITIES, +})); diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts new file mode 100644 index 0000000..7244f18 --- /dev/null +++ b/apps/server/src/main.ts @@ -0,0 +1,98 @@ +import 'reflect-metadata'; + +import { NestFactory } from '@nestjs/core'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { AppModule } from './app.module'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { ErrorFilter } from './common/filters/errors.filter'; +import { AppConfigService } from './config/app/config.service'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { join } from 'path'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + // Bug #8 fix: replace cors: true with proper CORS config + app.enableCors({ + origin: process.env.CORS_ORIGIN?.split(',') ?? ['http://localhost:4200'], + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + credentials: true, + }); + app.useStaticAssets(join(process.cwd(), 'uploads'), { prefix: '/uploads' }); + + app.setGlobalPrefix('api/v1'); + app.useGlobalFilters(new ErrorFilter()); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + transform: true, + }), + ); + const appConfig: AppConfigService = app.get(AppConfigService); + + bootstrapSwagger(app, appConfig); + + await app.listen(appConfig.port); +} + +function bootstrapSwagger(app: INestApplication, appConfig: AppConfigService) { + const config = new DocumentBuilder() + .setTitle('ematric') + .setDescription('The Adler API description') + .setVersion('1.0') + .addServer(appConfig.url + '/api/v1/', 'v1') + .addBearerAuth() + .build(); + + const document = SwaggerModule.createDocument(app, config, { + ignoreGlobalPrefix: true, + deepScanRoutes: true, + include: [AppModule], + }); + + SwaggerModule.setup('api/v1/doc', app, document, { + swaggerOptions: { + persistAuthorization: true, + }, + customSiteTitle: 'API Docs', + }); + + bootstrapSwaggerUniversall(app, appConfig); +} + +function bootstrapSwaggerUniversall( + app: INestApplication, + appConfig: AppConfigService, +) { + const config = new DocumentBuilder() + .setTitle('Allgemein') + .setDescription('The Adler API Beschreibung Produktionsmanagement System') + .setVersion('1.0') + .addServer(appConfig.url + '/api/v1/', 'v1') + .addBearerAuth() + .setContact('ematric', 'www.ematric.com', 'ematric@ematric.com') + .build(); + + const document = SwaggerModule.createDocument(app, config, { + ignoreGlobalPrefix: true, + deepScanRoutes: false, + }); + + SwaggerModule.setup('api/v1/doc/univ', app, document, { + swaggerOptions: { + persistAuthorization: true, + }, + explorer: true, + customSiteTitle: 'Allgemeine API Docs', + ...getSwaggerOptions(), + }); +} + +function getSwaggerOptions() { + return { + customCss: ".swagger-ui img { content:url('/api/assets/ematric.svg'); }", + customfavIcon: '/api/assets/favicon-96x96.ico', + }; +} + +bootstrap(); diff --git a/apps/server/src/models/article/article-group.repository.ts b/apps/server/src/models/article/article-group.repository.ts new file mode 100644 index 0000000..16edd6e --- /dev/null +++ b/apps/server/src/models/article/article-group.repository.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ClassTransformOptions } from '@nestjs/common/interfaces/external/class-transform-options.interface'; +import { ModelRepository } from '../model.repository'; +import { ArticleGroup } from './entities/article-group.entity'; +import { allArticleGroupForSerializing, ArticleGroupEntity, extendedArticleGroupForSerializing } from './serializers/article-group.serializer'; + +@Injectable() +export class ArticleGroupRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(ArticleGroup, dataSource.createEntityManager()); + } + + transform(model: ArticleGroup): ArticleGroupEntity { + const tranformOptions: ClassTransformOptions = { + groups: extendedArticleGroupForSerializing, + }; + return plainToInstance( + ArticleGroupEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: ArticleGroup[]): ArticleGroupEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/article/article-group.service.ts b/apps/server/src/models/article/article-group.service.ts new file mode 100644 index 0000000..3e4c483 --- /dev/null +++ b/apps/server/src/models/article/article-group.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; +import { BaseService } from '../../common/base.service'; +import { ArticleGroupRepository } from './article-group.repository'; +import { ArticleGroup } from './entities/article-group.entity'; +import { ArticleGroupEntity } from './serializers/article-group.serializer'; + +@Injectable() +export class ArticleGroupService extends BaseService { + constructor( + private readonly articleGroupRepository: ArticleGroupRepository, + ) { + super(articleGroupRepository); + } +} diff --git a/apps/server/src/models/article/article.module.ts b/apps/server/src/models/article/article.module.ts new file mode 100644 index 0000000..319b95f --- /dev/null +++ b/apps/server/src/models/article/article.module.ts @@ -0,0 +1,34 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SharedModule } from '../../common/shared.module'; +import { ArticleGroupRepository } from './article-group.repository'; +import { ArticleGroupService } from './article-group.service'; +import { ArticleRepository } from './article.repository'; +import { ArticleService } from './article.service'; +import { ArticleGroupController } from './controllers/article-group.controller'; +import { ArticleController } from './controllers/article.controller'; +import { CsvModule } from 'nest-csv-parser'; +import { InventoryRepository } from './inventory.repository'; +import { InventoryService } from './inventory.service'; +import { Article } from './entities/article.entity'; +import { ArticleGroup } from './entities/article-group.entity'; +import { Inventory } from './entities/inventory.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Article, ArticleGroup, Inventory]), + SharedModule, + CsvModule, + ], + controllers: [ArticleController, ArticleGroupController], + providers: [ + ArticleRepository, + ArticleGroupRepository, + InventoryRepository, + ArticleService, + ArticleGroupService, + InventoryService, + ], + exports: [ArticleService, ArticleGroupService, InventoryRepository], +}) +export class ArticleModule {} diff --git a/apps/server/src/models/article/article.repository.ts b/apps/server/src/models/article/article.repository.ts new file mode 100644 index 0000000..6e853b8 --- /dev/null +++ b/apps/server/src/models/article/article.repository.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ClassTransformOptions } from '@nestjs/common/interfaces/external/class-transform-options.interface'; +import { ModelRepository } from '../model.repository'; +import { Article } from './entities/article.entity'; +import { allArticleForSerializing, ArticleEntity, extendedArticleForSerializing } from './serializers/article.serializer'; + +@Injectable() +export class ArticleRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(Article, dataSource.createEntityManager()); + } + + transform(model: Article): ArticleEntity { + const tranformOptions: ClassTransformOptions = { + groups: extendedArticleForSerializing, + }; + return plainToInstance( + ArticleEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: Article[]): ArticleEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/article/article.service.ts b/apps/server/src/models/article/article.service.ts new file mode 100644 index 0000000..c1f299f --- /dev/null +++ b/apps/server/src/models/article/article.service.ts @@ -0,0 +1,289 @@ +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { CsvParser } from 'nest-csv-parser'; +import { Readable } from 'stream'; +import { BaseService } from '../../common/base.service'; +import { OrderEntry } from '../bills/entities/order-entry.entity'; +import { ArticleRepository } from './article.repository'; +import { ArticleCsvEntity } from './entities/article-import.csv-entity'; +import { Article } from './entities/article.entity'; +import { InventoryService } from './inventory.service'; +import { ArticleEntity } from './serializers/article.serializer'; +import { UpdateArticleDto } from './dto/update-article.dto'; + +@Injectable() +export class ArticleService extends BaseService { + + constructor( + private readonly articleRepository: ArticleRepository, + private readonly csvParser: CsvParser, + private readonly inventoryService: InventoryService, + ) { + super(articleRepository); + } + + async get( + id: number, + relations: string[] = [], + throwsException = true, + ): Promise { + + let querybuilder = this.getQueryBuilder(); + + let value: ArticleEntity = await querybuilder + .where('article.id = :id', { id }) + .getOne() + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + if (entity) { + entity['stock'] = (entity.inventoryStock ?? 0) - (entity['totalAmount'] ?? 0); + } + + return Promise.resolve(entity ? this.articleRepository.transform(entity) : null); + }) + .catch((error) => Promise.reject(error)); + + return value; + } + + + async getAll( + relations: string[] = [], + throwsException = true, + ): Promise { + + let querybuilder = this.getQueryBuilder(); + + let value: ArticleEntity[] = await querybuilder + .getMany() + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + entity.map((m) => (m['stock'] = (m.inventoryStock ?? 0) - (m['totalAmount'] ?? 0))); + + return Promise.resolve(entity ? this.articleRepository.transformMany(entity) : null); + }) + .catch((error) => Promise.reject(error)); + + return value; + } + + async getByCode( + code: string, + throwsException = true, + ): Promise { + + let querybuilder = this.getQueryBuilder(); + + let value: ArticleEntity = await querybuilder + .where('article.code = :code', { code }) + .getOne() + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + if (entity) { + entity['stock'] = (entity.inventoryStock ?? 0) - (entity['totalAmount'] ?? 0); + } + return Promise.resolve(entity ? this.articleRepository.transform(entity) : null); + }) + .catch((error) => Promise.reject(error)); + + return value; + } + + getQueryBuilder() { + return this.articleRepository + .createQueryBuilder('article') + .addSelect( + (subquery) => + subquery + .select('COALESCE(SUM(orderEntry.amount), 0)') + .from(OrderEntry, 'orderEntry') + .where('orderEntry.articleId = article.id') + .andWhere('orderEntry.createdAt >= article.inventoryDate'), + 'article_totalAmount', + ) + .leftJoinAndSelect('article.articleGroup', 'articlegroup'); + } + + + async importCsv(preview: boolean, file: Express.Multer.File) { + + const entities = await this.csvParser.parse(Readable.from(file.buffer.toString()), ArticleCsvEntity); + const articleUpdate = await this.checkCSV(entities.list); + + if (preview) { + return articleUpdate; + } else { + + const [cRet, cRetFailed] = await this.createArticlesFromImport(articleUpdate.newArticle); + const [uRet, uRetFailed] = await this.updateArticlesFromImport(articleUpdate.updateableArticle); + + return [[...cRet, ...uRet], [...cRetFailed, ...uRetFailed]]; + } + } + + async updateArticlesFromImport(updateableArticle: ArticleCsvEntity[]) { + let successArticles = []; + let failedArticles = []; + + for (let index = 0; index < updateableArticle.length; index++) { + const element = updateableArticle[index]; + try { + + let per = (+(element.pe.replace(',', '.')) || 1); + per = per === 0 ? 1 : per; + await this.articleRepository.update({ code: element.barcode }, { + name: element.artikelbez, + unit: element.unit, + price: this.ceilNumber(+(element.brutto.replace(',', '.')) / per), + netto: this.ceilNumber(+(element.netto.replace(',', '.')) / per), + type: element.bezeichnung2, + artNumber: element.verkaufsobjekt, + }); + successArticles.push(element); + + } catch (error) { + failedArticles.push(element); + } + } + + return [successArticles, failedArticles]; + + } + + ceilNumber(value) { + return Math.ceil(value * 100) / 100; + } + + async createArticlesFromImport(newArticle: ArticleCsvEntity[]) { + + let successArticles = []; + let failedArticles = []; + + for (let index = 0; index < newArticle.length; index++) { + const element = newArticle[index]; + let article = new Article(); + article.code = element.barcode; + let per = (+(element.pe.replace(',', '.')) || 1); + per = per === 0 ? 1 : per; + try { + + article.name = element.artikelbez; + article.unit = element.unit; + article.price = this.ceilNumber(+(element.brutto.replace(',', '.')) / per); + article.netto = this.ceilNumber(+(element.netto.replace(',', '.')) / per); + article.type = element.bezeichnung2; + article.artNumber = element.verkaufsobjekt; + + await this.articleRepository.insert(article); + successArticles.push(element); + + } catch (error) { + failedArticles.push(element); + } + } + + return [successArticles, failedArticles]; + + } + + + async checkCSV(entries: ArticleCsvEntity[]) { + + const articles = await this.getAll(); + const newArticle = []; + const updateableArticle = []; + + entries.forEach((element: ArticleCsvEntity) => { + let find = articles.find((a) => a.code === element.barcode); + if (find) { + updateableArticle.push(element); + } else { + newArticle.push(element); + } + }); + + let retValue = { + articleCount: updateableArticle.length + newArticle.length, + newArticle, + updateableArticle, + }; + + return retValue; + } + + + async update(id: number, inputs: UpdateArticleDto): Promise { + const { articleGroup, ...rest } = inputs; + // TypeORM's update() expects the FK column directly for ManyToOne relations. + // Passing { articleGroup: { id } } as a nested object is silently ignored in + // some TypeORM 0.3 versions. We pass it explicitly as a relation reference. + const updateData: any = { ...rest }; + if (articleGroup?.id !== undefined) { + updateData.articleGroup = { id: articleGroup.id }; + } + return this.articleRepository.updateEntity(id, updateData); + } + + async makeInventory(article: ArticleEntity, newStock: number): Promise { + + if (newStock === undefined || newStock < 0) { + throw new BadRequestException('newStock Parameter nicht angegeben oder kleiner 0'); + } + + if (!article.trackStock) { + throw new BadRequestException('Artikel Lagerstand wird nicht überwacht'); + } + + const articleStock = article.stock; + + // Bug #5 fix: wrap inventory creation in try/catch so a failed inventory entry + // does not prevent the article stock from being updated + try { + await this.inventoryService.create( + { + amountNew: newStock, + diff: articleStock - newStock, + article: article, + } + ); + } catch (error) { + // Inventory log entry failed — continue so the article stock is still updated + } + + await this.articleRepository.updateEntity(article.id, { + inventoryStock: newStock, + inventoryDate: new Date(), + }); + + return this.getByCode(article.code); + + } + + async getByName( + name: string, + relations: string[] = [], + throwsException = false, + ): Promise { + return this.articleRepository + .findOne({ + where: { name: name }, + relations, + }) + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + + return Promise.resolve( + entity ? this.articleRepository.transform(entity) : null, + ); + }) + .catch((error) => Promise.reject(error)); + } + +} diff --git a/apps/server/src/models/article/controllers/article-group.controller.ts b/apps/server/src/models/article/controllers/article-group.controller.ts new file mode 100644 index 0000000..23bcc59 --- /dev/null +++ b/apps/server/src/models/article/controllers/article-group.controller.ts @@ -0,0 +1,78 @@ +import { + Get, + Post, + Body, + Controller, + UseInterceptors, + SerializeOptions, + ClassSerializerInterceptor, + Param, + Delete, + Patch, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiExtraModels, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../../common/res.model'; +import { ApiReS } from '../../../common/decorators/apires.decorator'; +import { ArticleGroupService } from '../article-group.service'; +import { ArticleGroupEntity, extendedArticleGroupForSerializing } from '../serializers/article-group.serializer'; +import { CreateArticleGroupDto } from '../dto/create-article-group.dto'; +import { UpdateArticleGroupDto } from '../dto/update-article-group.dto'; + +@ApiBearerAuth() +@Controller('articlegroups') +@ApiTags('article-group') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ + groups: extendedArticleGroupForSerializing, +}) +export class ArticleGroupController { + constructor( + private readonly articleGroupService: ArticleGroupService, + ) { + } + + @Get('/:id') + @ApiOperation({ + summary: 'Get specific article group', + description: 'Fetchs data of id', + }) + async get(@Param('id') id: number): Promise> { + return ReS.FromData(await this.articleGroupService.get(id)); + } + + @Get() + @ApiOperation({ + summary: 'Get all article groups', + description: 'Fetchs all data', + }) + async getAll(): Promise> { + return ReS.FromData(await this.articleGroupService.getAll()); + } + + @Post() + @ApiOperation({ + summary: 'Create new article group', + description: 'Create new article group', + }) + async post(@Body() articleGroup: CreateArticleGroupDto): Promise> { + return ReS.FromData(await this.articleGroupService.create(articleGroup)); + } + + @Patch(':id') + @ApiOperation({ + summary: 'Update article group', + description: 'Update article froup from ID', + }) + async update(@Param('id') id: number, @Body() updateArticleGroupDto: UpdateArticleGroupDto) { + return ReS.FromData(await this.articleGroupService.update(id, updateArticleGroupDto)); + } + +} diff --git a/apps/server/src/models/article/controllers/article.controller.ts b/apps/server/src/models/article/controllers/article.controller.ts new file mode 100644 index 0000000..c8ff248 --- /dev/null +++ b/apps/server/src/models/article/controllers/article.controller.ts @@ -0,0 +1,151 @@ +import { + Get, + Post, + Body, + Controller, + UseInterceptors, + SerializeOptions, + ClassSerializerInterceptor, + Param, + Delete, + Patch, + Query, + UploadedFile, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiBody, + ApiConsumes, + ApiExtraModels, + ApiOperation, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../../common/res.model'; +import { ArticleService } from '../article.service'; +import { ArticleEntity, extendedArticleForSerializing } from '../serializers/article.serializer'; +import { CreateArticleDto } from '../dto/create-article.dto'; +import { UpdateArticleDto } from '../dto/update-article.dto'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { memoryStorage } from 'multer'; +import { UpdateInventoryDto } from '../dto/update-inventory.dto'; + + +@ApiBearerAuth() +@Controller('articles') +@ApiTags('articles') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ + groups: extendedArticleForSerializing, +}) + +export class ArticleController { + constructor( + private readonly articleService: ArticleService, + ) { + } + + @Get('/:id') + @ApiOperation({ + summary: 'Get specific customer', + description: 'Fetchs data of id', + }) + async get(@Param('id') id: number): Promise> { + return ReS.FromData(await this.articleService.get(id)); + } + + @Get() + @ApiOperation({ + summary: 'Get all articles', + description: 'Fetchs all data', + }) + async getAll( + @Query('code') code?: string, + @Query('id') id?: number, + ): Promise> { + if (code) { + return ReS.FromData([await this.articleService.getByCode(code)]); + } else if (id) { + return ReS.FromData([await this.articleService.get(id)]); + } else { + return ReS.FromData(await this.articleService.getAll()); + } + } + + @Post() + @ApiOperation({ + summary: 'Create new Article', + description: 'Create new Article', + }) + async post(@Body() article: CreateArticleDto): Promise> { + console.log(article); + return ReS.FromData(await this.articleService.create(article)); + } + + @Post('/:id/inventory') + @ApiOperation({ + summary: 'Create article inventory', + description: 'Create article inventory', + }) + async postInventory( + @Param('id') id: number, + @Body() newStock: UpdateInventoryDto): Promise> { + const article = await this.articleService.get(id); + return ReS.FromData(await this.articleService.makeInventory(article, newStock.newStock)); + } + + @Post('import') + @ApiOperation({ + summary: 'Create new Article', + description: 'Create new Article', + }) + @ApiConsumes('multipart/form-data') + @ApiQuery({ name: 'preview' }) + @ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }) + @UseInterceptors(FileInterceptor('file', { storage: memoryStorage() })) + @SerializeOptions({ + ignoreDecorators: true, + }) + + async importCvs( + @Query('preview') preview: boolean, + @UploadedFile() file: Express.Multer.File, + ): Promise { + + return ReS.FromData(await this.articleService.importCsv(preview, file)); + + } + + @Patch(':id') + @ApiOperation({ + summary: 'Update Article', + description: 'Update Article from ID', + }) + async update(@Param('id') id: number, @Body() updateArticleDto: UpdateArticleDto) { + return ReS.FromData(await this.articleService.update(id, updateArticleDto)); + } + + @Delete(':id') + @ApiOperation({ + summary: 'Delete Article', + description: 'Delete article by id', + }) + async delete(@Param('id') id: number): Promise> { + await this.articleService.delete(id, true); + return ReS.FromData(null); + } + +} diff --git a/apps/server/src/models/article/dto/create-article-group.dto.ts b/apps/server/src/models/article/dto/create-article-group.dto.ts new file mode 100644 index 0000000..115982a --- /dev/null +++ b/apps/server/src/models/article/dto/create-article-group.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty } from 'class-validator'; + +export class CreateArticleGroupDto { + @ApiProperty({ + example: 'Schrauben', + description: 'Name of group', + }) + @IsNotEmpty() + name: string; +} diff --git a/apps/server/src/models/article/dto/create-article.dto.ts b/apps/server/src/models/article/dto/create-article.dto.ts new file mode 100644 index 0000000..e5bae6f --- /dev/null +++ b/apps/server/src/models/article/dto/create-article.dto.ts @@ -0,0 +1,85 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsOptional, ValidateNested } from 'class-validator'; +import { IdDto } from '../../../common/dto/id.dto'; + +export class CreateArticleDto { + @ApiProperty({ + example: '555555', + description: 'Name', + }) + @IsNotEmpty() + name: string; + + @ApiProperty({ + example: 'M555555', + description: 'Barcode', + }) + @IsNotEmpty() + code: string; + + @ApiProperty({ + example: '4.55', + description: 'Price of one unit', + }) + @IsNotEmpty() + price: number; + + @ApiProperty({ + example: 'Schraube', + description: 'Type', + }) + @IsNotEmpty() + type: string; + + @ApiProperty({ + example: 'pc', + description: 'Unit of article', + }) + @IsNotEmpty() + unit: string; + + @ApiProperty({ + example: '33234', + description: 'Article Number from Supplier', + }) + @IsNotEmpty() + artNumber: string; + + @ApiProperty({ + type: IdDto, + }) + @IsNotEmpty() + @ValidateNested() + @Type(() => IdDto) + articleGroup: IdDto; + + @ApiProperty({ example: 'Schraubenlieferant GmbH', description: 'Lieferant' }) + @IsOptional() + supplier: string; + + @ApiProperty({ example: 'Hochfeste Schraube M8', description: 'Beschreibung' }) + @IsOptional() + description: string; + + @ApiProperty({ + example: 'true', + description: 'Do not sum up position', + }) + @IsOptional() + singlePos: boolean; + + @ApiProperty({ + example: 'true', + description: 'Do not track the stock', + }) + @IsOptional() + trackStock: boolean; + + @ApiProperty({ + example: 'true', + description: 'no general Discount', + }) + @IsOptional() + noDiscount: boolean; +} diff --git a/apps/server/src/models/article/dto/update-article-group.dto.ts b/apps/server/src/models/article/dto/update-article-group.dto.ts new file mode 100644 index 0000000..af37ff4 --- /dev/null +++ b/apps/server/src/models/article/dto/update-article-group.dto.ts @@ -0,0 +1,5 @@ +import { CreateArticleGroupDto } from './create-article-group.dto'; + +export class UpdateArticleGroupDto extends CreateArticleGroupDto { + +} diff --git a/apps/server/src/models/article/dto/update-article.dto.ts b/apps/server/src/models/article/dto/update-article.dto.ts new file mode 100644 index 0000000..9ede858 --- /dev/null +++ b/apps/server/src/models/article/dto/update-article.dto.ts @@ -0,0 +1,7 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateArticleDto } from './create-article.dto'; + +// PartialType macht alle Felder optional + behält alle Validator-Decorators bei. +// Nötig für PATCH: CreateArticleDto hat @IsNotEmpty() auf allen Feldern — diese +// würden bei leeren Strings (type:"", unit:"" etc.) die Validation zum Scheitern bringen. +export class UpdateArticleDto extends PartialType(CreateArticleDto) {} diff --git a/apps/server/src/models/article/dto/update-inventory.dto.ts b/apps/server/src/models/article/dto/update-inventory.dto.ts new file mode 100644 index 0000000..44574f1 --- /dev/null +++ b/apps/server/src/models/article/dto/update-inventory.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber } from 'class-validator'; + +export class UpdateInventoryDto { + @ApiProperty({ + example: '5', + description: 'New Inventory Stock', + }) + @IsNotEmpty() + @IsNumber() + newStock: number; +} diff --git a/apps/server/src/models/article/entities/article-group.entity.ts b/apps/server/src/models/article/entities/article-group.entity.ts new file mode 100644 index 0000000..e1cf495 --- /dev/null +++ b/apps/server/src/models/article/entities/article-group.entity.ts @@ -0,0 +1,34 @@ +import { Expose } from 'class-transformer'; +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + OneToMany, +} from 'typeorm'; +import { IArticleGroup } from '../interfaces/article-group.interface'; +import { Article } from './article.entity'; + + +@Entity({ name: 'article-groups' }) +export class ArticleGroup implements IArticleGroup { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ nullable: false, length: 50 }) + name!: string; + + @OneToMany(() => Article, (article) => article.articleGroup) + articles: Article[]; + + @CreateDateColumn({ + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/apps/server/src/models/article/entities/article-import.csv-entity.ts b/apps/server/src/models/article/entities/article-import.csv-entity.ts new file mode 100644 index 0000000..d7e09fb --- /dev/null +++ b/apps/server/src/models/article/entities/article-import.csv-entity.ts @@ -0,0 +1,42 @@ +//Verkaufsobjekt; +//Artikelbez; +//Bez.2; +//Barcode; +//MEH; +//Listenpreis EUR/EH; +//Rabatt %;EUR Netto/EH;Brutto;Rabatt;Netto;PE;Preisherkunft;Sortiment; + +import { CsvKey } from 'nest-csv-parser'; + + +export class ArticleCsvEntity { + + @CsvKey('Verkaufsobjekt') + verkaufsobjekt: string; + + @CsvKey('Artikelbez') + artikelbez: string; + + @CsvKey('Bez. 2') + bezeichnung2: string; + + @CsvKey('Barcode') + barcode: string; + + @CsvKey('MEH') + unit: string; + + @CsvKey('Brutto') + brutto: string; + + @CsvKey('Netto') + netto: string; + + @CsvKey('per') + pe: string; + + constructor(partial: Partial) { + Object.assign(this, partial); + } + +} diff --git a/apps/server/src/models/article/entities/article.entity.ts b/apps/server/src/models/article/entities/article.entity.ts new file mode 100644 index 0000000..f596745 --- /dev/null +++ b/apps/server/src/models/article/entities/article.entity.ts @@ -0,0 +1,85 @@ +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + OneToMany, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { OrderEntry } from '../../bills/entities/order-entry.entity'; +import { IArticle } from '../interfaces/article.interface'; +import { ArticleGroup } from './article-group.entity'; + +@Entity({ name: 'article' }) +export class Article implements IArticle { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ nullable: false, length: 50 }) + name!: string; + + @Column({ nullable: false, unique: true, length: 50 }) + code!: string; + + @Column({ nullable: false, default: 0.0, precision: 5, scale: 2 }) + price!: number; + + @Column({ nullable: false, default: 0.0, precision: 5, scale: 2 }) + netto!: number; + + @Column({ default: 0 }) + inventoryStock: number; + + // Bug #4 fix: was @CreateDateColumn() which auto-sets on INSERT — should be a plain nullable column + @Column({ type: 'datetime', nullable: true }) + inventoryDate: Date; + + @Column({ default: '', length: 50 }) + type: string; + + @Column({ default: '', length: 250, nullable: true }) + description: string; + + @Column({ default: '', length: 100, nullable: true }) + supplier: string; + + @Column({ default: '', length: 250 }) + imgPath: string; + + @Column({ default: '', length: 50 }) + unit: string; + + @Column({ default: '', length: 50 }) + artNumber: string; + + @Column('boolean', { default: false }) + singlePos: boolean = false; + + @Column('boolean', { default: true }) + trackStock: boolean = true; + + @Column('boolean', { default: false }) + noDiscount: boolean = false; + + @OneToMany(() => OrderEntry, (orderEntry) => orderEntry.article) + orderEntry: OrderEntry[]; + + @ManyToOne(() => ArticleGroup) + @JoinColumn() + articleGroup: ArticleGroup; + + @CreateDateColumn({ + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + name: 'updated_at', + }) + updatedAt: Date; + + @Column({ select: false, insert: false, readonly: true, update: false, nullable: true }) + totalAmount: number; +} diff --git a/apps/server/src/models/article/entities/inventory.entity.ts b/apps/server/src/models/article/entities/inventory.entity.ts new file mode 100644 index 0000000..3d41b76 --- /dev/null +++ b/apps/server/src/models/article/entities/inventory.entity.ts @@ -0,0 +1,37 @@ +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { IInventory } from '../interfaces/inventory.interface'; +import { Article } from './article.entity'; + +@Entity({ name: 'inventory' }) +export class Inventory implements IInventory { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ nullable: false, default: 0.0, precision: 5, scale: 2 }) + amountNew!: number; + + @Column({ nullable: false, default: 0.0, precision: 5, scale: 2 }) + diff!: number; + + @ManyToOne(() => Article) + @JoinColumn() + article: Article; + + @CreateDateColumn({ + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/apps/server/src/models/article/interfaces/article-group.interface.ts b/apps/server/src/models/article/interfaces/article-group.interface.ts new file mode 100644 index 0000000..1d7c67e --- /dev/null +++ b/apps/server/src/models/article/interfaces/article-group.interface.ts @@ -0,0 +1,3 @@ +export interface IArticleGroup { + name: string; +} diff --git a/apps/server/src/models/article/interfaces/article.interface.ts b/apps/server/src/models/article/interfaces/article.interface.ts new file mode 100644 index 0000000..b41d219 --- /dev/null +++ b/apps/server/src/models/article/interfaces/article.interface.ts @@ -0,0 +1,18 @@ +import { ArticleGroup } from '../entities/article-group.entity'; +import { IArticleGroup } from './article-group.interface'; + +export interface IArticle { + name: string; + code: string; + price: number; + inventoryStock: number; + inventoryDate: Date; + type: string; + imgPath: string; + unit: string; + artNumber: string; + articleGroup: IArticleGroup; + singlePos: boolean; + trackStock: boolean; + noDiscount: boolean; +} diff --git a/apps/server/src/models/article/interfaces/inventory.interface.ts b/apps/server/src/models/article/interfaces/inventory.interface.ts new file mode 100644 index 0000000..b45c63a --- /dev/null +++ b/apps/server/src/models/article/interfaces/inventory.interface.ts @@ -0,0 +1,10 @@ +import { IArticle } from './article.interface'; + +export interface IInventory { + id: number; + amountNew: number; + diff: number; + article: IArticle; + createdAt: Date; + updatedAt: Date; +} diff --git a/apps/server/src/models/article/inventory.repository.ts b/apps/server/src/models/article/inventory.repository.ts new file mode 100644 index 0000000..78516fc --- /dev/null +++ b/apps/server/src/models/article/inventory.repository.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ClassTransformOptions } from '@nestjs/common/interfaces/external/class-transform-options.interface'; +import { ModelRepository } from '../model.repository'; +import { Inventory } from './entities/inventory.entity'; +import { allInventoryForSerializing, InventoryEntity, extendedInventoryForSerializing } from './serializers/inventory.serializer'; + +@Injectable() +export class InventoryRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(Inventory, dataSource.createEntityManager()); + } + + transform(model: Inventory): InventoryEntity { + const tranformOptions: ClassTransformOptions = { + groups: extendedInventoryForSerializing, + }; + return plainToInstance( + InventoryEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: Inventory[]): InventoryEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/article/inventory.service.ts b/apps/server/src/models/article/inventory.service.ts new file mode 100644 index 0000000..b8e29af --- /dev/null +++ b/apps/server/src/models/article/inventory.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; +import { BaseService } from '../../common/base.service'; +import { InventoryRepository } from './inventory.repository'; +import { Inventory } from './entities/inventory.entity'; +import { InventoryEntity } from './serializers/inventory.serializer'; + +@Injectable() +export class InventoryService extends BaseService { + constructor( + private readonly inventoryRepository: InventoryRepository, + ) { + super(inventoryRepository); + } +} diff --git a/apps/server/src/models/article/serializers/article-group.serializer.ts b/apps/server/src/models/article/serializers/article-group.serializer.ts new file mode 100644 index 0000000..fee2af3 --- /dev/null +++ b/apps/server/src/models/article/serializers/article-group.serializer.ts @@ -0,0 +1,26 @@ +import { Expose, Type } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { IArticleGroup } from '../interfaces/article-group.interface'; + +export const defaultArticleGroupForSerializing: string[] = [ + 'articleGroup.timestamps', + 'default', +]; +export const extendedArticleGroupForSerializing: string[] = [ + ...defaultArticleGroupForSerializing, +]; +export const allArticleGroupForSerializing: string[] = [ + ...extendedArticleGroupForSerializing, +]; +export class ArticleGroupEntity + extends ModelEntity + implements IArticleGroup { + + @Expose({ groups: ['default'] }) + name: string; + + @Expose({ groups: ['default', 'articleGroup.timestamps'] }) + createdAt: Date; + @Expose({ groups: ['default', 'articleGroup.timestamps'] }) + updatedAt: Date; +} diff --git a/apps/server/src/models/article/serializers/article.serializer.ts b/apps/server/src/models/article/serializers/article.serializer.ts new file mode 100644 index 0000000..5747013 --- /dev/null +++ b/apps/server/src/models/article/serializers/article.serializer.ts @@ -0,0 +1,63 @@ +import { Expose, Type } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { IArticle } from '../interfaces/article.interface'; +import { ArticleGroupEntity } from './article-group.serializer'; + +export const defaultArticleForSerializing: string[] = [ + 'bucketCategory.timestamps', + 'default', +]; +export const extendedArticleForSerializing: string[] = [ + ...defaultArticleForSerializing, +]; +export const allArticleForSerializing: string[] = [ + ...extendedArticleForSerializing, +]; +export class ArticleEntity + extends ModelEntity + implements IArticle { + @Expose({ groups: ['default'] }) + name: string; + @Expose({ groups: ['default'] }) + code: string; + @Expose({ groups: ['default'] }) + inventoryStock: number; + @Expose({ groups: ['default'] }) + inventoryDate: Date; + @Expose({ groups: ['default'] }) + imgPath: string; + @Expose({ groups: ['default'] }) + unit: string; + @Expose({ groups: ['default'] }) + artNumber: string; + + @Expose({ groups: ['default'] }) + stock: number; + + @Expose({ groups: ['default'] }) + @Type(() => ArticleGroupEntity) + articleGroup: ArticleGroupEntity; + + @Expose({ groups: ['default'] }) + description: string; + + @Expose({ groups: ['default'] }) + supplier: string; + + @Expose({ groups: ['default'] }) + singlePos: boolean; + @Expose({ groups: ['default'] }) + trackStock: boolean; + @Expose({ groups: ['default'] }) + noDiscount: boolean; + + @Expose({ groups: ['default'] }) + price: number; + @Expose({ groups: ['default'] }) + type: string; + + @Expose({ groups: ['default', 'bucketCategory.timestamps'] }) + createdAt: Date; + @Expose({ groups: ['default', 'bucketCategory.timestamps'] }) + updatedAt: Date; +} diff --git a/apps/server/src/models/article/serializers/inventory.serializer.ts b/apps/server/src/models/article/serializers/inventory.serializer.ts new file mode 100644 index 0000000..7e0d324 --- /dev/null +++ b/apps/server/src/models/article/serializers/inventory.serializer.ts @@ -0,0 +1,34 @@ +import { Expose, Type } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { IInventory } from '../interfaces/inventory.interface'; +import { ArticleEntity } from './article.serializer'; + +export const defaultInventoryForSerializing: string[] = [ + 'inventory.timestamps', + 'default', +]; +export const extendedInventoryForSerializing: string[] = [ + ...defaultInventoryForSerializing, +]; +export const allInventoryForSerializing: string[] = [ + ...extendedInventoryForSerializing, +]; +export class InventoryEntity + extends ModelEntity + implements IInventory { + + @Expose({ groups: ['default'] }) + amountNew: number; + + @Expose({ groups: ['default'] }) + diff: number; + + @Expose({ groups: ['default'] }) + @Type(() => ArticleEntity) + article: ArticleEntity; + + @Expose({ groups: ['default', 'inventory.timestamps'] }) + createdAt: Date; + @Expose({ groups: ['default', 'inventory.timestamps'] }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/annotation.repository.ts b/apps/server/src/models/bills/annotation.repository.ts new file mode 100644 index 0000000..8b60b12 --- /dev/null +++ b/apps/server/src/models/bills/annotation.repository.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ModelRepository } from '../model.repository'; +import { Annotation } from './entities/annotation.entity'; +import { allAnnotationForSerializing, AnnotationEntity } from './serializers/annotation.serializer'; + +@Injectable() +export class AnnotationRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(Annotation, dataSource.createEntityManager()); + } + + transform(model: Annotation): AnnotationEntity { + const tranformOptions = { + groups: allAnnotationForSerializing, + }; + return plainToInstance( + AnnotationEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: Annotation[]): AnnotationEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/bills/annotation.service.ts b/apps/server/src/models/bills/annotation.service.ts new file mode 100644 index 0000000..236100d --- /dev/null +++ b/apps/server/src/models/bills/annotation.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; +import { AnnotationRepository } from './annotation.repository'; +import { AnnotationEntity } from './serializers/annotation.serializer'; +import { BaseService } from '../../common/base.service'; +import { Annotation } from './entities/annotation.entity'; + +@Injectable() +export class AnnotationService extends BaseService { + constructor( + private readonly annotationRepository: AnnotationRepository, + ) { + super(annotationRepository); + } +} diff --git a/apps/server/src/models/bills/bill.module.ts b/apps/server/src/models/bills/bill.module.ts new file mode 100644 index 0000000..e7810ed --- /dev/null +++ b/apps/server/src/models/bills/bill.module.ts @@ -0,0 +1,70 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { BillService } from './bill.service'; +import { BillController } from './controllers/bill.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { BillRepository } from './bill.repository'; +import { OrderEntryService } from './order-entry.service'; +import { OrderEntryRepository } from './order-entry.repository'; +import { SlipsheetRepository } from './slipsheet.repository'; +import { SlipsheetService } from './slipsheet.service'; +import { ArticleModule } from '../article/article.module'; +import { SharedModule } from '../../common/shared.module'; +import { SlipsheetController } from './controllers/slipsheet.controller'; +import { AppConfigModule } from '../../config/app/config.module'; +import { AnnotationRepository } from './annotation.repository'; +import { AnnotationService } from './annotation.service'; +import { AnnotationController } from './controllers/annotation.controller'; +import { CustomerModule } from '../customer/customer.module'; +import { OrderEntryController } from './controllers/order-entry.controller'; +import { DiscountRepository } from './discount.repository'; +import { DiscountService } from './discount.service'; +import { DashboardController } from './controllers/dashboard.controller'; +import { DashboardService } from './dashboard.service'; +import { InventoryRepository } from '../article/inventory.repository'; +import { Bill } from './entities/bill.entity'; +import { Slipsheet } from './entities/slipsheet.entity'; +import { OrderEntry } from './entities/order-entry.entity'; +import { Annotation } from './entities/annotation.entity'; +import { Discount } from './entities/discount.entity'; +import { Inventory } from '../article/entities/inventory.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + Bill, + Slipsheet, + OrderEntry, + Annotation, + Discount, + Inventory, + ]), + ArticleModule, + AppConfigModule, + SharedModule, + forwardRef(() => CustomerModule), + ], + + controllers: [ + BillController, + SlipsheetController, + AnnotationController, + OrderEntryController, + DashboardController, + ], + providers: [ + BillRepository, + SlipsheetRepository, + OrderEntryRepository, + AnnotationRepository, + DiscountRepository, + // InventoryRepository is exported from ArticleModule — do not re-declare here + BillService, + OrderEntryService, + SlipsheetService, + AnnotationService, + DiscountService, + DashboardService, + ], + exports: [OrderEntryService, SlipsheetService, BillService, DiscountService], +}) +export class BillModule {} diff --git a/apps/server/src/models/bills/bill.repository.ts b/apps/server/src/models/bills/bill.repository.ts new file mode 100644 index 0000000..b636325 --- /dev/null +++ b/apps/server/src/models/bills/bill.repository.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ModelRepository } from '../model.repository'; +import { Bill } from './entities/bill.entity'; +import { allBillForSerializing, BillEntity } from './serializers/bill.serializer'; + +@Injectable() +export class BillRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(Bill, dataSource.createEntityManager()); + } + + transform(model: Bill): BillEntity { + const tranformOptions = { + groups: allBillForSerializing, + }; + return plainToInstance( + BillEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: Bill[]): BillEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/bills/bill.service.ts b/apps/server/src/models/bills/bill.service.ts new file mode 100644 index 0000000..90a7949 --- /dev/null +++ b/apps/server/src/models/bills/bill.service.ts @@ -0,0 +1,346 @@ +import { + BadRequestException, + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { CreateBillDto } from './dto/create-bill.dto'; +import { UpdateBillDto } from './dto/update-bill.dto'; +import { BillRepository } from './bill.repository'; +import { BillEntity } from './serializers/bill.serializer'; +import { EntityManager } from 'typeorm'; +import { BaseService } from '../../common/base.service'; +import { Bill } from './entities/bill.entity'; +import { SlipsheetEntity } from './serializers/slipsheet.serializer'; +import { SlipsheetState } from './enums/slipsheet-state.enum'; +import { SlipsheetService } from './slipsheet.service'; +import { BillState } from './enums/bill-state.enum'; +import { PdfMakerService } from '../../common/services/pdfmaker.service'; +import { join } from 'path'; +import { AppConfigService } from '../../config/app/config.service'; +import { Slipsheet } from './entities/slipsheet.entity'; +import { existsSync } from 'fs'; +import { unlink } from 'fs/promises'; + +@Injectable() +export class BillService extends BaseService { + + constructor( + private readonly billRepository: BillRepository, + private readonly slipsheetService: SlipsheetService, + private readonly pdfMakerService: PdfMakerService, + private readonly appConfigService: AppConfigService, + ) { + super(billRepository); + } + + + async generateBill( + slipsheetEntities: SlipsheetEntity[], + bill?: BillEntity, + ): Promise { + if (!slipsheetEntities?.length) { + throw new BadRequestException('Keine Lieferscheine angegeben'); + } + + const shoppingcartIds = slipsheetEntities.map((o) => o.id); + const firstCustomerId = slipsheetEntities[0]?.customer?.id; + + if (slipsheetEntities.some((cart) => cart.state !== SlipsheetState.CLOSED)) + throw new BadRequestException('Lieferscheine noch nicht erstellt'); + + if (!firstCustomerId || slipsheetEntities.some((cart) => cart.customer?.id !== firstCustomerId)) + throw new BadRequestException('Warenkorb von unterschiedlichen Kunden'); + + if ( + slipsheetEntities.some( + (cart) => !!cart.bill && (!bill || cart.bill.id !== bill.id), + ) + ) + throw new BadRequestException('Manche Lieferscheine haben schon eine Rechnung'); + + let firstBillEntity: BillEntity; + const createdNewBill = !bill; + + try { + if (!bill) { + firstBillEntity = await this.createBillWithRetry(); + } else { + firstBillEntity = await this.getAllInformations(bill.id); + if (!firstBillEntity) { + throw new NotFoundException('Rechnung nicht gefunden'); + } + } + + firstBillEntity.state = BillState.CLOSED; + if (!firstBillEntity.path) { + firstBillEntity.path = this.getPathName(firstBillEntity); + } + + await this.billRepository.manager.transaction(async (entityManager) => { + await entityManager.update(Bill, firstBillEntity.id, { + path: firstBillEntity.path, + state: firstBillEntity.state, + }); + await entityManager.update(Slipsheet, shoppingcartIds, { + billId: firstBillEntity.id, + state: SlipsheetState.CLOSED, + }); + }); + + firstBillEntity = await this.getAllInformations(firstBillEntity.id); + + await this.writeBillPdf(firstBillEntity); + + return firstBillEntity; + + } catch (error) { + console.error(error); + if (createdNewBill && firstBillEntity?.id) { + await this.rollbackCreatedBill(firstBillEntity.id, shoppingcartIds); + } + + throw error; + } + } + + async regenerateBillPdf(id: number): Promise { + const bill = await this.getAllInformations(id); + if (!bill) { + throw new NotFoundException('Rechnung nicht gefunden'); + } + + if (bill.state !== BillState.CLOSED) { + bill.state = BillState.CLOSED; + } + + if (!bill.path) { + bill.path = this.getPathName(bill); + } + + await this.billRepository.updateEntity(bill.id, { + path: bill.path, + state: bill.state, + }); + + await this.writeBillPdf(bill); + return this.getAllInformations(id); + } + + async updateBill(id: number, inputs: UpdateBillDto): Promise { + const existingBill = await this.getAllInformations(id); + if (!existingBill) { + throw new NotFoundException('Rechnung nicht gefunden'); + } + + const nextBillNumber = inputs.billNumber?.trim() || existingBill.billNumber; + const previousPath = existingBill.path; + + await this.billRepository.updateEntity(existingBill.id, { + billNumber: nextBillNumber, + billDate: this.normalizeBillDate(inputs.billDate), + path: this.getPathName({ + ...existingBill, + billNumber: nextBillNumber, + } as BillEntity), + }); + + const updatedBill = await this.regenerateBillPdf(id); + + if (previousPath && previousPath !== updatedBill.path) { + await this.deletePdfFile(previousPath); + } + + return updatedBill; + } + + async releaseBill(id: number): Promise { + const bill = await this.getAllInformations(id); + if (!bill) { + throw new NotFoundException('Rechnung nicht gefunden'); + } + + const slipsheetIds = (bill.slipsheets || []).map((slipsheet) => slipsheet.id); + + await this.billRepository.manager.transaction(async (entityManager) => { + if (slipsheetIds.length) { + await entityManager.update(Slipsheet, slipsheetIds, { + billId: null, + state: SlipsheetState.CLOSED, + }); + } + + await entityManager.delete(Bill, bill.id); + }); + + await this.deletePdfFile(bill.path); + } + + getAllInformations(id: number): Promise { + return this.get(id, + [ + 'slipsheets', + 'slipsheets.customer', + 'slipsheets.orderEntries', + 'slipsheets.orderEntries.article', + 'slipsheets.orderEntries.article.articleGroup', + 'slipsheets.annotations'], + ); + } + + getAllAllInformations(): Promise { + return this.getAll( + [ + 'slipsheets', + 'slipsheets.customer', + 'slipsheets.orderEntries', + 'slipsheets.orderEntries.article', + 'slipsheets.orderEntries.article.articleGroup', + 'slipsheets.annotations'], + ); + } + + async generateNumber(entityManager: EntityManager = this.billRepository.manager): Promise { + const lastEntry = await entityManager.query(` + SELECT "Bill"."billNumber" AS "billNumber" + FROM "bill" "Bill" WHERE NOT("Bill"."billNumber" IS NULL) and "Bill"."billNumber" not like '%/%' + ORDER BY CAST("Bill"."billNumber" as INTEGER) DESC Limit 1`); + if (lastEntry?.length != 0) { + const number = +lastEntry[0]?.billNumber || 0; + return (number + 1).toString(); + } else { + return '1'; + } + } + + getPathName(bill: BillEntity): string { + return 'R_' + bill.billNumber + '.pdf'; + } + + + async findFromCustomer(customerId: number): Promise { + const bills = await this.billRepository + .createQueryBuilder('bill') + .leftJoinAndSelect('bill.slipsheets', 'slipsheet') + .leftJoinAndSelect('slipsheet.customer', 'customer') + .leftJoinAndSelect('slipsheet.orderEntries', 'orderEntries') + .leftJoinAndSelect('orderEntries.article', 'article') + .leftJoinAndSelect('article.articleGroup', 'articleGroup') + .leftJoinAndSelect('slipsheet.annotations', 'annotations') + .where('slipsheet.customerId = :customerId', { customerId }) + .getMany(); + + return this.billRepository.transformMany(bills); + } + + async findByState(state: BillState): Promise { + return this.billRepository.findAll({ + filter: { state }, + relations: [ + 'slipsheets', + 'slipsheets.customer', + 'slipsheets.orderEntries', + 'slipsheets.orderEntries.article', + 'slipsheets.orderEntries.article.articleGroup', + 'slipsheets.annotations', + ], + }); + } + + + async changed(billId: number) { + return this.update(billId, { state: BillState.OPEN }); + } + + private async createBillWithRetry(maxAttempts = 5): Promise { + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + const billNumber = await this.generateNumber(); + try { + const bill = await this.billRepository.createEntity({ + billNumber, + billDate: new Date(), + path: this.getPathName({ billNumber } as BillEntity), + state: BillState.CLOSED, + }); + return bill; + } catch (error) { + if (this.isUniqueConstraintError(error) && attempt < maxAttempts) { + continue; + } + throw error; + } + } + throw new InternalServerErrorException( + 'Rechnungsnummer konnte nicht eindeutig erzeugt werden', + ); + } + + private async rollbackCreatedBill( + billId: number, + slipsheetIds: number[], + ): Promise { + try { + await this.billRepository.manager.transaction(async (entityManager) => { + await entityManager + .createQueryBuilder() + .update(Slipsheet) + .set({ billId: null }) + .where('id IN (:...ids)', { ids: slipsheetIds }) + .andWhere('billId = :billId', { billId }) + .execute(); + await entityManager.delete(Bill, billId); + }); + } catch (rollbackError) { + console.error('Rollback created bill failed', rollbackError); + } + } + + private isUniqueConstraintError(error: any): boolean { + const code = error?.code || error?.errno; + const message = `${error?.message || ''}`.toLowerCase(); + return ( + code === 'SQLITE_CONSTRAINT' || + code === '23505' || + message.includes('unique constraint') + ); + } + + private normalizeBillDate(value: string | Date): Date { + if (value instanceof Date) { + return value; + } + + const [year, month, day] = `${value || ''}`.split('-').map((part) => +part); + if (year && month && day) { + return new Date(year, month - 1, day, 12, 0, 0, 0); + } + + return new Date(value); + } + + private async writeBillPdf(bill: BillEntity): Promise { + const retpdf = await this.pdfMakerService.generateBill(bill); + await this.pdfMakerService.savePDFToFileSystem( + retpdf, + join(this.appConfigService.pdf_bill_path, bill.path), + ); + } + + private async deletePdfFile(path?: string | null): Promise { + if (!path) { + return; + } + + const filepath = join(this.appConfigService.pdf_bill_path, path); + if (!existsSync(filepath)) { + return; + } + + try { + await unlink(filepath); + } catch (error) { + console.error('Delete bill PDF failed', error); + } + } + +} diff --git a/apps/server/src/models/bills/controllers/annotation.controller.ts b/apps/server/src/models/bills/controllers/annotation.controller.ts new file mode 100644 index 0000000..5d0aac3 --- /dev/null +++ b/apps/server/src/models/bills/controllers/annotation.controller.ts @@ -0,0 +1,78 @@ +import { + Get, + Post, + Body, + Controller, + UseInterceptors, + SerializeOptions, + ClassSerializerInterceptor, + Param, + Patch, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiExtraModels, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../../common/res.model'; +import { AnnotationService } from '../annotation.service'; +import { extendedAnnotationForSerializing, AnnotationEntity } from '../serializers/annotation.serializer'; +import { CreateAnnotationDto } from '../dto/create-annotation.dto'; +import { UpdateAnnotationDto } from '../dto/update-annotation.dto'; + +@ApiBearerAuth() +@Controller('annotation') +@ApiTags('annotation') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ + groups: extendedAnnotationForSerializing, +}) +export class AnnotationController { + constructor( + private readonly annotationService: AnnotationService, + ) { + + } + + @Get('/:id') + @ApiOperation({ + summary: 'Get specific annotation', + description: 'Fetchs data of id', + }) + async get(@Param('id') id: number): Promise> { + return ReS.FromData(await this.annotationService.get(id)); + } + + @Get() + @ApiOperation({ + summary: 'Get all annotations', + description: 'Fetchs all data', + }) + async getAll(): Promise> { + return ReS.FromData(await this.annotationService.getAll()); + } + + @Post() + @ApiOperation({ + summary: 'Create new annotation', + description: 'Create new annotation', + }) + async post(@Body() annotation: CreateAnnotationDto): Promise> { + console.log(annotation); + return ReS.FromData(await this.annotationService.create(annotation)); + } + + @Patch(':id') + @ApiOperation({ + summary: 'Update annotation', + description: 'Update annotation from ID', + }) + async update(@Param('id') id: number, @Body() updateArticleDto: UpdateAnnotationDto) { + return ReS.FromData(await this.annotationService.update(id, updateArticleDto)); + } + +} diff --git a/apps/server/src/models/bills/controllers/bill.controller.ts b/apps/server/src/models/bills/controllers/bill.controller.ts new file mode 100644 index 0000000..97d34f8 --- /dev/null +++ b/apps/server/src/models/bills/controllers/bill.controller.ts @@ -0,0 +1,150 @@ +import { + Get, + Post, + Body, + Controller, + UseInterceptors, + SerializeOptions, + ClassSerializerInterceptor, + Param, + Response, + Delete, + NotFoundException, + StreamableFile, + Query, + Put, +} from '@nestjs/common'; +import { + BillEntity, + extendedBillForSerializing, +} from '../serializers/bill.serializer'; +import { BillService } from '../bill.service'; +import { CreateBillDto } from '../dto/create-bill.dto'; +import { UpdateBillDto } from '../dto/update-bill.dto'; +import { + ApiBearerAuth, + ApiExtraModels, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../../common/res.model'; +import { ApiReS } from '../../../common/decorators/apires.decorator'; +import { createReadStream } from 'fs'; +import { existsSync } from 'fs'; +import { join } from 'path'; +import { BillState } from '../enums/bill-state.enum'; +import { AppConfigService } from '../../../config/app/config.service'; +import { SlipsheetEntity } from '../serializers/slipsheet.serializer'; +import { SlipsheetService } from '../slipsheet.service'; + +@ApiBearerAuth() +@Controller('bills') +@ApiTags('bills') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ + groups: extendedBillForSerializing, +}) +export class BillController { + constructor( + private readonly billService: BillService, + private readonly slipsheetService: SlipsheetService, + private readonly appConfigService: AppConfigService, + ) {} + + @Get('/:id') + async get(@Param('id') id: number): Promise> { + return ReS.FromData(await this.billService.getAllInformations(id)); + } + + @Get('/') + async getAll(@Query('state') state?: BillState): Promise> { + if (state) { + return ReS.FromData(await this.billService.findByState(state)); + } + return ReS.FromData(await this.billService.getAllAllInformations()); + } + + // Bug #2 fix: GET /:id/pdf must NOT regenerate the PDF (side-effect on GET). + // If the file is missing, return 404 and let the client call POST /:id to regenerate. + @Get('/:id/pdf') + @ApiOperation({ + summary: 'Download bill PDF', + description: 'Returns existing PDF. Returns 404 if not yet generated.', + }) + async getPDF( + @Param('id') id: number, + @Response({ passthrough: true }) res, + ): Promise { + const bill: BillEntity = await this.billService.getAllInformations(id); + + if (!bill.path) { + throw new NotFoundException( + 'Rechnung-PDF nicht gefunden. Bitte neu erzeugen.', + ); + } + + const filepath = join(this.appConfigService.pdf_bill_path, bill.path); + if (!existsSync(filepath)) { + throw new NotFoundException( + 'Rechnung-PDF nicht gefunden. Bitte neu erzeugen.', + ); + } + + const file = createReadStream(filepath); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Disposition': 'inline; filename="' + bill.path + '"', + }); + return new StreamableFile(file); + } + + @Post('/') + @ApiReS(CreateBillDto, 'The record has been successfully created.', 201) + async create(@Body() inputs: CreateBillDto): Promise> { + console.log(inputs); + return ReS.FromData(await this.billService.create(inputs)); + } + + @Post('/generate') + @ApiOperation({ + summary: 'Generate bill from slipsheets', + description: 'Creates a bill PDF from the given slipsheet IDs', + }) + async generateBill(@Body() slipsheets: number[]): Promise> { + const slipsheetEntities: SlipsheetEntity[] = + await this.slipsheetService.getAllInformations(slipsheets); + + const bill: BillEntity = await this.billService.generateBill( + slipsheetEntities, + ); + + return ReS.FromData(bill); + } + + @Post('/:id') + @ApiOperation({ + summary: 'Recreate bill PDF', + description: 'Regenerates PDF for existing bill', + }) + async recreateBill(@Param('id') id: number): Promise> { + return ReS.FromData(await this.billService.regenerateBillPdf(id)); + } + + @Put('/:id') + async update( + @Param('id') id: number, + @Body() + inputs: UpdateBillDto, + ): Promise> { + return ReS.FromData(await this.billService.updateBill(id, inputs)); + } + + @Delete('/:id') + async delete(@Param('id') id: number): Promise> { + await this.billService.releaseBill(id); + return ReS.FromData(null); + } +} diff --git a/apps/server/src/models/bills/controllers/dashboard.controller.ts b/apps/server/src/models/bills/controllers/dashboard.controller.ts new file mode 100644 index 0000000..054b5fd --- /dev/null +++ b/apps/server/src/models/bills/controllers/dashboard.controller.ts @@ -0,0 +1,58 @@ +import { + Get, + Controller, + Param, + forwardRef, + Inject, + Query, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiExtraModels, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../../common/res.model'; +import { AppConfigService } from '../../../config/app/config.service'; +import { SlipsheetService } from '../slipsheet.service'; +import { CustomerService } from '../../customer/customer.service'; +import { TimeRangeDto } from '../../../common/dto/time-range.dto'; +import { DashboardService } from '../dashboard.service'; +import { DashboardSummaryQueryDto } from '../dto/dashboard-summary-query.dto'; + +@ApiBearerAuth() +@Controller('dashboard') +@ApiTags('dashboard') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +export class DashboardController { + constructor( + private readonly slipsheetService: SlipsheetService, + private readonly dashboardService: DashboardService, + private readonly appConfigService: AppConfigService, + @Inject(forwardRef(() => CustomerService)) + private readonly customerService: CustomerService, + ) {} + + @Get('/summary') + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + async getSummary( + @Query() query: DashboardSummaryQueryDto, + ): Promise> { + return ReS.FromData(await this.dashboardService.getSummary(query)); + } + + @Get('/verbrauch/:customerId') + async get( + @Param('customerId') customerId: number, + @Query() timerange: TimeRangeDto, + ): Promise> { + + return ReS.FromData( + await this.slipsheetService.findFromCustomer(customerId, timerange), + ); + } + +} diff --git a/apps/server/src/models/bills/controllers/order-entry.controller.ts b/apps/server/src/models/bills/controllers/order-entry.controller.ts new file mode 100644 index 0000000..f14a1d5 --- /dev/null +++ b/apps/server/src/models/bills/controllers/order-entry.controller.ts @@ -0,0 +1,68 @@ +import { + Get, + Put, + Body, + Controller, + UseInterceptors, + SerializeOptions, + ClassSerializerInterceptor, + Param, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiExtraModels, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../../common/res.model'; +import { OrderEntryService } from '../order-entry.service'; +import { extendedOrderEntryForSerializing, OrderEntryEntity } from '../serializers/order-entry.serializer'; +import { UpdateOrderEntryDto } from '../dto/update-order-entry.dto'; + +@ApiBearerAuth() +@Controller('order-entries') +@ApiTags('order-entries') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ + groups: extendedOrderEntryForSerializing, +}) +export class OrderEntryController { + constructor( + private readonly orderEntryService: OrderEntryService, + ) { + + } + + @Get('/:id') + @ApiOperation({ + summary: 'Get specific order entry', + description: 'Fetchs data of id', + }) + async get(@Param('id') id: number): Promise> { + return ReS.FromData(await this.orderEntryService.get(id)); + } + + @Get() + @ApiOperation({ + summary: 'Get all orderEntrys', + description: 'Fetchs all data', + }) + async getAll(): Promise> { + return ReS.FromData(await this.orderEntryService.getAll()); + } + + @Put(':id') + @ApiOperation({ + summary: 'Update Order Article', + description: 'Update Order from ID', + }) + async update( + @Param('id') id: number, + @Body() updateOrderDto: UpdateOrderEntryDto) { + return ReS.FromData(await this.orderEntryService.update(id, updateOrderDto)); + } + +} diff --git a/apps/server/src/models/bills/controllers/slipsheet.controller.ts b/apps/server/src/models/bills/controllers/slipsheet.controller.ts new file mode 100644 index 0000000..2a0a72e --- /dev/null +++ b/apps/server/src/models/bills/controllers/slipsheet.controller.ts @@ -0,0 +1,234 @@ +import { + Get, + Post, + Body, + Controller, + UseInterceptors, + SerializeOptions, + ClassSerializerInterceptor, + Param, + Response, + Delete, + StreamableFile, + NotFoundException, + Query, + forwardRef, + Inject, + DefaultValuePipe, + ParseBoolPipe, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiExtraModels, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../../common/res.model'; +import { SlipsheetService } from '../slipsheet.service'; +import { extendedSlipsheetForSerializing, SlipsheetEntity } from '../serializers/slipsheet.serializer'; +import { createReadStream, existsSync } from 'fs'; +import { join } from 'path'; +import { AppConfigService } from '../../../config/app/config.service'; +import { SlipsheetState } from '../enums/slipsheet-state.enum'; +import { CreateAnnotationDto } from '../dto/create-annotation.dto'; +import { AnnotationEntity } from '../serializers/annotation.serializer'; +import { AnnotationService } from '../annotation.service'; +import { Slipsheet } from '../entities/slipsheet.entity'; +import { CustomerService } from '../../customer/customer.service'; +import { AddOrderEntryDto } from '../dto/add-order-entry.dto'; +import { OrderEntryEntity } from '../serializers/order-entry.serializer'; +import { OrderEntryService } from '../order-entry.service'; +import { PrinterService } from '../../../common/services/printer.service'; +import { BillService } from '../bill.service'; + +@ApiBearerAuth() +@Controller('slipsheets') +@ApiTags('slipsheets') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ + groups: extendedSlipsheetForSerializing, +}) +export class SlipsheetController { + constructor( + @Inject(forwardRef(() => SlipsheetService)) + private readonly slipsheetService: SlipsheetService, + private readonly billService: BillService, + private readonly orderEntryService: OrderEntryService, + private readonly annotationService: AnnotationService, + private readonly appConfigService: AppConfigService, + @Inject(forwardRef(() => CustomerService)) + private readonly customerService: CustomerService, + private readonly printerService: PrinterService, + ) { + + } + + @Get() + @ApiOperation({ + summary: 'Get all slipsheets', + description: 'Fetchs all data', + }) + async getAll( + @Query('customerId') customerId: number, + @Query('state') state?: SlipsheetState, + ): Promise> { + if (customerId) { + // Nur bestehende offene/geänderte LS zurückgeben — KEIN Auto-Anlegen + const slips = await this.slipsheetService.findFromCustomer(+customerId); + const open = slips.filter(s => s.state === SlipsheetState.OPEN || s.state === SlipsheetState.CHANGED); + return ReS.FromData(open); + } else if (state) + return ReS.FromData(await this.slipsheetService.findByState(state)); + else + return ReS.FromData(await this.slipsheetService.getAll(this.slipsheetService.getAllRelations())); + } + + // Bug #1 fix: cast id to number with +id to avoid string being passed to getAllInformations + @Get('/:id') + @ApiOperation({ + summary: 'Get specific slipsheet', + description: 'Fetchs data of id', + }) + async get(@Param('id') id: string): Promise> { + const slip = (await this.slipsheetService.getAllInformations([+id]))[0]; + if (!slip) { + throw new NotFoundException('Lieferschein nicht gefunden'); + } + return ReS.FromData(slip); + } + + @Get('/:id/pdf') + @ApiOperation({ + summary: 'Get slipsheet PDF', + description: 'Fetchs data of id', + }) + async getPDF( + @Param('id') id: number, + @Query('close', new DefaultValuePipe(true), ParseBoolPipe) close: boolean, + @Response({ passthrough: true }) res): Promise { + + let slip: SlipsheetEntity = await this.getOrGenerateSlipsheep(id, close); + + const file = createReadStream(join(this.appConfigService.pdf_slip_path, slip.path)); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Disposition': 'inline; filename="' + slip.path + '"', + }); + return new StreamableFile(file); + } + + @Post('/:id/print') + @ApiOperation({ + summary: 'Print PDF', + description: '', + }) + async printPDF( + @Param('id') id: number, + @Response({ passthrough: true }) res): Promise> { + + let slip: SlipsheetEntity = await this.getOrGenerateSlipsheep(id, true); + + return ReS.FromData(await this.printerService.print(join(this.appConfigService.pdf_slip_path, slip.path))); + } + + private async getOrGenerateSlipsheep(id: number, close: boolean) { + let slip: SlipsheetEntity = (await this.slipsheetService.getAllInformations([id]))[0]; + if (!slip.path || slip.state === SlipsheetState.OPEN || slip.state === SlipsheetState.CHANGED) { + slip = await this.slipsheetService.generateSlipsheet(id, close); + } else if (!existsSync(join(this.appConfigService.pdf_slip_path, slip.path))) { + slip = await this.slipsheetService.generateSlipsheet(id, close); + } + return slip; + } + + @Post('/:id/annotation') + @ApiOperation({ + summary: 'Add annotation to slipsheet', + description: 'Fetchs data of id', + }) + async addAnnotation( + @Param('id') id: number, + @Body() annotation: CreateAnnotationDto, + ): Promise> { + + const annotationToAdd = new AnnotationEntity(); + annotationToAdd.text = annotation.text; + annotationToAdd.slipsheet = await this.slipsheetService.get(id); + + await this.slipsheetService.changed(annotationToAdd.slipsheet); + if (annotationToAdd.slipsheet.billId) + await this.billService.changed(annotationToAdd.slipsheet.billId); + + return ReS.FromData(await this.annotationService.create(annotationToAdd)); + } + + @Post() + @ApiOperation({ + summary: 'Add article to open slipsheet', + description: 'Fetchs data of id', + }) + + async postOrderOpenSlipsheet( + @Body() addOrderEntryDto: AddOrderEntryDto): Promise> { + + let customer = await this.customerService.get(addOrderEntryDto.customer.id, ['discounts', 'discounts.articleGroup']); + let slipsheet = await this.slipsheetService.findOpenForCustomer(customer); + + const ret = await this.orderEntryService.addOrderToSlipsheet( + { + customer, + slipsheet, + addOrderEntry: addOrderEntryDto, + }); + console.log(ret); + return ReS.FromData((await this.slipsheetService.getAllInformations([ret.slipsheet.id]))[0]); + } + + @Post('/:id') + @ApiOperation({ + summary: 'Add article to existing slipsheet', + description: 'Fetchs data of id', + }) + + async postOrder( + @Param('id') id: number, + @Body() addOrderEntryDto: AddOrderEntryDto): Promise> { + + let slipsheet = (await this.slipsheetService.getAllInformations([id]))[0]; + + const ret = await this.orderEntryService.addOrderToSlipsheet( + { + customer: slipsheet.customer, + slipsheet: slipsheet, + addOrderEntry: addOrderEntryDto, + }); + return ReS.FromData((await this.slipsheetService.getAllInformations([ret.slipsheet.id]))[0]); + } + + @Delete('/:id') + @ApiOperation({ + summary: 'Leeren offenen Lieferschein löschen', + description: 'Löscht einen offenen Lieferschein ohne Positionen', + }) + async deleteSlipsheet(@Param('id') id: number): Promise> { + await this.slipsheetService.deleteEmpty(id); + return ReS.FromData(null); + } + + @Post('/:id/close') + @ApiOperation({ + summary: 'Close slipsheet and generate PDF', + description: 'Fetchs data of id', + }) + async closeSlipsheet( + @Param('id') id: number, + ): Promise> { + + let slip: SlipsheetEntity = await this.slipsheetService.generateSlipsheet(id, true); + return ReS.FromData(slip); + + } +} diff --git a/apps/server/src/models/bills/dashboard.service.ts b/apps/server/src/models/bills/dashboard.service.ts new file mode 100644 index 0000000..7d482ea --- /dev/null +++ b/apps/server/src/models/bills/dashboard.service.ts @@ -0,0 +1,549 @@ +import { Injectable } from '@nestjs/common'; +import { ArticleService } from '../article/article.service'; +import { InventoryRepository } from '../article/inventory.repository'; +import { BillRepository } from './bill.repository'; +import { OrderEntryRepository } from './order-entry.repository'; +import { SlipsheetRepository } from './slipsheet.repository'; +import { BillState } from './enums/bill-state.enum'; +import { SlipsheetState } from './enums/slipsheet-state.enum'; +import { DashboardSummaryQueryDto } from './dto/dashboard-summary-query.dto'; + +@Injectable() +export class DashboardService { + constructor( + private readonly articleService: ArticleService, + private readonly inventoryRepository: InventoryRepository, + private readonly slipsheetRepository: SlipsheetRepository, + private readonly billRepository: BillRepository, + private readonly orderEntryRepository: OrderEntryRepository, + ) {} + + async getSummary(query: DashboardSummaryQueryDto): Promise { + const filters = this.normalizeFilters(query); + const allowFallback = !query?.from && !query?.to; + const now = new Date(); + const todayStart = this.startOfDay(now); + const todayEnd = this.endOfDay(now); + const sevenDaysAgo = this.shiftDays(now, -7); + const thirtyDaysAgo = this.shiftDays(now, -30); + + const allArticles = await this.articleService.getAll(); + // Bug fix: TypeORM 0.3 count() requires { where: { ... } } wrapper + const openSlipsheetsCount = Number( + await this.slipsheetRepository.count({ where: { state: SlipsheetState.OPEN } }), + ); + const changedSlipsheetsCount = Number( + await this.slipsheetRepository.count({ where: { state: SlipsheetState.CHANGED } }), + ); + const openBillsCount = Number( + await this.billRepository.count({ where: { state: BillState.OPEN } }), + ); + const todayInventoryActivities = await this.queryInventoryRange( + todayStart, + todayEnd, + filters.articleGroupId, + 200, + ); + let recentInventoryActivities = await this.queryInventoryRange( + filters.from, + filters.to, + filters.articleGroupId, + 12, + ); + if (!recentInventoryActivities.length && allowFallback) { + recentInventoryActivities = await this.queryRecentInventoryActivities( + filters.articleGroupId, + 12, + ); + } + const openSlipsheetsQueue = await this.slipsheetRepository.find({ + where: { state: SlipsheetState.OPEN }, + relations: ['customer'], + order: { createdAt: 'DESC' }, + take: 8, + }); + const changedSlipsheetsQueue = await this.slipsheetRepository.find({ + where: { state: SlipsheetState.CHANGED }, + relations: ['customer'], + order: { createdAt: 'DESC' }, + take: 8, + }); + const openBillsQueue = await this.billRepository + .createQueryBuilder('bill') + .leftJoinAndSelect('bill.slipsheets', 'slipsheet') + .leftJoinAndSelect('slipsheet.customer', 'customer') + .where('bill.state = :state', { state: BillState.OPEN }) + .orderBy('bill.createdAt', 'DESC') + .take(8) + .getMany(); + const topMoving7 = await this.queryTopMoving( + sevenDaysAgo, + now, + filters.articleGroupId, + 10, + ); + const topMoving30 = await this.queryTopMoving( + thirtyDaysAgo, + now, + filters.articleGroupId, + 10, + ); + let effectiveTrendFrom = filters.from; + let effectiveTrendTo = filters.to; + let trend = await this.buildTrend( + effectiveTrendFrom, + effectiveTrendTo, + filters.articleGroupId, + ); + + if (allowFallback && !this.trendHasActivity(trend)) { + const latestActivityDate = await this.findLatestActivityDate( + filters.articleGroupId, + ); + + if (latestActivityDate && latestActivityDate < effectiveTrendFrom) { + effectiveTrendTo = this.endOfDay(latestActivityDate); + effectiveTrendFrom = this.startOfDay( + this.shiftDays(effectiveTrendTo, -(filters.days - 1)), + ); + + trend = await this.buildTrend( + effectiveTrendFrom, + effectiveTrendTo, + filters.articleGroupId, + ); + } + } + + const filteredArticles = this.filterArticlesByGroup( + allArticles, + filters.articleGroupId, + ); + const trackedArticles = filteredArticles.filter((a) => !!a.trackStock); + const lowStockArticles = trackedArticles + .filter((a) => Number(a.stock ?? 0) <= filters.threshold) + .sort((a, b) => Number(a.stock ?? 0) - Number(b.stock ?? 0)); + const outOfStockArticles = trackedArticles.filter( + (a) => Number(a.stock ?? 0) <= 0, + ); + + return { + generatedAt: new Date().toISOString(), + filters: { + from: effectiveTrendFrom.toISOString(), + to: effectiveTrendTo.toISOString(), + days: filters.days, + threshold: filters.threshold, + articleGroupId: filters.articleGroupId || null, + }, + kpis: { + totalArticles: filteredArticles.length, + trackedArticles: trackedArticles.length, + lowStockCount: lowStockArticles.length, + outOfStockCount: outOfStockArticles.length, + inventoryAdjustmentsTodayCount: todayInventoryActivities.length, + inventoryAdjustmentsTodayDelta: this.sum( + todayInventoryActivities.map((item) => Number(item.diff || 0)), + ), + openSlipsheets: openSlipsheetsCount, + changedSlipsheets: changedSlipsheetsCount, + openBills: openBillsCount, + }, + lowStockItems: lowStockArticles.slice(0, 15).map((item) => ({ + id: item.id, + name: item.name, + code: item.code, + unit: item.unit, + stock: Number(item.stock ?? 0), + inventoryStock: Number(item.inventoryStock ?? 0), + articleGroupName: item.articleGroup?.name || null, + })), + recentInventoryActivities: recentInventoryActivities.map((item) => ({ + id: item.id, + createdAt: item.createdAt, + amountNew: Number(item.amountNew || 0), + diff: Number(item.diff || 0), + article: item.article + ? { + id: item.article.id, + name: item.article.name, + code: item.article.code, + unit: item.article.unit, + } + : null, + })), + documentQueue: { + openSlipsheets: openSlipsheetsQueue.map((item) => ({ + id: item.id, + slipsheetnumber: item.slipsheetnumber, + state: item.state, + createdAt: item.createdAt, + customerId: item.customer?.id || null, + customerName: + item.customer?.companyName || + `${item.customer?.firstName || ''} ${item.customer?.lastName || ''}`.trim() || + '-', + })), + changedSlipsheets: changedSlipsheetsQueue.map((item) => ({ + id: item.id, + slipsheetnumber: item.slipsheetnumber, + state: item.state, + createdAt: item.createdAt, + customerId: item.customer?.id || null, + customerName: + item.customer?.companyName || + `${item.customer?.firstName || ''} ${item.customer?.lastName || ''}`.trim() || + '-', + })), + openBills: openBillsQueue.map((item) => ({ + id: item.id, + billNumber: item.billNumber, + state: item.state, + billDate: item.billDate, + slipsheetCount: item.slipsheets?.length || 0, + customerId: item.slipsheets?.[0]?.customer?.id || null, + customerName: item.slipsheets?.[0]?.customer?.['name'] || '-', + })), + }, + topMovingArticles7: topMoving7, + topMovingArticles30: topMoving30, + stockTrend: trend, + }; + } + + private async queryTopMoving( + from: Date, + to: Date, + articleGroupId?: number, + limit = 10, + ): Promise { + const fromParam = this.toDateParam(from); + const toParam = this.toDateParam(to); + const queryBuilder = this.orderEntryRepository + .createQueryBuilder('orderEntry') + .select('article.id', 'id') + .addSelect('article.name', 'name') + .addSelect('article.code', 'code') + .addSelect('article.unit', 'unit') + .addSelect('SUM(orderEntry.amount)', 'totalAmount') + .leftJoin('orderEntry.article', 'article') + .where('datetime(orderEntry.createdAt) >= datetime(:from)', { + from: fromParam, + }) + .andWhere('datetime(orderEntry.createdAt) <= datetime(:to)', { + to: toParam, + }) + .groupBy('article.id') + .addGroupBy('article.name') + .addGroupBy('article.code') + .addGroupBy('article.unit') + .orderBy('SUM(orderEntry.amount)', 'DESC') + .take(limit); + + if (articleGroupId) { + queryBuilder.andWhere('article.articleGroupId = :articleGroupId', { + articleGroupId, + }); + } + + const raw = await queryBuilder.getRawMany(); + return raw.map((item) => ({ + id: Number(item.id), + name: item.name, + code: item.code, + unit: item.unit, + totalAmount: Number(item.totalAmount || 0), + })); + } + + private async buildTrend( + from: Date, + to: Date, + articleGroupId?: number, + ): Promise { + const fromParam = this.toDateParam(from); + const toParam = this.toDateParam(to); + const outgoingQuery = this.orderEntryRepository + .createQueryBuilder('orderEntry') + .leftJoin('orderEntry.article', 'article') + .where('datetime(orderEntry.createdAt) >= datetime(:from)', { + from: fromParam, + }) + .andWhere('datetime(orderEntry.createdAt) <= datetime(:to)', { + to: toParam, + }); + + if (articleGroupId) { + outgoingQuery.andWhere('article.articleGroupId = :articleGroupId', { + articleGroupId, + }); + } + + const outgoingEntries = await outgoingQuery.getMany(); + const inventoryEntries = await this.queryInventoryRange( + from, + to, + articleGroupId, + 0, + false, + ); + + const buckets = new Map(); + const cursor = new Date(from); + while (cursor <= to) { + const key = this.toDayKey(cursor); + buckets.set(key, { + day: key, + label: this.toDayLabel(cursor), + outgoing: 0, + adjustmentDelta: 0, + }); + cursor.setDate(cursor.getDate() + 1); + } + + outgoingEntries.forEach((entry: any) => { + const key = this.toDayKey(entry.createdAt); + const bucket = buckets.get(key); + if (bucket) { + bucket.outgoing += Number(entry.amount || 0); + } + }); + + inventoryEntries.forEach((entry: any) => { + const key = this.toDayKey(entry.createdAt); + const bucket = buckets.get(key); + if (bucket) { + bucket.adjustmentDelta += Number(entry.diff || 0); + } + }); + + return Array.from(buckets.values()); + } + + private async queryInventoryRange( + from: Date, + to: Date, + articleGroupId?: number, + take = 12, + desc = true, + ): Promise { + const fromParam = this.toDateParam(from); + const toParam = this.toDateParam(to); + const queryBuilder = this.inventoryRepository + .createQueryBuilder('inventory') + .leftJoinAndSelect('inventory.article', 'article') + .where('datetime(inventory.createdAt) >= datetime(:from)', { + from: fromParam, + }) + .andWhere('datetime(inventory.createdAt) <= datetime(:to)', { + to: toParam, + }) + .orderBy('inventory.createdAt', desc ? 'DESC' : 'ASC'); + + if (articleGroupId) { + queryBuilder.leftJoinAndSelect('article.articleGroup', 'articleGroup'); + queryBuilder.andWhere('article.articleGroupId = :articleGroupId', { + articleGroupId, + }); + } + + if (take && take > 0) { + queryBuilder.take(take); + } + + return queryBuilder.getMany(); + } + + private async queryRecentInventoryActivities( + articleGroupId?: number, + take = 12, + ): Promise { + const queryBuilder = this.inventoryRepository + .createQueryBuilder('inventory') + .leftJoinAndSelect('inventory.article', 'article') + .leftJoinAndSelect('article.articleGroup', 'articleGroup') + .orderBy('inventory.createdAt', 'DESC') + .take(take); + + if (articleGroupId) { + queryBuilder.where('article.articleGroupId = :articleGroupId', { + articleGroupId, + }); + } + + return queryBuilder.getMany(); + } + + private trendHasActivity(trend: any[]): boolean { + return trend.some( + (point) => + Number(point.outgoing || 0) !== 0 || + Number(point.adjustmentDelta || 0) !== 0, + ); + } + + private async findLatestActivityDate( + articleGroupId?: number, + ): Promise { + const [latestInventoryDate, latestOutgoingDate] = await Promise.all([ + this.getLatestInventoryDate(articleGroupId), + this.getLatestOutgoingDate(articleGroupId), + ]); + + if (!latestInventoryDate) { + return latestOutgoingDate; + } + if (!latestOutgoingDate) { + return latestInventoryDate; + } + return latestInventoryDate > latestOutgoingDate + ? latestInventoryDate + : latestOutgoingDate; + } + + private async getLatestInventoryDate( + articleGroupId?: number, + ): Promise { + const latest = await this.queryRecentInventoryActivities(articleGroupId, 1); + return this.toValidDate(latest?.[0]?.createdAt); + } + + private async getLatestOutgoingDate( + articleGroupId?: number, + ): Promise { + const queryBuilder = this.orderEntryRepository + .createQueryBuilder('orderEntry') + .leftJoin('orderEntry.article', 'article') + .orderBy('orderEntry.createdAt', 'DESC') + .take(1); + + if (articleGroupId) { + queryBuilder.where('article.articleGroupId = :articleGroupId', { + articleGroupId, + }); + } + + const latest = await queryBuilder.getOne(); + return this.toValidDate(latest?.createdAt); + } + + private toValidDate(input: any): Date | null { + if (!input) { + return null; + } + const parsed = new Date(input); + if (Number.isNaN(parsed.getTime())) { + return null; + } + return parsed; + } + + private filterArticlesByGroup(articles: any[], articleGroupId?: number) { + if (!articleGroupId) { + return articles; + } + return articles.filter( + (item) => Number(item.articleGroup?.id || 0) === Number(articleGroupId), + ); + } + + private normalizeFilters(query: DashboardSummaryQueryDto): { + days: number; + threshold: number; + articleGroupId?: number; + from: Date; + to: Date; + } { + const now = new Date(); + const days = this.normalizeNumber(query.days, 30, 7, 120); + const threshold = this.normalizeNumber(query.threshold, 10, 0, 100000); + const defaultFrom = this.startOfDay(this.shiftDays(now, -(days - 1))); + const defaultTo = this.endOfDay(now); + const from = this.parseDate(query.from, defaultFrom); + const to = this.parseDate(query.to, defaultTo); + const articleGroupId = query.articleGroupId + ? Number(query.articleGroupId) + : undefined; + + if (from > to) { + return { + days, + threshold, + articleGroupId, + from: defaultFrom, + to: defaultTo, + }; + } + + return { days, threshold, articleGroupId, from, to }; + } + + private normalizeNumber( + value: number, + fallback: number, + min: number, + max: number, + ): number { + const parsed = Number(value); + if (Number.isNaN(parsed)) { + return fallback; + } + return Math.max(min, Math.min(max, parsed)); + } + + private parseDate(value: string, fallback: Date): Date { + if (!value) { + return fallback; + } + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) { + return fallback; + } + return parsed; + } + + private shiftDays(date: Date, days: number): Date { + const value = new Date(date); + value.setDate(value.getDate() + days); + return value; + } + + private startOfDay(date: Date): Date { + const value = new Date(date); + value.setHours(0, 0, 0, 0); + return value; + } + + private endOfDay(date: Date): Date { + const value = new Date(date); + value.setHours(23, 59, 59, 999); + return value; + } + + private toDayKey(input: Date): string { + const date = new Date(input); + const year = date.getFullYear(); + const month = `${date.getMonth() + 1}`.padStart(2, '0'); + const day = `${date.getDate()}`.padStart(2, '0'); + return `${year}-${month}-${day}`; + } + + private toDayLabel(input: Date): string { + const date = new Date(input); + const day = `${date.getDate()}`.padStart(2, '0'); + const month = `${date.getMonth() + 1}`.padStart(2, '0'); + return `${day}.${month}`; + } + + private sum(values: number[]): number { + return values.reduce((acc, current) => acc + Number(current || 0), 0); + } + + private toDateParam(value: Date): string { + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) { + return new Date().toISOString(); + } + return parsed.toISOString(); + } +} diff --git a/apps/server/src/models/bills/discount.repository.ts b/apps/server/src/models/bills/discount.repository.ts new file mode 100644 index 0000000..2755f12 --- /dev/null +++ b/apps/server/src/models/bills/discount.repository.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ModelRepository } from '../model.repository'; +import { Discount } from './entities/discount.entity'; +import { allDiscountForSerializing, DiscountEntity } from './serializers/discount.serializer'; + +@Injectable() +export class DiscountRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(Discount, dataSource.createEntityManager()); + } + + transform(model: Discount): DiscountEntity { + const tranformOptions = { + groups: allDiscountForSerializing, + }; + return plainToInstance( + DiscountEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: Discount[]): DiscountEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/bills/discount.service.ts b/apps/server/src/models/bills/discount.service.ts new file mode 100644 index 0000000..4457ed9 --- /dev/null +++ b/apps/server/src/models/bills/discount.service.ts @@ -0,0 +1,38 @@ +import { Injectable, MethodNotAllowedException } from '@nestjs/common'; +import { CreateUpdateDiscountDto } from './dto/add-update-discount.dto'; +import { BaseService } from '../../common/base.service'; +import { Discount } from './entities/discount.entity'; +import { DiscountEntity } from './serializers/discount.serializer'; +import { DiscountRepository } from './discount.repository'; +import { CustomerEntity } from '../customer/serializers/customer.serializer'; + +@Injectable() +export class DiscountService extends BaseService { + + constructor( + private readonly discountRepository: DiscountRepository, + ) { + super(discountRepository); + } + + async createOrUpdate(customer: CustomerEntity, createUpdateDiscountDto: CreateUpdateDiscountDto): Promise { + + let ret; + if (createUpdateDiscountDto.id) { + let dbEntity = await this.get(createUpdateDiscountDto.id); + if (dbEntity.articleGroupId != createUpdateDiscountDto.articleGroup.id) + throw new MethodNotAllowedException('Artikel Gruppe darf nicht geändert werden'); + + ret = await this.update(createUpdateDiscountDto.id, { value: createUpdateDiscountDto.value }); + } else { + const discount = new Discount(); + discount.articleGroupId = createUpdateDiscountDto.articleGroup.id; + discount.customerId = customer.id; + discount.value = createUpdateDiscountDto.value; + ret = await this.create(discount); + } + + return this.get(ret.id, ['articleGroup']); + } + +} diff --git a/apps/server/src/models/bills/dto/add-order-entry.dto.ts b/apps/server/src/models/bills/dto/add-order-entry.dto.ts new file mode 100644 index 0000000..1e946ed --- /dev/null +++ b/apps/server/src/models/bills/dto/add-order-entry.dto.ts @@ -0,0 +1,67 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsNumber, IsOptional, ValidateIf, ValidateNested } from 'class-validator'; +import { IdDto } from '../../../common/dto/id.dto'; + +export class AddOrderEntryDto { + + @ApiProperty({ + type: IdDto, + description: 'Article for the order', + }) + @IsOptional() + @ValidateNested() + @Type(() => IdDto) + article: IdDto; + + @ApiProperty({ + type: IdDto, + description: 'Customer for the order', + }) + @IsOptional() + @ValidateNested() + @Type(() => IdDto) + customer: IdDto; + + @ApiProperty({ + example: 'true', + description: 'Free text if not put article ', + }) + @IsOptional() + text: string; + + @ApiProperty({ + example: '4.3', + description: 'Free text if not put article ', + }) + @ValidateIf((o) => !o.article) + @IsNotEmpty() + @IsNumber() + price: number; + + @ApiProperty({ + example: '20', + description: 'customer rabatt', + }) + @ValidateIf((o) => !o.article) + @IsNotEmpty() + @IsNumber() + customerRabatt: number; + + @ApiProperty({ + example: '10', + description: 'article rabatt', + }) + @ValidateIf((o) => !o.article) + @IsNotEmpty() + @IsNumber() + articleGroupRabatt: number; + + @ApiProperty({ + example: '3', + description: 'Amount of article which will be added', + }) + @IsNotEmpty() + @IsNumber() + amount: number; +} diff --git a/apps/server/src/models/bills/dto/add-update-discount.dto.ts b/apps/server/src/models/bills/dto/add-update-discount.dto.ts new file mode 100644 index 0000000..8a28aee --- /dev/null +++ b/apps/server/src/models/bills/dto/add-update-discount.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsNumber, IsOptional, ValidateNested } from 'class-validator'; +import { IdDto } from '../../../common/dto/id.dto'; + +export class CreateUpdateDiscountDto { + + @IsOptional() + @IsNumber() + id: number; + + @ApiProperty({ + type: IdDto, + }) + @IsNotEmpty() + @ValidateNested() + @Type(() => IdDto) + articleGroup: IdDto; + + @IsNotEmpty() + @IsNumber() + value: number; + +} diff --git a/apps/server/src/models/bills/dto/create-annotation.dto.ts b/apps/server/src/models/bills/dto/create-annotation.dto.ts new file mode 100644 index 0000000..c2a7c89 --- /dev/null +++ b/apps/server/src/models/bills/dto/create-annotation.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty } from 'class-validator'; + +export class CreateAnnotationDto { + + @ApiProperty({ + example: 'Kommision', + description: 'Text input', + }) + @IsNotEmpty() + text: string; + +} diff --git a/apps/server/src/models/bills/dto/create-bill.dto.ts b/apps/server/src/models/bills/dto/create-bill.dto.ts new file mode 100644 index 0000000..7de5a31 --- /dev/null +++ b/apps/server/src/models/bills/dto/create-bill.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, Length } from 'class-validator'; + +export class CreateBillDto { + +} diff --git a/apps/server/src/models/bills/dto/dashboard-summary-query.dto.ts b/apps/server/src/models/bills/dto/dashboard-summary-query.dto.ts new file mode 100644 index 0000000..b2eb539 --- /dev/null +++ b/apps/server/src/models/bills/dto/dashboard-summary-query.dto.ts @@ -0,0 +1,51 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsInt, IsOptional, Min } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class DashboardSummaryQueryDto { + @ApiPropertyOptional({ + description: 'Number of days for trend window (default 30, min 7)', + example: 30, + }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + days?: number; + + @ApiPropertyOptional({ + description: 'Low stock threshold', + example: 10, + }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(0) + threshold?: number; + + @ApiPropertyOptional({ + description: 'Optional article group filter', + example: 2, + }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + articleGroupId?: number; + + @ApiPropertyOptional({ + description: 'Optional trend start timestamp (ISO)', + example: '2026-01-01T00:00:00.000Z', + }) + @IsOptional() + @IsDateString() + from?: string; + + @ApiPropertyOptional({ + description: 'Optional trend end timestamp (ISO)', + example: '2026-01-31T23:59:59.999Z', + }) + @IsOptional() + @IsDateString() + to?: string; +} diff --git a/apps/server/src/models/bills/dto/update-annotation.dto.ts b/apps/server/src/models/bills/dto/update-annotation.dto.ts new file mode 100644 index 0000000..ea54511 --- /dev/null +++ b/apps/server/src/models/bills/dto/update-annotation.dto.ts @@ -0,0 +1,5 @@ +import { CreateAnnotationDto } from './create-annotation.dto'; + +export class UpdateAnnotationDto extends CreateAnnotationDto { + +} diff --git a/apps/server/src/models/bills/dto/update-bill.dto.ts b/apps/server/src/models/bills/dto/update-bill.dto.ts new file mode 100644 index 0000000..0356477 --- /dev/null +++ b/apps/server/src/models/bills/dto/update-bill.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString, IsNotEmpty, IsDateString } from 'class-validator'; + +export class UpdateBillDto { + @ApiProperty({ + example: '4', + description: 'Rechnungsnummer', + }) + @IsNotEmpty() + @IsString() + billNumber: string; + + @ApiProperty({ + example: '2022-01-01', + description: 'Rechnungsdatum', + }) + @IsNotEmpty() + @IsDateString() + billDate: string; +} diff --git a/apps/server/src/models/bills/dto/update-order-entry.dto.ts b/apps/server/src/models/bills/dto/update-order-entry.dto.ts new file mode 100644 index 0000000..a823c4b --- /dev/null +++ b/apps/server/src/models/bills/dto/update-order-entry.dto.ts @@ -0,0 +1,6 @@ +import { PickType } from '@nestjs/mapped-types'; +import { AddOrderEntryDto } from './add-order-entry.dto'; + +export class UpdateOrderEntryDto extends PickType(AddOrderEntryDto, ['article', 'text', 'price', 'customerRabatt', 'articleGroupRabatt', 'amount'] as const) { + +} diff --git a/apps/server/src/models/bills/entities/annotation.entity.ts b/apps/server/src/models/bills/entities/annotation.entity.ts new file mode 100644 index 0000000..755cd68 --- /dev/null +++ b/apps/server/src/models/bills/entities/annotation.entity.ts @@ -0,0 +1,30 @@ +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + JoinColumn, + ManyToOne, +} from 'typeorm'; +import { IAnnotation } from '../interfaces/annotation.interface'; +import { Slipsheet } from './slipsheet.entity'; + +@Entity({ name: 'annotation' }) +export class Annotation implements IAnnotation { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ nullable: false }) + text: string; + + @ManyToOne(() => Slipsheet, (slipsheet) => slipsheet.orderEntries, { nullable: false }) + @JoinColumn() + slipsheet: Slipsheet; + + @CreateDateColumn({ name: 'created_at', nullable: false }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', nullable: false }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/entities/bill.entity.ts b/apps/server/src/models/bills/entities/bill.entity.ts new file mode 100644 index 0000000..582633a --- /dev/null +++ b/apps/server/src/models/bills/entities/bill.entity.ts @@ -0,0 +1,39 @@ +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + OneToMany, +} from 'typeorm'; +import { BillState } from '../enums/bill-state.enum'; +import { IBill } from '../interfaces/bill.interface'; +import { SlipsheetEntity } from '../serializers/slipsheet.serializer'; +import { Slipsheet } from './slipsheet.entity'; + +@Entity({ name: 'bill' }) +export class Bill implements IBill { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ nullable: false, unique: true }) + billNumber: string; + + @Column({ nullable: false, default: BillState.OPEN, length: 20 }) + state: BillState; + + @Column({ type: 'datetime', nullable: false, default: () => 'CURRENT_TIMESTAMP' }) + billDate: Date; + + @Column({ nullable: true, length: 256 }) + path: string; + + @OneToMany(() => Slipsheet, (slipsheet) => slipsheet.bill) + slipsheets: SlipsheetEntity[]; + + @CreateDateColumn({ name: 'created_at', nullable: false }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', nullable: false }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/entities/discount.entity.ts b/apps/server/src/models/bills/entities/discount.entity.ts new file mode 100644 index 0000000..9904bf7 --- /dev/null +++ b/apps/server/src/models/bills/entities/discount.entity.ts @@ -0,0 +1,40 @@ +import { ArticleGroup } from '../../article/entities/article-group.entity'; +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + JoinColumn, + ManyToOne, +} from 'typeorm'; +import { IDiscount } from '../interfaces/discount.interface'; +import { Customer } from '../../customer/entities/customer.entity'; + + +@Entity({ name: 'discount' }) +export class Discount implements IDiscount { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ nullable: false }) + value!: number; + + @ManyToOne(() => ArticleGroup) + @JoinColumn() + articleGroup!: ArticleGroup; + @Column({ nullable: false }) + articleGroupId!: number; + + @ManyToOne(() => Customer) + @JoinColumn() + customer!: Customer; + @Column({ nullable: false }) + customerId!: number; + + @CreateDateColumn({ name: 'created_at', nullable: false }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', nullable: false }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/entities/order-entry.entity.ts b/apps/server/src/models/bills/entities/order-entry.entity.ts new file mode 100644 index 0000000..d3d0d2c --- /dev/null +++ b/apps/server/src/models/bills/entities/order-entry.entity.ts @@ -0,0 +1,47 @@ +import { Article } from '../../article/entities/article.entity'; +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { IOrderEntry } from '../interfaces/order-entry.interface'; +import { Slipsheet } from './slipsheet.entity'; + +@Entity({ name: 'order-entries' }) +export class OrderEntry implements IOrderEntry { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ nullable: false }) + text: string; + + @Column({ type: 'real', nullable: false }) + amount: number; + + @Column({ type: 'real', nullable: false }) + price: number; + + @Column({ type: 'real', nullable: false }) + customerRabatt: number; + + @Column({ type: 'real', nullable: false }) + articleGroupRabatt: number; + + @ManyToOne(() => Article, (article) => article.orderEntry, { nullable: true }) + @JoinColumn() + article: Article; + + @ManyToOne(() => Slipsheet, (slipsheet) => slipsheet.orderEntries) + @JoinColumn() + slipsheet: Slipsheet; + + @CreateDateColumn({ name: 'created_at', nullable: false }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', nullable: false }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/entities/slipsheet.entity.ts b/apps/server/src/models/bills/entities/slipsheet.entity.ts new file mode 100644 index 0000000..eada2bc --- /dev/null +++ b/apps/server/src/models/bills/entities/slipsheet.entity.ts @@ -0,0 +1,57 @@ +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + OneToMany, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { Customer } from '../../customer/entities/customer.entity'; +import { SlipsheetState } from '../enums/slipsheet-state.enum'; +import { ISlipsheet } from '../interfaces/slipsheet.interface'; +import { Annotation } from './annotation.entity'; +import { Bill } from './bill.entity'; +import { OrderEntry } from './order-entry.entity'; + +@Entity({ name: 'slipsheet' }) +export class Slipsheet implements ISlipsheet { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ nullable: true, unique: true }) + slipsheetnumber: string; + + @CreateDateColumn({ nullable: false }) + printDate: Date; + + @Column({ nullable: false, default: SlipsheetState.OPEN, length: 20 }) + state: SlipsheetState; + + @Column({ nullable: true, length: 256 }) + path: string; + + @OneToMany(() => OrderEntry, (orderEntrie) => orderEntrie.slipsheet, { cascade: false, onDelete: 'CASCADE' }) + orderEntries: OrderEntry[]; + + @OneToMany(() => Annotation, (annotation) => annotation.slipsheet) + annotations: Annotation[]; + + @ManyToOne(() => Bill, (slipsheet) => slipsheet.slipsheets, { nullable: true, onDelete: 'SET NULL' }) + @JoinColumn() + bill: Bill; + + @Column({ nullable: true }) + billId: number; + + @ManyToOne(() => Customer, (customer) => customer.slipsheets, { nullable: true }) + @JoinColumn() + customer: Customer; + + @CreateDateColumn({ name: 'created_at', nullable: false }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', nullable: false }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/enums/bill-state.enum.ts b/apps/server/src/models/bills/enums/bill-state.enum.ts new file mode 100644 index 0000000..22301c3 --- /dev/null +++ b/apps/server/src/models/bills/enums/bill-state.enum.ts @@ -0,0 +1,4 @@ +export enum BillState { + OPEN = 'open', + CLOSED = 'closed', +} diff --git a/apps/server/src/models/bills/enums/slipsheet-state.enum.ts b/apps/server/src/models/bills/enums/slipsheet-state.enum.ts new file mode 100644 index 0000000..7c9278e --- /dev/null +++ b/apps/server/src/models/bills/enums/slipsheet-state.enum.ts @@ -0,0 +1,5 @@ +export enum SlipsheetState { + OPEN = 'open', + CLOSED = 'closed', + CHANGED = 'changed', +} diff --git a/apps/server/src/models/bills/interfaces/annotation.interface.ts b/apps/server/src/models/bills/interfaces/annotation.interface.ts new file mode 100644 index 0000000..ee30a38 --- /dev/null +++ b/apps/server/src/models/bills/interfaces/annotation.interface.ts @@ -0,0 +1,6 @@ +import { ISlipsheet } from './slipsheet.interface'; + +export interface IAnnotation { + text: string; + slipsheet: ISlipsheet; +} diff --git a/apps/server/src/models/bills/interfaces/bill.interface.ts b/apps/server/src/models/bills/interfaces/bill.interface.ts new file mode 100644 index 0000000..e4e04ec --- /dev/null +++ b/apps/server/src/models/bills/interfaces/bill.interface.ts @@ -0,0 +1,10 @@ +import { BillState } from '../enums/bill-state.enum'; +import { ISlipsheet } from './slipsheet.interface'; + +export interface IBill { + billNumber: string; + slipsheets: ISlipsheet[]; + state: BillState; + billDate: Date; + path: string; +} diff --git a/apps/server/src/models/bills/interfaces/discount.interface.ts b/apps/server/src/models/bills/interfaces/discount.interface.ts new file mode 100644 index 0000000..0edc056 --- /dev/null +++ b/apps/server/src/models/bills/interfaces/discount.interface.ts @@ -0,0 +1,8 @@ +import { IArticleGroup } from '../../article/interfaces/article-group.interface'; +import { ICustomer } from '../../customer/interfaces/customer.interface'; + +export interface IDiscount { + value: number; + articleGroup: IArticleGroup; + customer: ICustomer; +} diff --git a/apps/server/src/models/bills/interfaces/order-entry.interface.ts b/apps/server/src/models/bills/interfaces/order-entry.interface.ts new file mode 100644 index 0000000..64424be --- /dev/null +++ b/apps/server/src/models/bills/interfaces/order-entry.interface.ts @@ -0,0 +1,12 @@ +import { IArticle } from '../../article/interfaces/article.interface'; +import { ISlipsheet } from './slipsheet.interface'; + +export interface IOrderEntry { + text: string; + amount: number; + price: number; + customerRabatt: number; + articleGroupRabatt: number; + article: IArticle; + slipsheet: ISlipsheet; +} diff --git a/apps/server/src/models/bills/interfaces/slipsheet.interface.ts b/apps/server/src/models/bills/interfaces/slipsheet.interface.ts new file mode 100644 index 0000000..519892e --- /dev/null +++ b/apps/server/src/models/bills/interfaces/slipsheet.interface.ts @@ -0,0 +1,15 @@ +import { ICustomer } from '../../customer/interfaces/customer.interface'; +import { SlipsheetState } from '../enums/slipsheet-state.enum'; +import { IAnnotation } from './annotation.interface'; +import { IBill } from './bill.interface'; +import { IOrderEntry } from './order-entry.interface'; + +export interface ISlipsheet { + slipsheetnumber: string; + state: SlipsheetState; + path: string; + orderEntries: IOrderEntry[]; + customer: ICustomer; + bill: IBill; + annotations: IAnnotation[]; +} diff --git a/apps/server/src/models/bills/order-entry.repository.ts b/apps/server/src/models/bills/order-entry.repository.ts new file mode 100644 index 0000000..ce192ec --- /dev/null +++ b/apps/server/src/models/bills/order-entry.repository.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ModelRepository } from '../model.repository'; +import { OrderEntry } from './entities/order-entry.entity'; +import { allOrderEntryForSerializing, OrderEntryEntity } from './serializers/order-entry.serializer'; + +@Injectable() +export class OrderEntryRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(OrderEntry, dataSource.createEntityManager()); + } + + transform(model: OrderEntry): OrderEntryEntity { + const tranformOptions = { + groups: allOrderEntryForSerializing, + }; + return plainToInstance( + OrderEntryEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: OrderEntry[]): OrderEntryEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/bills/order-entry.service.ts b/apps/server/src/models/bills/order-entry.service.ts new file mode 100644 index 0000000..29d0585 --- /dev/null +++ b/apps/server/src/models/bills/order-entry.service.ts @@ -0,0 +1,131 @@ +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { BaseService } from '../../common/base.service'; +import { OrderEntryRepository } from './order-entry.repository'; +import { OrderEntry } from './entities/order-entry.entity'; +import { OrderEntryEntity } from './serializers/order-entry.serializer'; +import { CustomerEntity } from '../customer/serializers/customer.serializer'; +import { SlipsheetEntity } from './serializers/slipsheet.serializer'; +import { AddOrderEntryDto } from './dto/add-order-entry.dto'; +import { SlipsheetService } from './slipsheet.service'; +import { DeepPartial } from 'typeorm'; +import { ArticleService } from '../article/article.service'; +import { BillService } from './bill.service'; + +@Injectable() +export class OrderEntryService extends BaseService { + + constructor( + private readonly orderEntryRepository: OrderEntryRepository, + private readonly slipsheetService: SlipsheetService, + private readonly billService: BillService, + private readonly articleServie: ArticleService, + ) { + super(orderEntryRepository); + } + + + async addOrderToSlipsheet( + { + customer, + slipsheet = null, + addOrderEntry, + }: { + customer: CustomerEntity, + slipsheet?: SlipsheetEntity, + addOrderEntry: AddOrderEntryDto, + }): Promise { + + if (!customer && !slipsheet) + throw new NotFoundException('Customer not found'); + + if (!addOrderEntry) + throw new NotFoundException('addOrderEntry not found'); + + // Bug #6 fix: use BadRequestException instead of UnprocessableEntityException + const hasArticle = !!addOrderEntry.article; + const hasText = !!addOrderEntry.text; + if (hasArticle === hasText) + throw new BadRequestException('ether article or text has to be set, not both or nothing'); + + if (!slipsheet) + slipsheet = await this.slipsheetService.findOpenForCustomer(customer); + + let retVal = null; + if (addOrderEntry.article) { + retVal = await this.addEntryByArticle(addOrderEntry, customer, slipsheet); + } else { + retVal = this.addEntryByText(addOrderEntry, customer, slipsheet); + } + await this.slipsheetService.changed(slipsheet); + if (slipsheet.billId) + await this.billService.changed(slipsheet.billId); + return retVal; + } + + + addEntryByText(addOrderEntry: AddOrderEntryDto, customer: CustomerEntity, slipsheet: SlipsheetEntity): Promise { + + const orderEntryEntity: DeepPartial = { + text: addOrderEntry.text, + price: addOrderEntry.price, + customerRabatt: addOrderEntry.customerRabatt, + amount: addOrderEntry.amount, + articleGroupRabatt: addOrderEntry.articleGroupRabatt, + slipsheet: slipsheet, + }; + return this.orderEntryRepository.createEntity(orderEntryEntity, ['slipsheet']); + + } + + async addEntryByArticle(addOrderEntry: AddOrderEntryDto, customer: CustomerEntity, slipsheet: SlipsheetEntity): Promise { + + const relations = ['article', 'article.articleGroup', 'slipsheet']; + let article = await this.articleServie.get(addOrderEntry.article.id, ['articleGroup']); + let order = slipsheet.orderEntries?.find((o) => o.article?.id === addOrderEntry.article.id); + + if (article.singlePos || !order) { + + if (addOrderEntry.amount <= 0) + throw new BadRequestException('Amount <= 0, for new or single position'); + + const articleDiscount = customer.discounts?.find((o) => o.articleGroupId === article.articleGroup?.id)?.value || 0; + const orderEntryEntity: DeepPartial = { + article: article, + price: article.price, + customerRabatt: article.noDiscount ? 0 : customer.customerDiscount, + amount: addOrderEntry.amount, + articleGroupRabatt: article.noDiscount ? 0 : articleDiscount, + text: article.name, + slipsheet: slipsheet, + }; + return this.orderEntryRepository.createEntity(orderEntryEntity, relations); + } else { + if (order.amount + addOrderEntry.amount <= 0) + throw new BadRequestException('new amount <= 0, update position'); + + return this.orderEntryRepository.updateEntity( + order.id, + { amount: order.amount + addOrderEntry.amount }, + relations); + } + } + + + override async update(id: number, inputs: DeepPartial): Promise { + const relations = ['article', 'article.articleGroup', 'slipsheet']; + const orderEntry = await this.orderEntryRepository.get(id, ['slipsheet']); + if (!orderEntry) + throw new NotFoundException('OrderEntry not found'); + + await this.slipsheetService.changed(orderEntry.slipsheet); + + if (orderEntry.slipsheet.billId) + await this.billService.changed(orderEntry.slipsheet.billId); + + if (inputs.amount == 0) { + this.orderEntryRepository.delete(id); + return null; + } + return this.orderEntryRepository.updateEntity(id, inputs, relations); + } +} diff --git a/apps/server/src/models/bills/serializers/annotation.serializer.ts b/apps/server/src/models/bills/serializers/annotation.serializer.ts new file mode 100644 index 0000000..0de93eb --- /dev/null +++ b/apps/server/src/models/bills/serializers/annotation.serializer.ts @@ -0,0 +1,29 @@ +import { Expose, Type } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { IAnnotation } from '../interfaces/annotation.interface'; +import { SlipsheetEntity } from './slipsheet.serializer'; + +export const defaultAnnotationForSerializing: string[] = [ + 'default', + 'annotation.timestamps', +]; +export const extendedAnnotationForSerializing: string[] = [ + ...defaultAnnotationForSerializing, +]; +export const allAnnotationForSerializing: string[] = [ + ...extendedAnnotationForSerializing, +]; +export class AnnotationEntity extends ModelEntity implements IAnnotation { + + @Expose({ groups: ['default'] }) + text: string; + + @Expose({ groups: ['default'] }) + @Type(() => SlipsheetEntity) + slipsheet: SlipsheetEntity; + + @Expose({ groups: ['annotation.timestamps'] }) + createdAt: Date; + @Expose({ groups: ['annotation.timestamps'] }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/serializers/bill.serializer.ts b/apps/server/src/models/bills/serializers/bill.serializer.ts new file mode 100644 index 0000000..32f9130 --- /dev/null +++ b/apps/server/src/models/bills/serializers/bill.serializer.ts @@ -0,0 +1,40 @@ +import { Expose } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { BillState } from '../enums/bill-state.enum'; +import { IBill } from '../interfaces/bill.interface'; +import { SlipsheetEntity } from './slipsheet.serializer'; + +export const defaultBillForSerializing: string[] = [ + 'default', + 'bill_timestamps', +]; +export const extendedBillForSerializing: string[] = [ + ...defaultBillForSerializing, +]; +export const allBillForSerializing: string[] = [ + ...extendedBillForSerializing, +]; +export class BillEntity extends ModelEntity implements IBill { + + @Expose({ groups: ['default'] }) + billNumber: string; + + @Expose({ groups: ['default'] }) + slipsheets: SlipsheetEntity[]; + + @Expose({ groups: ['default'] }) + state: BillState; + + @Expose({ groups: ['default'] }) + billDate: Date; + + @Expose({ groups: ['default'] }) + path: string; + + @Expose({ groups: ['bill_timestamps'] }) + lastSeen: Date; + @Expose({ groups: ['bill_timestamps'] }) + createdAt: Date; + @Expose({ groups: ['bill_timestamps'] }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/serializers/discount.serializer.ts b/apps/server/src/models/bills/serializers/discount.serializer.ts new file mode 100644 index 0000000..1153cf4 --- /dev/null +++ b/apps/server/src/models/bills/serializers/discount.serializer.ts @@ -0,0 +1,27 @@ +import { Expose } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { ArticleGroupEntity } from '../../article/serializers/article-group.serializer'; +import { CustomerEntity } from '../../customer/serializers/customer.serializer'; +import { IDiscount } from '../interfaces/discount.interface'; + +export const defaultDiscountForSerializing: string[] = [ + 'default', +]; +export const extendedDiscountForSerializing: string[] = [ + ...defaultDiscountForSerializing, +]; +export const allDiscountForSerializing: string[] = [ + ...extendedDiscountForSerializing, +]; +export class DiscountEntity extends ModelEntity implements IDiscount { + + @Expose({ groups: ['default'] }) + value: number; + + @Expose({ groups: ['default'] }) + articleGroup: ArticleGroupEntity; + + @Expose({ groups: ['default'] }) + customer: CustomerEntity; + +} diff --git a/apps/server/src/models/bills/serializers/order-entry.serializer.ts b/apps/server/src/models/bills/serializers/order-entry.serializer.ts new file mode 100644 index 0000000..bac3de3 --- /dev/null +++ b/apps/server/src/models/bills/serializers/order-entry.serializer.ts @@ -0,0 +1,48 @@ +import { Expose, Type } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { ArticleEntity } from '../../article/serializers/article.serializer'; +import { IOrderEntry } from '../interfaces/order-entry.interface'; +import { SlipsheetEntity } from './slipsheet.serializer'; + +export const defaultOrderEntryForSerializing: string[] = [ + 'default', + 'orderEntry.timestamps', +]; +export const extendedOrderEntryForSerializing: string[] = [ + ...defaultOrderEntryForSerializing, +]; +export const allOrderEntryForSerializing: string[] = [ + ...extendedOrderEntryForSerializing, +]; +export class OrderEntryEntity extends ModelEntity implements IOrderEntry { + + @Expose({ groups: ['default'] }) + text: string; + + @Expose({ groups: ['default'] }) + amount: number; + + @Expose({ groups: ['default'] }) + price: number; + + @Expose({ groups: ['default'] }) + customerRabatt: number; + + @Expose({ groups: ['default'] }) + articleGroupRabatt: number; + + @Expose({ groups: ['default'] }) + @Type(() => ArticleEntity) + article: ArticleEntity; + + @Expose({ groups: ['default'] }) + @Type(() => SlipsheetEntity) + slipsheet: SlipsheetEntity; + + @Expose({ groups: ['orderEntry.timestamps'] }) + lastSeen: Date; + @Expose({ groups: ['orderEntry.timestamps'] }) + createdAt: Date; + @Expose({ groups: ['orderEntry.timestamps'] }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/serializers/slipsheet.serializer.ts b/apps/server/src/models/bills/serializers/slipsheet.serializer.ts new file mode 100644 index 0000000..885d928 --- /dev/null +++ b/apps/server/src/models/bills/serializers/slipsheet.serializer.ts @@ -0,0 +1,56 @@ +import { Expose, Type } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { CustomerEntity } from '../../customer/serializers/customer.serializer'; +import { SlipsheetState } from '../enums/slipsheet-state.enum'; +import { ISlipsheet } from '../interfaces/slipsheet.interface'; +import { AnnotationEntity } from './annotation.serializer'; +import { BillEntity } from './bill.serializer'; +import { OrderEntryEntity } from './order-entry.serializer'; + +export const defaultSlipsheetForSerializing: string[] = [ + 'default', + 'slipsheet.timestamps', +]; +export const extendedSlipsheetForSerializing: string[] = [ + ...defaultSlipsheetForSerializing, +]; +export const allSlipsheetForSerializing: string[] = [ + ...extendedSlipsheetForSerializing, +]; +export class SlipsheetEntity extends ModelEntity implements ISlipsheet { + + @Expose({ groups: ['default'] }) + slipsheetnumber: string; + + @Expose({ groups: ['default'] }) + @Type(() => OrderEntryEntity) + orderEntries: OrderEntryEntity[]; + + @Expose({ groups: ['default'] }) + bill: BillEntity; + + @Expose({ groups: ['default'] }) + printDate: Date; + + @Expose({ groups: ['default'] }) + state: SlipsheetState; + + @Expose({ groups: ['default'] }) + path: string; + + @Expose({ groups: ['default'] }) + name: string; + + @Expose({ groups: ['default'] }) + @Type(() => CustomerEntity) + customer: CustomerEntity; + + @Expose({ groups: ['default'] }) + @Type(() => AnnotationEntity) + annotations: AnnotationEntity[]; + + @Expose({ groups: ['slipsheet.timestamps'] }) + createdAt: Date; + @Expose({ groups: ['slipsheet.timestamps'] }) + updatedAt: Date; +} diff --git a/apps/server/src/models/bills/slipsheet.repository.ts b/apps/server/src/models/bills/slipsheet.repository.ts new file mode 100644 index 0000000..3506577 --- /dev/null +++ b/apps/server/src/models/bills/slipsheet.repository.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ModelRepository } from '../model.repository'; +import { Slipsheet } from './entities/slipsheet.entity'; +import { allSlipsheetForSerializing, SlipsheetEntity } from './serializers/slipsheet.serializer'; + +@Injectable() +export class SlipsheetRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(Slipsheet, dataSource.createEntityManager()); + } + + transform(model: Slipsheet): SlipsheetEntity { + const tranformOptions = { + groups: allSlipsheetForSerializing, + }; + return plainToInstance( + SlipsheetEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: Slipsheet[]): SlipsheetEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/bills/slipsheet.service.ts b/apps/server/src/models/bills/slipsheet.service.ts new file mode 100644 index 0000000..83084d3 --- /dev/null +++ b/apps/server/src/models/bills/slipsheet.service.ts @@ -0,0 +1,228 @@ +import { + Injectable, + InternalServerErrorException, + NotFoundException, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseService } from '../../common/base.service'; +import { SlipsheetRepository } from './slipsheet.repository'; +import { Slipsheet } from './entities/slipsheet.entity'; +import { SlipsheetEntity } from './serializers/slipsheet.serializer'; +import { CustomerEntity } from '../customer/serializers/customer.serializer'; +import { SlipsheetState } from './enums/slipsheet-state.enum'; +import { Between, EntityManager } from 'typeorm'; +import { join } from 'path'; +import { PdfMakerService } from '../../common/services/pdfmaker.service'; +import { AppConfigService } from '../../config/app/config.service'; +import { TimeRangeDto } from '../../common/dto/time-range.dto'; + +@Injectable() +export class SlipsheetService extends BaseService { + constructor( + private readonly slipsheetRepository: SlipsheetRepository, + private readonly pdfMakerService: PdfMakerService, + private readonly appConfigService: AppConfigService, + ) { + super(slipsheetRepository); + } + + getAllInformations(ids: number[]): Promise { + return this.slipsheetRepository.getAll(ids, this.getAllRelations()); + } + + public getAllRelations() { + return [ + 'bill', + 'customer', + 'customer.discounts', + 'customer.discounts.articleGroup', + 'orderEntries', + 'orderEntries.article', + 'orderEntries.article.articleGroup', + 'annotations', + ]; + } + + async deleteEmpty(id: number): Promise { + const [slip] = await this.getAllInformations([id]); + if (!slip) throw new NotFoundException('Lieferschein nicht gefunden.'); + if (slip.bill) throw new UnprocessableEntityException('Lieferschein ist bereits verrechnet und kann nicht gelöscht werden.'); + if (slip.orderEntries?.length > 0) throw new UnprocessableEntityException('Lieferschein hat noch Positionen und kann nicht gelöscht werden.'); + await this.slipsheetRepository.delete(id); + } + + async changed(slip: SlipsheetEntity) { + if (slip.state !== SlipsheetState.OPEN) { + await this.update(slip.id, { state: SlipsheetState.CHANGED }); + } + } + + async generateNumber( + entityManager: EntityManager = this.slipsheetRepository.manager, + ): Promise { + const lastEntry = await entityManager.query(` + SELECT "Slipsheet"."slipsheetnumber" AS "slipsheetnumber" + FROM "slipsheet" "Slipsheet" WHERE NOT("Slipsheet"."slipsheetnumber" IS NULL) and "Slipsheet"."slipsheetnumber" not like '%/%' + ORDER BY CAST("Slipsheet"."slipsheetnumber" as INTEGER) DESC Limit 1`); + + if (lastEntry?.length != 0) { + const number = +lastEntry[0]?.slipsheetnumber || 0; + return (number + 1).toString(); + } + return '1'; + } + + async generateSlipsheet(id: number, close: boolean): Promise { + let slip: SlipsheetEntity = (await this.getAllInformations([id]))[0]; + if (!slip) { + throw new NotFoundException('Lieferschein nicht gefunden'); + } + + if (slip.orderEntries.length === 0 && slip.annotations.length === 0) { + throw new UnprocessableEntityException('Keine Eintraege auf Lieferschein'); + } + + if (!slip.path || !slip.slipsheetnumber) { + const numberAndPath = await this.reserveSlipNumberAndPath(id, close); + slip.slipsheetnumber = numberAndPath.slipsheetnumber; + slip.path = numberAndPath.path; + } else if (close && slip.state !== SlipsheetState.CLOSED) { + await this.update(id, { state: SlipsheetState.CLOSED }); + slip.state = SlipsheetState.CLOSED; + } + + if (close) { + slip.state = SlipsheetState.CLOSED; + } + + try { + const contentd = await this.pdfMakerService.generateDeliverySlip(slip); + await this.pdfMakerService.savePDFToFileSystem( + contentd, + join(this.appConfigService.pdf_slip_path, slip.path), + ); + } catch (error) { + throw new InternalServerErrorException(error); + } + + slip = (await this.getAllInformations([id]))[0]; + return slip; + } + + closeAndSetBillId(shoppingcartIds: number[], billId: number): Promise { + return this.slipsheetRepository.updateEntities(shoppingcartIds, { + bill: { id: billId }, + state: SlipsheetState.CLOSED, + }); + } + + getPathName(slip: SlipsheetEntity): string { + return 'L_' + slip.slipsheetnumber + '.pdf'; + } + + async findOpenForCustomer(customer: CustomerEntity): Promise { + if (!customer) { + throw new NotFoundException('Customer not found'); + } + + let returnSlipsheet = await this.slipsheetRepository.findOneAsync( + { + state: SlipsheetState.OPEN, + customer, + }, + [ + 'customer', + 'orderEntries', + 'orderEntries.article', + 'orderEntries.article.articleGroup', + 'annotations', + ], + false, + ); + + if (!returnSlipsheet) { + returnSlipsheet = await this.slipsheetRepository.createEntity( + { customer }, + [ + 'customer', + 'orderEntries', + 'orderEntries.article', + 'orderEntries.article.articleGroup', + 'annotations', + ], + ); + } + + return returnSlipsheet; + } + + // Bug fix: TypeORM 0.3 requires { customer: { id: customerId } } instead of { customer: customerId } + async findFromCustomer(customerId: number, timerange?: TimeRangeDto): Promise { + const filter: any = { + customer: { id: +customerId }, + }; + + if (timerange) { + filter.createdAt = Between(timerange.from, timerange.to); + } + + return this.slipsheetRepository.findAll({ + filter, + relations: ['customer', 'orderEntries', 'orderEntries.article', 'orderEntries.article.articleGroup', 'bill', 'annotations'], + }); + } + + async findByState(state: SlipsheetState): Promise { + return this.slipsheetRepository.findAll({ + filter: { state }, + relations: [ + 'customer', + 'orderEntries', + 'orderEntries.article', + 'orderEntries.article.articleGroup', + 'bill', + 'annotations', + ], + }); + } + + private async reserveSlipNumberAndPath( + id: number, + close: boolean, + maxAttempts = 5, + ): Promise<{ slipsheetnumber: string; path: string }> { + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + const slipsheetnumber = await this.generateNumber(); + const path = this.getPathName({ slipsheetnumber } as SlipsheetEntity); + + try { + await this.update(id, { + slipsheetnumber, + path, + ...(close ? { state: SlipsheetState.CLOSED } : {}), + }); + + return { slipsheetnumber, path }; + } catch (error) { + if (this.isUniqueConstraintError(error) && attempt < maxAttempts) { + continue; + } + throw error; + } + } + + throw new InternalServerErrorException( + 'Lieferscheinnummer konnte nicht eindeutig erzeugt werden', + ); + } + + private isUniqueConstraintError(error: any): boolean { + const code = error?.code || error?.errno; + const message = `${error?.message || ''}`.toLowerCase(); + return ( + code === 'SQLITE_CONSTRAINT' || + code === '23505' || + message.includes('unique constraint') + ); + } +} diff --git a/apps/server/src/models/customer/controllers/customer.controller.ts b/apps/server/src/models/customer/controllers/customer.controller.ts new file mode 100644 index 0000000..0ecb2be --- /dev/null +++ b/apps/server/src/models/customer/controllers/customer.controller.ts @@ -0,0 +1,162 @@ +import { + Get, + Put, + Post, + Body, + Controller, + UseInterceptors, + SerializeOptions, + ClassSerializerInterceptor, + Param, + Delete, + Patch, + forwardRef, + Inject, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiExtraModels, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../../common/res.model'; +import { ApiReS } from '../../../common/decorators/apires.decorator'; +import { CreateCustomerDto } from '../dto/create-customer.dto'; +import { UpdateCustomerDto } from '../dto/update-customer.dto'; +import { CustomerEntity, defaultCustomerForSerializing } from '../serializers/customer.serializer'; +import { CustomerService } from '../customer.service'; +import { AddOrderEntryDto } from '../../bills/dto/add-order-entry.dto'; +import { OrderEntryService } from '../../bills/order-entry.service'; +import { defaultOrderEntryForSerializing, OrderEntryEntity } from '../../bills/serializers/order-entry.serializer'; +import { PdfMakerService } from '../../../common/services/pdfmaker.service'; +import { SlipsheetEntity } from '../../bills/serializers/slipsheet.serializer'; +import { SlipsheetService } from '../../bills/slipsheet.service'; +import { BillEntity, extendedBillForSerializing } from '../../bills/serializers/bill.serializer'; +import { BillService } from '../../bills/bill.service'; +import { CreateUpdateDiscountDto } from '../../bills/dto/add-update-discount.dto'; +import { DiscountService } from '../../bills/discount.service'; +import { DiscountEntity } from '../../bills/serializers/discount.serializer'; + + + +@ApiBearerAuth() +@Controller('customers') +@ApiTags('customers') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ + excludeExtraneousValues: true, + groups: defaultCustomerForSerializing, +}) +export class CustomerController { + constructor( + @Inject(forwardRef(() => CustomerService)) + private readonly customerService: CustomerService, + private readonly discountService: DiscountService, + private readonly orderEntryService: OrderEntryService, + private readonly pdfMakerService: PdfMakerService, + @Inject(forwardRef(() => SlipsheetService)) + private readonly slipsheetRepository: SlipsheetService, + @Inject(forwardRef(() => BillService)) + private readonly billService: BillService, + ) { + } + + @Get('/:id') + @ApiOperation({ + summary: 'Get specific customer', + description: 'Fetchs data of id', + }) + async get(@Param('id') id: number): Promise> { + return ReS.FromData(await this.customerService.get(id, ['discounts', 'discounts.articleGroup'])); + } + + @Get('/:id/slipsheets') + @ApiOperation({ + summary: 'Get slipsheets for customer', + description: 'Fetchs data of id', + }) + async getSlipsheets(@Param('id') id: number): Promise> { + let returnSlipsheet = await this.slipsheetRepository.findFromCustomer(id); + return ReS.FromData(returnSlipsheet); + } + + @Get('/:id/bills') + @ApiOperation({ + summary: 'Get bills for customer', + description: 'Fetchs data of id', + }) + @SerializeOptions({ + excludeExtraneousValues: true, + groups: [...defaultCustomerForSerializing, ...extendedBillForSerializing], + }) + async getBills(@Param('id') id: number): Promise> { + return ReS.FromData(await this.billService.findFromCustomer(id)); + } + + @Get() + @UseInterceptors(ClassSerializerInterceptor) + @ApiOperation({ + summary: 'Get all customer', + description: 'Fetchs all data', + }) + async getAll(): Promise> { + return ReS.FromData(await this.customerService.getAll()); + } + + @Post() + @ApiOperation({ + summary: 'Create customer', + description: 'Fetchs data of id', + }) + async post(@Body() customer: CreateCustomerDto): Promise> { + console.log(customer); + return ReS.FromData(await this.customerService.create(customer)); + } + + @Post('/:id/order') + @ApiOperation({ + summary: 'Add article to open slipsheet', + description: 'Fetchs data of id', + }) + @SerializeOptions({ + groups: defaultOrderEntryForSerializing, + }) + async postOrder( + @Param('id') id: number, + @Body() addOrderEntryDto: AddOrderEntryDto): Promise> { + let customer = await this.customerService.get(id); + + const ret = await this.orderEntryService.addOrderToSlipsheet( + { + customer, + addOrderEntry: addOrderEntryDto, + }); + console.log(ret); + return ReS.FromData(ret); + } + + @Patch(':id') + async update(@Param('id') id: number, @Body() updateCustomerDto: UpdateCustomerDto) { + return ReS.FromData(await this.customerService.update(id, updateCustomerDto)); + } + + @Put('/:id/discounts') + @ApiOperation({ + summary: 'Add or update Discount', + description: 'Fetchs data of id', + }) + @SerializeOptions({ + groups: defaultOrderEntryForSerializing, + }) + async postDiscount( + @Param('id') id: number, + @Body() addUpdateDiscountOrderEntryDto: CreateUpdateDiscountDto): Promise> { + + let customer = await this.customerService.get(id); + return ReS.FromData(await this.discountService.createOrUpdate(customer, addUpdateDiscountOrderEntryDto)); + } + +} diff --git a/apps/server/src/models/customer/customer.module.ts b/apps/server/src/models/customer/customer.module.ts new file mode 100644 index 0000000..481bfe7 --- /dev/null +++ b/apps/server/src/models/customer/customer.module.ts @@ -0,0 +1,22 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SharedModule } from '../../common/shared.module'; +import { CustomerRepository } from './customer.repository'; +import { CustomerService } from './customer.service'; +import { CustomerController } from './controllers/customer.controller'; +import { BillModule } from '../bills/bill.module'; +import { ArticleModule } from '../article/article.module'; +import { Customer } from './entities/customer.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Customer]), + SharedModule, + forwardRef(() => BillModule), + ArticleModule, + ], + controllers: [CustomerController], + providers: [CustomerRepository, CustomerService], + exports: [CustomerService], +}) +export class CustomerModule {} diff --git a/apps/server/src/models/customer/customer.repository.ts b/apps/server/src/models/customer/customer.repository.ts new file mode 100644 index 0000000..8613f40 --- /dev/null +++ b/apps/server/src/models/customer/customer.repository.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ClassTransformOptions } from '@nestjs/common/interfaces/external/class-transform-options.interface'; +import { ModelRepository } from '../model.repository'; +import { Customer } from './entities/customer.entity'; +import { allCustomerForSerializing, CustomerEntity } from './serializers/customer.serializer'; + +@Injectable() +export class CustomerRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(Customer, dataSource.createEntityManager()); + } + + transform(model: Customer): CustomerEntity { + const tranformOptions: ClassTransformOptions = { + groups: allCustomerForSerializing, + }; + return plainToInstance( + CustomerEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: Customer[]): CustomerEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/customer/customer.service.ts b/apps/server/src/models/customer/customer.service.ts new file mode 100644 index 0000000..ac8f03d --- /dev/null +++ b/apps/server/src/models/customer/customer.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; +import { BaseService } from '../../common/base.service'; +import { CustomerRepository } from './customer.repository'; +import { Customer } from './entities/customer.entity'; +import { CustomerEntity } from './serializers/customer.serializer'; + +@Injectable() +export class CustomerService extends BaseService { + constructor( + private readonly customerRepository: CustomerRepository, + ) { + super(customerRepository); + } +} diff --git a/apps/server/src/models/customer/dto/create-customer.dto.ts b/apps/server/src/models/customer/dto/create-customer.dto.ts new file mode 100644 index 0000000..12f021b --- /dev/null +++ b/apps/server/src/models/customer/dto/create-customer.dto.ts @@ -0,0 +1,90 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional } from 'class-validator'; +import { ICustomer } from '../interfaces/customer.interface'; + +export class CreateCustomerDto implements ICustomer { + + @ApiProperty({ + example: 'Simon', + description: 'Firstname', + }) + @IsOptional() + firstName: string; + + @ApiProperty({ + example: 'Abler', + description: 'Lastname', + }) + @IsOptional() + lastName: string; + + @ApiProperty({ + example: '00002', + description: 'Customer number, has to be unique', + }) + @IsNotEmpty() + customerNumber: string; + + @ApiProperty({ + example: '20', + description: 'Generall Discount of customer', + }) + @IsOptional() + customerDiscount: number = 0; + + @ApiProperty({ + example: 'simon.abler@gmail.com', + description: 'email', + }) + @IsOptional() + email: string; + + @ApiProperty({ + example: 'Abler gmbh', + description: 'Company name', + }) + @IsOptional() + companyName: string; + + @ApiProperty({ + example: 'Musterstraße 4', + description: 'Adress with Number', + }) + @IsOptional() + address: string; + + @ApiProperty({ + example: '6020', + description: 'Postcode', + }) + @IsOptional() + postcode: string; + + @ApiProperty({ + example: '0000033', + description: 'Phonenumber company', + }) + @IsOptional() + phoneCompany: string; + + @ApiProperty({ + example: '00000023', + description: 'Phonenumber private', + }) + @IsOptional() + phonePrivate: string; + + @ApiProperty({ + example: 'AT-555422', + description: 'UID number', + }) + @IsOptional() + uid: string; + + @ApiProperty({ + example: 'Austria', + description: 'Country', + }) + @IsOptional() + country: string; +} diff --git a/apps/server/src/models/customer/dto/update-customer.dto.ts b/apps/server/src/models/customer/dto/update-customer.dto.ts new file mode 100644 index 0000000..4888af1 --- /dev/null +++ b/apps/server/src/models/customer/dto/update-customer.dto.ts @@ -0,0 +1,5 @@ +import { CreateCustomerDto } from './create-customer.dto'; + +export class UpdateCustomerDto extends CreateCustomerDto { + +} diff --git a/apps/server/src/models/customer/entities/customer.entity.ts b/apps/server/src/models/customer/entities/customer.entity.ts new file mode 100644 index 0000000..4bb65d1 --- /dev/null +++ b/apps/server/src/models/customer/entities/customer.entity.ts @@ -0,0 +1,58 @@ +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + OneToMany, +} from 'typeorm'; +import { Discount } from '../../bills/entities/discount.entity'; +import { Slipsheet } from '../../bills/entities/slipsheet.entity'; +import { ICustomer } from '../interfaces/customer.interface'; + + +@Entity({ name: 'customer' }) +export class Customer implements ICustomer { + @PrimaryGeneratedColumn('increment') + id: number; + @Column({ length: 150 }) + firstName: string; + @Column({ length: 150 }) + lastName: string; + @Column({ nullable: false, unique: true, length: 150 }) + customerNumber: string; + @Column({ nullable: false, default: 0 }) + customerDiscount: number; + @Column({ length: 150 }) + email: string; + @Column({ length: 150 }) + companyName: string; + @Column({ length: 150 }) + address: string; + @Column({ length: 10 }) + postcode: string; + @Column({ length: 150 }) + phoneCompany: string; + @Column({ length: 150 }) + phonePrivate: string; + @Column({ length: 50 }) + uid: string; + @Column({ length: 50 }) + country: string; + + @OneToMany(() => Discount, (discount) => discount.customer) + discounts: Discount[]; + + @OneToMany(() => Slipsheet, (slipsheets) => slipsheets.customer) + slipsheets: Slipsheet[]; + + @CreateDateColumn({ + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/apps/server/src/models/customer/interfaces/customer.interface.ts b/apps/server/src/models/customer/interfaces/customer.interface.ts new file mode 100644 index 0000000..4241039 --- /dev/null +++ b/apps/server/src/models/customer/interfaces/customer.interface.ts @@ -0,0 +1,14 @@ +export interface ICustomer { + firstName: string; + lastName: string; + customerNumber: string; + customerDiscount: number; + email: string; + companyName: string; + address: string; + postcode: string; + phoneCompany: string; + phonePrivate: string; + uid: string; + country: string; +} diff --git a/apps/server/src/models/customer/serializers/customer.serializer.ts b/apps/server/src/models/customer/serializers/customer.serializer.ts new file mode 100644 index 0000000..f1bc60d --- /dev/null +++ b/apps/server/src/models/customer/serializers/customer.serializer.ts @@ -0,0 +1,53 @@ +import { Expose, Type } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { DiscountEntity } from '../../bills/serializers/discount.serializer'; +import { ICustomer } from '../interfaces/customer.interface'; + +export const defaultCustomerForSerializing: string[] = [ + 'default', + 'slipsheet.timestamps', +]; +export const extendedCustomerForSerializing: string[] = [ + 'customer.timestamps', + ...defaultCustomerForSerializing, +]; +export const allCustomerForSerializing: string[] = [ + ...extendedCustomerForSerializing, +]; +export class CustomerEntity + extends ModelEntity + implements ICustomer { + + @Expose({ groups: ['default'] }) + firstName: string; + @Expose({ groups: ['default'] }) + lastName: string; + @Expose({ groups: ['default'] }) + customerNumber: string; + @Expose({ groups: ['default'] }) + customerDiscount: number; + @Expose({ groups: ['default'] }) + email: string; + @Expose({ groups: ['default'] }) + companyName: string; + @Expose({ groups: ['default'] }) + address: string; + @Expose({ groups: ['default'] }) + postcode: string; + @Expose({ groups: ['default'] }) + phoneCompany: string; + @Expose({ groups: ['default'] }) + phonePrivate: string; + @Expose({ groups: ['default'] }) + uid: string; + @Expose({ groups: ['default'] }) + country: string; + @Expose({ groups: ['default'] }) + @Type(() => DiscountEntity) + discounts: DiscountEntity[]; + + @Expose({ groups: ['customer.timestamps'] }) + createdAt: Date; + @Expose({ groups: ['customer.timestamps'] }) + updatedAt: Date; +} diff --git a/apps/server/src/models/model.repository.ts b/apps/server/src/models/model.repository.ts new file mode 100644 index 0000000..af4073d --- /dev/null +++ b/apps/server/src/models/model.repository.ts @@ -0,0 +1,138 @@ +import { plainToInstance } from 'class-transformer'; +import { EntityManager, EntityTarget, Repository, DeepPartial, In, InsertResult } from 'typeorm'; +import { NotFoundException } from '@nestjs/common'; +import { ModelEntity } from '../common/serializers/model.serializer'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; + +export class ModelRepository extends Repository { + constructor(target: EntityTarget, manager: EntityManager) { + super(target, manager); + } + + async get( + id: number, + relations: string[] = [], + throwsException = false, + ): Promise { + return this.findOne({ + where: { id } as any, + relations, + }) + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + + return Promise.resolve(entity ? this.transform(entity) : null); + }) + .catch((error) => Promise.reject(error)); + } + + async getAll( + ids: number[], + relations: string[] = [], + throwsException = false, + ): Promise { + return this.find({ + where: { id: In(ids) } as any, + relations, + }) + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + + return Promise.resolve(entity ? this.transformMany(entity) : null); + }) + .catch((error) => Promise.reject(error)); + } + + async findAll({ + filter = null, + relations = [], + throwsException = false, + }: { + filter?: any; + relations?: string[]; + throwsException?: boolean; + }): Promise { + return await this.find({ + where: filter, + relations, + }) + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + + return Promise.resolve(entity ? this.transformMany(entity) : null); + }) + .catch((error) => Promise.reject(error)); + } + + async findOneAsync( + where: any = {}, + relations: string[] = [], + throwsException = false, + ): Promise { + return await this.findOne({ + where, + relations, + }) + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + + return Promise.resolve(entity ? this.transform(entity) : null); + }) + .catch((error) => Promise.reject(error)); + } + + async createEntity( + inputs: DeepPartial, + relations: string[] = [], + ): Promise { + return ( + this.insert(inputs as any) + .then( + async (entity: InsertResult) => + await this.get(entity.identifiers[0].id, relations), + ) + .catch((error) => Promise.reject(error)) + ); + } + + async updateEntity( + id: number, + inputs: QueryDeepPartialEntity, + relations: string[] = [], + ): Promise { + return this.update(id, inputs) + .then(async () => + await this.get(id, relations) + ) + .catch((error) => Promise.reject(error)); + } + + + async updateEntities( + ids: number[], + inputs: QueryDeepPartialEntity, + relations: string[] = [], + ): Promise { + return this.update(ids, inputs) + .then(async () => + await this.getAll(ids, relations) + ) + .catch((error) => Promise.reject(error)); + } + + transform(model: T, transformOptions = {}): K { + return plainToInstance(ModelEntity, model, transformOptions) as K; + } + + transformMany(models: T[], transformOptions = {}): K[] { + return models.map((model) => this.transform(model, transformOptions)); + } +} diff --git a/apps/server/src/models/settings/company-settings.controller.ts b/apps/server/src/models/settings/company-settings.controller.ts new file mode 100644 index 0000000..fbe35b9 --- /dev/null +++ b/apps/server/src/models/settings/company-settings.controller.ts @@ -0,0 +1,120 @@ +import { + BadRequestException, + Body, + ClassSerializerInterceptor, + Controller, + Get, + Post, + Put, + SerializeOptions, + UploadedFile, + UseInterceptors, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { ApiBearerAuth, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { diskStorage } from 'multer'; +import { extname, join } from 'path'; +import { ReS } from '../../common/res.model'; +import { UpdateSettingsDto } from './dto/update-settings.dto'; +import { CompanySettingsService } from './company-settings.service'; +import { + CompanySettingsEntity, + defaultSettingsGroupsForSerializing, +} from './serializers/company-settings.serializer'; + +const UPLOAD_DEST = join(process.cwd(), 'uploads', 'settings'); + +function storageConfig(fieldName: string) { + return diskStorage({ + destination: UPLOAD_DEST, + filename: (_req, file, cb) => { + cb(null, `${fieldName}${extname(file.originalname)}`); + }, + }); +} + +function pdfOnlyFilter( + _req: unknown, + file: Express.Multer.File, + cb: (error: Error | null, acceptFile: boolean) => void, +) { + const isPdf = + file.mimetype === 'application/pdf' || + file.originalname.toLowerCase().endsWith('.pdf'); + + if (!isPdf) { + cb(new BadRequestException('Nur PDF-Dateien sind erlaubt'), false); + return; + } + + cb(null, true); +} + +@ApiBearerAuth() +@Controller('settings') +@ApiTags('settings') +@UseInterceptors(ClassSerializerInterceptor) +@SerializeOptions({ groups: defaultSettingsGroupsForSerializing, excludeExtraneousValues: true }) +export class CompanySettingsController { + constructor(private readonly settingsService: CompanySettingsService) {} + + @Get('/') + @ApiOperation({ summary: 'Einstellungen laden' }) + async get(): Promise> { + return ReS.FromData(await this.settingsService.get()); + } + + @Put('/') + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + @ApiOperation({ summary: 'Einstellungen speichern' }) + async upsert(@Body() dto: UpdateSettingsDto): Promise> { + return ReS.FromData(await this.settingsService.upsert(dto)); + } + + @Post('/logo') + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Logo hochladen (SVG oder PNG)' }) + @UseInterceptors(FileInterceptor('file', { storage: storageConfig('logo') })) + async uploadLogo(@UploadedFile() file: Express.Multer.File): Promise> { + const relativePath = join('uploads', 'settings', file.filename); + return ReS.FromData(await this.settingsService.updateAssetPath('logoPath', relativePath)); + } + + @Post('/badge1') + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Badge links hochladen' }) + @UseInterceptors(FileInterceptor('file', { storage: storageConfig('badge1') })) + async uploadBadge1(@UploadedFile() file: Express.Multer.File): Promise> { + const relativePath = join('uploads', 'settings', file.filename); + return ReS.FromData(await this.settingsService.updateAssetPath('badge1Path', relativePath)); + } + + @Post('/badge2') + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Badge rechts hochladen' }) + @UseInterceptors(FileInterceptor('file', { storage: storageConfig('badge2') })) + async uploadBadge2(@UploadedFile() file: Express.Multer.File): Promise> { + const relativePath = join('uploads', 'settings', file.filename); + return ReS.FromData(await this.settingsService.updateAssetPath('badge2Path', relativePath)); + } + + @Post('/template-pdf') + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Template-PDF hochladen' }) + @UseInterceptors( + FileInterceptor('file', { + storage: storageConfig('template'), + fileFilter: pdfOnlyFilter as any, + }), + ) + async uploadTemplatePdf( + @UploadedFile() file: Express.Multer.File, + ): Promise> { + const relativePath = join('uploads', 'settings', file.filename); + return ReS.FromData( + await this.settingsService.updateAssetPath('templatePdfPath', relativePath), + ); + } +} diff --git a/apps/server/src/models/settings/company-settings.module.ts b/apps/server/src/models/settings/company-settings.module.ts new file mode 100644 index 0000000..93973f4 --- /dev/null +++ b/apps/server/src/models/settings/company-settings.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CompanySettings } from './entities/company-settings.entity'; +import { CompanySettingsRepository } from './company-settings.repository'; +import { CompanySettingsService } from './company-settings.service'; +import { CompanySettingsController } from './company-settings.controller'; +import { MulterModule } from '@nestjs/platform-express'; +import { join } from 'path'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([CompanySettings]), + MulterModule.register({ dest: join(process.cwd(), 'uploads', 'settings') }), + ], + controllers: [CompanySettingsController], + providers: [CompanySettingsRepository, CompanySettingsService], + exports: [CompanySettingsService], +}) +export class CompanySettingsModule {} diff --git a/apps/server/src/models/settings/company-settings.repository.ts b/apps/server/src/models/settings/company-settings.repository.ts new file mode 100644 index 0000000..8bb3c89 --- /dev/null +++ b/apps/server/src/models/settings/company-settings.repository.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ModelRepository } from '../model.repository'; +import { CompanySettings } from './entities/company-settings.entity'; +import { + CompanySettingsEntity, + defaultSettingsGroupsForSerializing, +} from './serializers/company-settings.serializer'; + +@Injectable() +export class CompanySettingsRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(CompanySettings, dataSource.createEntityManager()); + } + + transform(model: CompanySettings): CompanySettingsEntity { + const transformOptions = { groups: defaultSettingsGroupsForSerializing }; + return plainToInstance( + CompanySettingsEntity, + instanceToPlain(model, transformOptions), + transformOptions, + ); + } + + transformMany(models: CompanySettings[]): CompanySettingsEntity[] { + return models.map((m) => this.transform(m)); + } +} diff --git a/apps/server/src/models/settings/company-settings.service.ts b/apps/server/src/models/settings/company-settings.service.ts new file mode 100644 index 0000000..a347638 --- /dev/null +++ b/apps/server/src/models/settings/company-settings.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common'; +import { CompanySettingsRepository } from './company-settings.repository'; +import { CompanySettingsEntity } from './serializers/company-settings.serializer'; +import { UpdateSettingsDto } from './dto/update-settings.dto'; +import { CompanySettings } from './entities/company-settings.entity'; + +@Injectable() +export class CompanySettingsService { + constructor(private readonly repo: CompanySettingsRepository) {} + + async get(): Promise { + return this.repo.get(1, [], false); + } + + async upsert(dto: UpdateSettingsDto): Promise { + const existing = await this.repo.findOne({ where: { id: 1 } }); + if (existing) { + return this.repo.updateEntity(1, dto as any); + } + const created = await this.repo.save({ id: 1, ...dto } as CompanySettings); + return this.repo.transform(created); + } + + async updateAssetPath( + field: 'logoPath' | 'badge1Path' | 'badge2Path' | 'templatePdfPath', + path: string, + ): Promise { + const existing = await this.repo.findOne({ where: { id: 1 } }); + if (existing) { + return this.repo.updateEntity(1, { [field]: path } as any); + } + const created = await this.repo.save( + Object.assign(new CompanySettings(), { id: 1, [field]: path }), + ); + return this.repo.transform(created); + } +} diff --git a/apps/server/src/models/settings/dto/update-settings.dto.ts b/apps/server/src/models/settings/dto/update-settings.dto.ts new file mode 100644 index 0000000..2e376b2 --- /dev/null +++ b/apps/server/src/models/settings/dto/update-settings.dto.ts @@ -0,0 +1,115 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsOptional, + IsString, + IsNumber, + IsArray, + ValidateNested, + Min, + Max, + IsIn, +} from 'class-validator'; +import { Type } from 'class-transformer'; + +export class BankAccountDto { + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsString() + iban: string; + + @ApiProperty() + @IsString() + bic: string; +} + +export class UpdateSettingsDto { + @ApiPropertyOptional() + @IsOptional() + @IsString() + companyName?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + street?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + zip?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + city?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + country?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + phone?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + email?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + website?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + firmenbuchnummer?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + vatId?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + issueCity?: string; + + @ApiPropertyOptional({ enum: ['generated', 'disabled', 'template_pdf'] }) + @IsOptional() + @IsIn(['generated', 'disabled', 'template_pdf']) + letterheadMode?: 'generated' | 'disabled' | 'template_pdf'; + + @ApiPropertyOptional() + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(0) + @Max(100) + vatRate?: number; + + @ApiPropertyOptional() + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(0) + paymentTermDays?: number; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + paymentFooterText?: string; + + @ApiPropertyOptional({ type: [BankAccountDto] }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => BankAccountDto) + bankAccounts?: BankAccountDto[]; +} diff --git a/apps/server/src/models/settings/entities/company-settings.entity.ts b/apps/server/src/models/settings/entities/company-settings.entity.ts new file mode 100644 index 0000000..ced7c44 --- /dev/null +++ b/apps/server/src/models/settings/entities/company-settings.entity.ts @@ -0,0 +1,79 @@ +import { + Column, + Entity, + PrimaryColumn, + UpdateDateColumn, +} from 'typeorm'; +import { + ICompanySettings, + LetterheadMode, +} from '../interfaces/company-settings.interface'; + +@Entity({ name: 'company_settings' }) +export class CompanySettings implements ICompanySettings { + @PrimaryColumn({ default: 1 }) + id: number; + + @Column({ nullable: true, default: null }) + companyName: string | null; + + @Column({ nullable: true, default: null }) + street: string | null; + + @Column({ nullable: true, default: null }) + zip: string | null; + + @Column({ nullable: true, default: null }) + city: string | null; + + @Column({ nullable: true, default: 'Oesterreich' }) + country: string | null; + + @Column({ nullable: true, default: null }) + phone: string | null; + + @Column({ nullable: true, default: null }) + email: string | null; + + @Column({ nullable: true, default: null }) + website: string | null; + + @Column({ nullable: true, default: null }) + firmenbuchnummer: string | null; + + @Column({ nullable: true, default: null }) + vatId: string | null; + + @Column({ nullable: true, default: null }) + issueCity: string | null; + + @Column({ default: 20 }) + vatRate: number; + + @Column({ default: 14 }) + paymentTermDays: number; + + @Column({ nullable: true, default: null, type: 'text' }) + paymentFooterText: string | null; + + @Column({ type: 'simple-json', nullable: true, default: '[]' }) + bankAccounts: Array<{ name: string; iban: string; bic: string }>; + + @Column({ nullable: true, default: null }) + logoPath: string | null; + + @Column({ nullable: true, default: null }) + badge1Path: string | null; + + @Column({ nullable: true, default: null }) + badge2Path: string | null; + + @Column({ nullable: false, default: 'generated', length: 20 }) + letterheadMode: LetterheadMode; + + @Column({ nullable: true, default: null }) + templatePdfPath: string | null; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; +} diff --git a/apps/server/src/models/settings/interfaces/company-settings.interface.ts b/apps/server/src/models/settings/interfaces/company-settings.interface.ts new file mode 100644 index 0000000..cf00550 --- /dev/null +++ b/apps/server/src/models/settings/interfaces/company-settings.interface.ts @@ -0,0 +1,26 @@ +export type LetterheadMode = 'generated' | 'disabled' | 'template_pdf'; + +export interface ICompanySettings { + id: number; + companyName: string | null; + street: string | null; + zip: string | null; + city: string | null; + country: string | null; + phone: string | null; + email: string | null; + website: string | null; + firmenbuchnummer: string | null; + vatId: string | null; + issueCity: string | null; + vatRate: number; + paymentTermDays: number; + paymentFooterText: string | null; + bankAccounts: Array<{ name: string; iban: string; bic: string }>; + logoPath: string | null; + badge1Path: string | null; + badge2Path: string | null; + letterheadMode: LetterheadMode; + templatePdfPath: string | null; + updatedAt: Date; +} diff --git a/apps/server/src/models/settings/serializers/company-settings.serializer.ts b/apps/server/src/models/settings/serializers/company-settings.serializer.ts new file mode 100644 index 0000000..97763cb --- /dev/null +++ b/apps/server/src/models/settings/serializers/company-settings.serializer.ts @@ -0,0 +1,29 @@ +import { Expose } from 'class-transformer'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; +import { ICompanySettings } from '../interfaces/company-settings.interface'; + +export const defaultSettingsGroupsForSerializing: string[] = ['default', 'settings.default']; + +export class CompanySettingsEntity extends ModelEntity implements ICompanySettings { + @Expose({ groups: ['default'] }) companyName: string | null; + @Expose({ groups: ['default'] }) street: string | null; + @Expose({ groups: ['default'] }) zip: string | null; + @Expose({ groups: ['default'] }) city: string | null; + @Expose({ groups: ['default'] }) country: string | null; + @Expose({ groups: ['default'] }) phone: string | null; + @Expose({ groups: ['default'] }) email: string | null; + @Expose({ groups: ['default'] }) website: string | null; + @Expose({ groups: ['default'] }) firmenbuchnummer: string | null; + @Expose({ groups: ['default'] }) vatId: string | null; + @Expose({ groups: ['default'] }) issueCity: string | null; + @Expose({ groups: ['default'] }) vatRate: number; + @Expose({ groups: ['default'] }) paymentTermDays: number; + @Expose({ groups: ['default'] }) paymentFooterText: string | null; + @Expose({ groups: ['default'] }) bankAccounts: Array<{ name: string; iban: string; bic: string }>; + @Expose({ groups: ['default'] }) logoPath: string | null; + @Expose({ groups: ['default'] }) badge1Path: string | null; + @Expose({ groups: ['default'] }) badge2Path: string | null; + @Expose({ groups: ['default'] }) letterheadMode: 'generated' | 'disabled' | 'template_pdf'; + @Expose({ groups: ['default'] }) templatePdfPath: string | null; + @Expose({ groups: ['default'] }) updatedAt: Date; +} diff --git a/apps/server/src/models/users/decorators/user.decorator.ts b/apps/server/src/models/users/decorators/user.decorator.ts new file mode 100644 index 0000000..0957665 --- /dev/null +++ b/apps/server/src/models/users/decorators/user.decorator.ts @@ -0,0 +1,8 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export const EntityBeingQueried = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + return request.user; + }, +); diff --git a/apps/server/src/models/users/dto/create-user.dto.ts b/apps/server/src/models/users/dto/create-user.dto.ts new file mode 100644 index 0000000..671895b --- /dev/null +++ b/apps/server/src/models/users/dto/create-user.dto.ts @@ -0,0 +1,43 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsEmail, Length } from 'class-validator'; + +export class CreateUserDto { + @ApiProperty({ + example: 'trinhchin', + description: 'The name of the User', + }) + @Length(5, 20) + @IsNotEmpty() + readonly name: string; + + @ApiProperty({ + example: 'username', + description: 'The name of the User', + }) + @Length(1, 20) + @IsNotEmpty() + readonly username: string; + + @ApiProperty({ + example: 'trinhchin.innos@gmail.com', + description: 'The email of the User', + }) + @IsEmail() + @IsNotEmpty() + readonly email: string; + + @ApiProperty({ + example: '0password', + description: 'The password of the User', + }) + @IsNotEmpty() + readonly password: string; + + @ApiProperty({ + example: '11111111', + description: 'The referralCode of the User', + }) + @Length(8, 8) + @IsNotEmpty() + readonly referralCode: string; +} diff --git a/apps/server/src/models/users/dto/login-response.dto.ts b/apps/server/src/models/users/dto/login-response.dto.ts new file mode 100644 index 0000000..c071dab --- /dev/null +++ b/apps/server/src/models/users/dto/login-response.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty } from 'class-validator'; +import { User } from '../entities/user.entity'; +import { UserEntity } from '../serializers/user.serializer'; + +export class LoginResponseDto { + @ApiProperty({ + example: User, + description: 'The user of the LoginResponse', + }) + @IsNotEmpty() + readonly user: UserEntity; + + @ApiProperty({ + example: 'xxxxxxxxxx', + description: 'The accessToken of the LoginResponse', + }) + @IsNotEmpty() + readonly accessToken: string; + + @ApiProperty({ + example: 60 * 60 * 24 * 30, + description: 'The expiresIn of the LoginResponse', + }) + @IsNotEmpty() + readonly expiresIn: number; +} diff --git a/apps/server/src/models/users/dto/update-user.dto.ts b/apps/server/src/models/users/dto/update-user.dto.ts new file mode 100644 index 0000000..2c8d1e9 --- /dev/null +++ b/apps/server/src/models/users/dto/update-user.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Length, IsOptional, IsEmail } from 'class-validator'; + +export class UpdateUserDto { + @ApiProperty({ + required: false, + description: 'The name of the User ', + }) + @Length(5, 20) + @IsOptional() + readonly name: string; + + @ApiProperty({ + required: false, + description: 'The email', + }) + @IsEmail() + @IsOptional() + readonly email: string; + + @ApiProperty({ + required: false, + description: 'The referralCode of the User', + }) + @Length(8, 8) + @IsOptional() + readonly referralCode: string; +} diff --git a/apps/server/src/models/users/entities/user.entity.ts b/apps/server/src/models/users/entities/user.entity.ts new file mode 100644 index 0000000..2730f2f --- /dev/null +++ b/apps/server/src/models/users/entities/user.entity.ts @@ -0,0 +1,32 @@ +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { IUser } from '../interfaces/user.interface'; + +@Entity({ name: 'users' }) +export class User implements IUser { + @PrimaryGeneratedColumn('increment') + id: string; + + @Column({ nullable: true, default: null }) + email: string; + + @Column({ nullable: true, default: null }) + name: null | string; + + @Column({ nullable: false, default: null, unique: true }) + username: null | string; + + @Column() + password: string; + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; +} diff --git a/apps/server/src/models/users/interfaces/user.interface.ts b/apps/server/src/models/users/interfaces/user.interface.ts new file mode 100644 index 0000000..ef98345 --- /dev/null +++ b/apps/server/src/models/users/interfaces/user.interface.ts @@ -0,0 +1,6 @@ +export interface IUser { + email: string; + name: null | string; + username: null | string; + password: string; +} diff --git a/apps/server/src/models/users/serializers/user.serializer.ts b/apps/server/src/models/users/serializers/user.serializer.ts new file mode 100644 index 0000000..b5d22ad --- /dev/null +++ b/apps/server/src/models/users/serializers/user.serializer.ts @@ -0,0 +1,37 @@ +import { Expose } from 'class-transformer'; +import { IUser } from '../interfaces/user.interface'; +import { ModelEntity } from '../../../common/serializers/model.serializer'; + +export const defaultUserGroupsForSerializing: string[] = [ + 'default', + 'user.default', +]; +export const nameUserGroupsForSerializing: string[] = ['user.name']; +export const extendedUserGroupsForSerializing: string[] = [ + ...defaultUserGroupsForSerializing, + 'user.timestamps', +]; +export const allUserGroupsForSerializing: string[] = [ + ...extendedUserGroupsForSerializing, + 'user.password', +]; +export class UserEntity extends ModelEntity implements IUser { + @Expose({ groups: ['default', 'user.default', 'user.name'] }) + email: string; + @Expose({ groups: ['default', 'user.default', 'user.name'] }) + name: null | string; + @Expose({ groups: ['default', 'user.default', 'user.name'] }) + username: null | string; + @Expose({ groups: ['user.password'] }) + password: string; + @Expose({ groups: ['default', 'user.default'] }) + ldapUser: boolean; + @Expose({ groups: ['user.timestamps'] }) + createdAt: Date; + @Expose({ groups: ['user.timestamps'] }) + updatedAt: Date; + + getName() { + return this.name; + } +} diff --git a/apps/server/src/models/users/users.controller.ts b/apps/server/src/models/users/users.controller.ts new file mode 100644 index 0000000..21b0b4a --- /dev/null +++ b/apps/server/src/models/users/users.controller.ts @@ -0,0 +1,99 @@ +import { + Get, + Put, + Body, + Controller, + UseInterceptors, + SerializeOptions, + ClassSerializerInterceptor, + Param, + ValidationPipe, + UsePipes, + Delete, +} from '@nestjs/common'; +import { + UserEntity, + extendedUserGroupsForSerializing, +} from './serializers/user.serializer'; +import { UsersService } from './users.service'; +import { EntityBeingQueried } from './decorators/user.decorator'; +import { UpdateUserDto } from './dto/update-user.dto'; +import { + ApiBearerAuth, + ApiExtraModels, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReS } from '../../common/res.model'; + +@ApiBearerAuth() +@Controller('users') +@ApiTags('users') +@ApiExtraModels(ReS) +@ApiResponse({ status: 403, description: 'Forbidden.' }) +@SerializeOptions({ + groups: extendedUserGroupsForSerializing, +}) +export class UsersController { + constructor( + private readonly usersService: UsersService, + ) { + } + + @Get('/:id') + @UseInterceptors(ClassSerializerInterceptor) + @ApiOperation({ + summary: 'Get specified user', + description: 'Fetches data of id', + }) + async get( + @Param('id') id: string, + @EntityBeingQueried() user: UserEntity, + ): Promise> { + return ReS.FromData( + await this.usersService.get(+id, ['roles', 'roles.permissions']), + ); + } + + @Get('/') + @UseInterceptors(ClassSerializerInterceptor) + @ApiOperation({ + summary: 'Get all users', + description: 'Fetches all data', + }) + async getAll(): Promise> { + return ReS.FromData( + await this.usersService.getAll(['roles', 'roles.permissions']), + ); + } + + @Put('/:id') + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + @ApiOperation({ + summary: 'Update specified user', + description: 'Fetches new data and updates values', + }) + async update( + @Param('id') id: string, + @EntityBeingQueried() user: UserEntity, + @Body() + inputs: UpdateUserDto, + ): Promise> { + const userToUpdate = await this.usersService.get(+id); + const userPostUpdate = await this.usersService.update(userToUpdate, inputs); + + return ReS.FromData(userPostUpdate); + } + + @Delete('/:id') + @UseInterceptors(ClassSerializerInterceptor) + @ApiOperation({ + summary: 'Delete specified user', + description: 'ATTENTION only for sa and admin user', + }) + async delete(@Param('id') id: string): Promise> { + await this.usersService.delete(+id, true); + return ReS.FromData(null); + } +} diff --git a/apps/server/src/models/users/users.module.ts b/apps/server/src/models/users/users.module.ts new file mode 100644 index 0000000..a19c4fa --- /dev/null +++ b/apps/server/src/models/users/users.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { UsersService } from './users.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UsersRepository } from './users.repository'; +import { UsersController } from './users.controller'; +import { SharedModule } from '../../common/shared.module'; +import { User } from './entities/user.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([User]), + SharedModule, + ], + controllers: [UsersController], + providers: [UsersRepository, UsersService], + exports: [UsersService], +}) +export class UsersModule {} diff --git a/apps/server/src/models/users/users.repository.ts b/apps/server/src/models/users/users.repository.ts new file mode 100644 index 0000000..f7efba4 --- /dev/null +++ b/apps/server/src/models/users/users.repository.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { ModelRepository } from '../model.repository'; +import { User } from './entities/user.entity'; +import { extendedUserGroupsForSerializing, UserEntity } from './serializers/user.serializer'; + +@Injectable() +export class UsersRepository extends ModelRepository { + constructor(private dataSource: DataSource) { + super(User, dataSource.createEntityManager()); + } + + transform(model: User): UserEntity { + const tranformOptions = { + groups: extendedUserGroupsForSerializing, + }; + return plainToInstance( + UserEntity, + instanceToPlain(model, tranformOptions), + tranformOptions, + ); + } + + transformMany(models: User[]): UserEntity[] { + return models.map((model) => this.transform(model)); + } +} diff --git a/apps/server/src/models/users/users.service.ts b/apps/server/src/models/users/users.service.ts new file mode 100644 index 0000000..4b78c6c --- /dev/null +++ b/apps/server/src/models/users/users.service.ts @@ -0,0 +1,69 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { UsersRepository } from './users.repository'; +import { UserEntity } from './serializers/user.serializer'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; + +@Injectable() +export class UsersService { + constructor( + private readonly usersRepository: UsersRepository, + ) { } + + get( + id: number, + relations: string[] = [], + throwsException = true, + ): Promise { + return this.usersRepository.get(id, relations, throwsException); + } + + getAll( + relations: string[] = [], + throwsException = true, + ): Promise { + return this.usersRepository.findAll({ relations, throwsException }); + } + + async delete(id: number, throwsException = true): Promise { + return this.usersRepository + .delete(id) + .then((result) => { + if (throwsException && (!result.affected || result.affected === 0)) { + return Promise.reject(new NotFoundException('Model not found.')); + } + return Promise.resolve(true); + }) + .catch((error) => Promise.reject(error)); + } + + async getByName( + name: number, + relations: string[] = [], + throwsException = false, + ): Promise { + return this.usersRepository + .findOne({ + where: { username: String(name) }, + relations, + }) + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + + return Promise.resolve( + entity ? this.usersRepository.transform(entity) : null, + ); + }) + .catch((error) => Promise.reject(error)); + } + + create(inputs: CreateUserDto, relations: string[] = []): Promise { + return this.usersRepository.createEntity(inputs, relations); + } + + update(user: UserEntity, inputs: UpdateUserDto): Promise { + return this.usersRepository.updateEntity(user.id as any, inputs); + } +} diff --git a/apps/server/src/providers/database/sqlite/provider.module.ts b/apps/server/src/providers/database/sqlite/provider.module.ts new file mode 100644 index 0000000..1aba828 --- /dev/null +++ b/apps/server/src/providers/database/sqlite/provider.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule, TypeOrmModuleAsyncOptions } from '@nestjs/typeorm'; +import { SqliteConfigService } from '../../../config/database/sqlite/config.service'; +import { SqliteConfigModule } from '../../../config/database/sqlite/config.module'; + +@Module({ + imports: [ + TypeOrmModule.forRootAsync({ + imports: [SqliteConfigModule], + useFactory: async (sqliteConfigService: SqliteConfigService) => ({ + type: 'better-sqlite3' as any, + database: sqliteConfigService.path, + entities: [sqliteConfigService.entities], + migrations: ['dist/migration/*.js'], + migrationsRun: sqliteConfigService.migrationsRun, + synchronize: sqliteConfigService.synchronizeRun, + logging: true, + }), + inject: [SqliteConfigService], + } as TypeOrmModuleAsyncOptions), + ], +}) +export class SqliteDatabaseProviderModule {} diff --git a/apps/server/tsconfig.app.json b/apps/server/tsconfig.app.json new file mode 100644 index 0000000..582004f --- /dev/null +++ b/apps/server/tsconfig.app.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "target": "ES2019", + "lib": ["ES2019"], + "types": ["node"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "strictPropertyInitialization": false, + "strict": false, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true + }, + "include": ["src/**/*.ts"], + "exclude": [ + "jest.config.ts", + "jest.config.cts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ] +} diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json new file mode 100644 index 0000000..c1e2dd4 --- /dev/null +++ b/apps/server/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/apps/server/tsconfig.spec.json b/apps/server/tsconfig.spec.json new file mode 100644 index 0000000..01c59b6 --- /dev/null +++ b/apps/server/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "moduleResolution": "node10", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "jest.config.cts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/apps/server/uploads/settings/.gitkeep b/apps/server/uploads/settings/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/server/uploads/settings/.gitkeep @@ -0,0 +1 @@ + diff --git a/apps/sim-system-e2e/eslint.config.mjs b/apps/sim-system-e2e/eslint.config.mjs new file mode 100644 index 0000000..b2e9fac --- /dev/null +++ b/apps/sim-system-e2e/eslint.config.mjs @@ -0,0 +1,12 @@ +import playwright from 'eslint-plugin-playwright'; +import baseConfig from '../../eslint.config.mjs'; + +export default [ + playwright.configs['flat/recommended'], + ...baseConfig, + { + files: ['**/*.ts', '**/*.js'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/apps/sim-system-e2e/playwright.config.ts b/apps/sim-system-e2e/playwright.config.ts new file mode 100644 index 0000000..ece5b99 --- /dev/null +++ b/apps/sim-system-e2e/playwright.config.ts @@ -0,0 +1,68 @@ +import { defineConfig, devices } from '@playwright/test'; +import { nxE2EPreset } from '@nx/playwright/preset'; +import { workspaceRoot } from '@nx/devkit'; + +// For CI, you may want to set BASE_URL to the deployed application. +const baseURL = process.env['BASE_URL'] || 'http://localhost:4200'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npx nx run sim-system:serve', + url: 'http://localhost:4200', + reuseExistingServer: true, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + // Uncomment for mobile browsers support + /* { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, */ + + // Uncomment for branded browsers + /* { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + } */ + ], +}); diff --git a/apps/sim-system-e2e/project.json b/apps/sim-system-e2e/project.json new file mode 100644 index 0000000..2a81bdb --- /dev/null +++ b/apps/sim-system-e2e/project.json @@ -0,0 +1,9 @@ +{ + "name": "sim-system-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "sourceRoot": "apps/sim-system-e2e/src", + "implicitDependencies": ["sim-system"], + "// targets": "to see all targets run: nx show project sim-system-e2e --web", + "targets": {} +} diff --git a/apps/sim-system-e2e/src/example.spec.ts b/apps/sim-system-e2e/src/example.spec.ts new file mode 100644 index 0000000..fa8f1f3 --- /dev/null +++ b/apps/sim-system-e2e/src/example.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('/'); + + // Expect h1 to contain a substring. + expect(await page.locator('h1').innerText()).toContain('Welcome'); +}); diff --git a/apps/sim-system-e2e/tsconfig.json b/apps/sim-system-e2e/tsconfig.json new file mode 100644 index 0000000..0b670c6 --- /dev/null +++ b/apps/sim-system-e2e/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "allowJs": true, + "outDir": "../../dist/out-tsc", + "sourceMap": false, + "module": "commonjs", + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "**/*.ts", + "**/*.js", + "playwright.config.ts", + "src/**/*.spec.ts", + "src/**/*.spec.js", + "src/**/*.test.ts", + "src/**/*.test.js", + "src/**/*.d.ts" + ] +} diff --git a/apps/sim-system/eslint.config.mjs b/apps/sim-system/eslint.config.mjs new file mode 100644 index 0000000..9b3aa71 --- /dev/null +++ b/apps/sim-system/eslint.config.mjs @@ -0,0 +1,34 @@ +import nx from '@nx/eslint-plugin'; +import baseConfig from '../../eslint.config.mjs'; + +export default [ + ...baseConfig, + ...nx.configs['flat/angular'], + ...nx.configs['flat/angular-template'], + { + files: ['**/*.ts'], + rules: { + '@angular-eslint/directive-selector': [ + 'error', + { + type: 'attribute', + prefix: 'app', + style: 'camelCase', + }, + ], + '@angular-eslint/component-selector': [ + 'error', + { + type: 'element', + prefix: 'app', + style: 'kebab-case', + }, + ], + }, + }, + { + files: ['**/*.html'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/apps/sim-system/jest.config.cts b/apps/sim-system/jest.config.cts new file mode 100644 index 0000000..899c544 --- /dev/null +++ b/apps/sim-system/jest.config.cts @@ -0,0 +1,21 @@ +module.exports = { + displayName: 'sim-system', + preset: '../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../coverage/apps/sim-system', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/apps/sim-system/project.json b/apps/sim-system/project.json new file mode 100644 index 0000000..2f6b651 --- /dev/null +++ b/apps/sim-system/project.json @@ -0,0 +1,84 @@ +{ + "name": "sim-system", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "prefix": "app", + "sourceRoot": "apps/sim-system/src", + "tags": [], + "targets": { + "build": { + "executor": "@angular/build:application", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/sim-system", + "browser": "apps/sim-system/src/main.ts", + "tsConfig": "apps/sim-system/tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "apps/sim-system/public" + } + ], + "styles": ["apps/sim-system/src/styles.scss"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kb", + "maximumError": "8kb" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "continuous": true, + "executor": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "sim-system:build:production" + }, + "development": { + "buildTarget": "sim-system:build:development" + } + }, + "defaultConfiguration": "development" + }, + "lint": { + "executor": "@nx/eslint:lint" + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/sim-system/jest.config.cts", + "tsConfig": "apps/sim-system/tsconfig.spec.json" + } + }, + "serve-static": { + "continuous": true, + "executor": "@nx/web:file-server", + "options": { + "buildTarget": "sim-system:build", + "port": 4200, + "staticFilePath": "dist/apps/sim-system/browser", + "spa": true + } + } + } +} diff --git a/apps/sim-system/public/favicon.ico b/apps/sim-system/public/favicon.ico new file mode 100644 index 0000000..317ebcb Binary files /dev/null and b/apps/sim-system/public/favicon.ico differ diff --git a/apps/sim-system/src/app/app.config.ts b/apps/sim-system/src/app/app.config.ts new file mode 100644 index 0000000..101c517 --- /dev/null +++ b/apps/sim-system/src/app/app.config.ts @@ -0,0 +1,13 @@ +import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { appRoutes } from './app.routes'; +import { apiInterceptor } from './helpers/jwt.interceptor'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideRouter(appRoutes), + provideHttpClient(withInterceptors([apiInterceptor])), + ], +}; diff --git a/apps/sim-system/src/app/app.html b/apps/sim-system/src/app/app.html new file mode 100644 index 0000000..5d7964b --- /dev/null +++ b/apps/sim-system/src/app/app.html @@ -0,0 +1,38 @@ + + + + + + + +
+
+ +
+
+ + + diff --git a/apps/sim-system/src/app/app.routes.ts b/apps/sim-system/src/app/app.routes.ts new file mode 100644 index 0000000..1e0402f --- /dev/null +++ b/apps/sim-system/src/app/app.routes.ts @@ -0,0 +1,63 @@ +import { Route } from '@angular/router'; + +export const appRoutes: Route[] = [ + { + path: 'dashboard', + loadComponent: () => import('./views/dashboard/dashboard.component').then(m => m.DashboardComponent) + }, + { + path: 'articles', + loadComponent: () => import('./views/articles/articles.component').then(m => m.ArticlesComponent) + }, + { + path: 'articles/:id', + loadComponent: () => import('./views/article-detail/article-detail.component').then(m => m.ArticleDetailComponent) + }, + { + path: 'customers', + loadComponent: () => import('./views/customers/customers.component').then(m => m.CustomersComponent) + }, + { + // Muss vor customers/:id stehen, damit 'new' nicht als ID gematcht wird + path: 'customers/new', + loadComponent: () => import('./views/customer-edit/customer-edit.component').then(m => m.CustomerEditComponent) + }, + { + path: 'customers/:id', + loadComponent: () => import('./views/customer-detail/customer-detail.component').then(m => m.CustomerDetailComponent) + }, + { + path: 'customers/:id/edit', + loadComponent: () => import('./views/customer-edit/customer-edit.component').then(m => m.CustomerEditComponent) + }, + { + path: 'slipsheets', + loadComponent: () => import('./views/slipsheets/slipsheets.component').then(m => m.SlipsheetsComponent) + }, + { + path: 'slipsheets/:id', + loadComponent: () => import('./views/slipsheet-detail/slipsheet-detail.component').then(m => m.SlipsheetDetailComponent) + }, + { + path: 'bills', + loadComponent: () => import('./views/bills/bills.component').then(m => m.BillsComponent) + }, + { + path: 'bills/:id', + loadComponent: () => import('./views/bill-detail/bill-detail.component').then(m => m.BillDetailComponent) + }, + { + path: 'inventory', + loadComponent: () => import('./views/inventory/inventory.component').then(m => m.InventoryComponent) + }, + { + path: 'order/new', + loadComponent: () => import('./views/order-new/order-new.component').then(m => m.OrderNewComponent) + }, + { + path: 'settings', + loadComponent: () => import('./views/settings/settings.component').then(m => m.SettingsComponent) + }, + { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, + { path: '**', redirectTo: 'dashboard' } +]; diff --git a/apps/sim-system/src/app/app.scss b/apps/sim-system/src/app/app.scss new file mode 100644 index 0000000..0f900f7 --- /dev/null +++ b/apps/sim-system/src/app/app.scss @@ -0,0 +1 @@ +// App-Shell: keine eigenen Styles nötig – alles in styles.scss definiert diff --git a/apps/sim-system/src/app/app.spec.ts b/apps/sim-system/src/app/app.spec.ts new file mode 100644 index 0000000..9caf72e --- /dev/null +++ b/apps/sim-system/src/app/app.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { App } from './app'; + +describe('App', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [App, RouterTestingModule], + }).compileComponents(); + }); + + it('should create the app', async () => { + const fixture = TestBed.createComponent(App); + expect(fixture.componentInstance).toBeTruthy(); + }); +}); diff --git a/apps/sim-system/src/app/app.ts b/apps/sim-system/src/app/app.ts new file mode 100644 index 0000000..7b8b0ce --- /dev/null +++ b/apps/sim-system/src/app/app.ts @@ -0,0 +1,95 @@ +import { Component, inject } from '@angular/core'; +import { Router, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +// Heroicons outline — stroke="currentColor" so they inherit active/hover color +const ICONS: Record = { + dashboard: ` + + + + + `, + articles: ` + + + + `, + customers: ` + + + `, + slipsheets: ` + + + + + `, + bills: ` + + + `, + order: ` + + + + `, + inventory: ` + + + + + `, + settings: ` + + + `, +}; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './app.html', + styleUrl: './app.scss', +}) +export class App { + private sanitizer = inject(DomSanitizer); + private router = inject(Router); + + readonly navItems = [ + { label: 'Start', children: [ + { label: 'Dashboard', route: '/dashboard', icon: 'dashboard' }, + ]}, + { label: 'Stammdaten', children: [ + { label: 'Artikel', route: '/articles', icon: 'articles' }, + { label: 'Kunden', route: '/customers', icon: 'customers' }, + ]}, + { label: 'Belege', children: [ + { label: 'Lieferscheine', route: '/slipsheets', icon: 'slipsheets' }, + { label: 'Rechnungen', route: '/bills', icon: 'bills' }, + { label: 'Neue Bestellung', route: '/order/new', icon: 'order' }, + ]}, + { label: 'Werkzeuge', children: [ + { label: 'Inventur', route: '/inventory', icon: 'inventory' }, + { label: 'Einstellungen', route: '/settings', icon: 'settings' }, + ]}, + ]; + + readonly bottomNavItems = [ + { label: 'Start', route: '/dashboard', icon: 'dashboard' }, + { label: 'Artikel', route: '/articles', icon: 'articles' }, + { label: 'Kunden', route: '/customers', icon: 'customers' }, + { label: 'Belege', route: '/slipsheets', icon: 'slipsheets' }, + { label: 'Inventur', route: '/inventory', icon: 'inventory' }, + ]; + + icon(name: string): SafeHtml { + return this.sanitizer.bypassSecurityTrustHtml(ICONS[name] ?? ''); + } + + isActive(route: string): boolean { + return this.router.url.startsWith(route); + } +} diff --git a/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.html b/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.html new file mode 100644 index 0000000..5d56f83 --- /dev/null +++ b/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.html @@ -0,0 +1,266 @@ + +@if (isBilled()) { +
+ +
+ Bereits verrechnet. + Änderungen setzen den Lieferschein auf + Bearbeitet — + die zugehörige Rechnung muss anschließend neu erstellt werden. +
+
+} + +@if (error()) { +
{{ error() }}
+} + + +
+ + +
+ + +
+ + @if (pendingArticle()) { +
+
+
{{ pendingArticle()!.name }}
+ +
+
{{ fmt(pendingArticle()!.price) }} €
+
+ +
+
+ +
+ + @if (pendingArticle()!.unit) { + {{ pendingArticle()!.unit }} + } +
+
+ +
+ } +
+ + +@if (showTextInput()) { +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+} + + +@if (slipsheet()?.orderEntries?.length) { +
+ +
+ + + + + + + + + + + + @for (order of slipsheet()!.orderEntries; track order.id) { + @if (editingOrder()?.id === order.id) { + + + + + + + + + } @else { + + + + + + + + } + } + +
Artikel / TextMengePreis/Stk.Gesamt
{{ order.article?.name || order.text }} + + {{ fmt(order.price) }} €{{ fmt(editAmount() * order.price) }} € + + +
+ {{ order.article?.name || order.text || 'Textposition' }} + @if (!order.article) { + TEXT + } + {{ order.amount }} {{ order.article?.unit || '' }}{{ fmt(order.price) }} €{{ fmt(order.getPrice()) }} € + + +
+
+ + +
+ Gesamt (netto) + {{ fmt(total()) }} € +
+
+} + + +@if (slipsheet()?.annotations?.length) { +
+ + @for (ann of slipsheet()!.annotations; track ann.id) { +
{{ ann.text }}
+ } +
+} + +@if (showAnnotationInput()) { +
+ +
+ + + +
+
+} + + +
+
+ + @if (slipsheet()) { + + } +
+
+ @if (canDelete()) { + + } + @if (slipsheet()) { + + } +
+
diff --git a/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.scss b/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.scss new file mode 100644 index 0000000..f139844 --- /dev/null +++ b/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.scss @@ -0,0 +1,318 @@ +.mb-2 { margin-bottom: 0.5rem; } + +/* ── Verrechnet-Warnung ───────────────────────────────────────── */ +.changed-banner { + display: flex; + align-items: flex-start; + gap: 0.75rem; + background: var(--status-empty-bg, #fef2f2); + border: 1px solid var(--status-empty, #ef4444); + border-left-width: 3px; + border-radius: var(--radius-md, 6px); + padding: 0.85rem 1rem; + font-size: 0.875rem; + color: #b91c1c; + margin-bottom: 1rem; + animation: fadeUp 0.2s ease forwards; +} + +.changed-banner-icon { + font-size: 1rem; + flex-shrink: 0; + margin-top: 0.05rem; +} + +.changed-state-label { + font-family: var(--mono); + font-size: 0.75rem; + letter-spacing: 0.08em; + background: #fecaca; + color: #b91c1c; + border-radius: var(--radius-pill, 999px); + padding: 0.1rem 0.5rem; +} + +/* ── Fehler ───────────────────────────────────────────────────── */ +.editor-error { + background: var(--status-empty-bg, #fef2f2); + color: #b91c1c; + border-left: 3px solid var(--status-empty, #ef4444); + border-radius: var(--radius-md, 6px); + padding: 0.65rem 1rem; + font-size: 0.875rem; + margin-bottom: 1rem; +} + +/* ── Editor Sections ─────────────────────────────────────────── */ +.editor-section { + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border); + + &:last-of-type { border-bottom: none; } +} + +.editor-section-table { padding: 0; } + +.editor-section-label { + font-family: var(--mono); + font-size: 0.62rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--ink-3); + margin-bottom: 0.75rem; +} + +.editor-section-table .editor-section-label { + padding: 0.85rem 1.25rem 0; +} + +/* ── Field Group (label + control) ──────────────────────────── */ +.field-group { + display: flex; + flex-direction: column; + gap: 0.35rem; +} + +.field-group-grow { flex: 1 1 auto; min-width: 0; } + +.field-label { + font-family: var(--mono); + font-size: 0.62rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--ink-3); +} + +/* ── Artikel Card ────────────────────────────────────────────── */ +.article-card { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.75rem; + background: var(--accent-light); + border: 1px solid var(--accent-highlight); + border-radius: var(--radius-md); + padding: 0.75rem 1rem; + margin-top: 0.75rem; +} + +.article-card-info { flex: 1; min-width: 0; } + +.article-name { + font-size: 0.9375rem; + font-weight: 500; + color: var(--ink); +} + +.article-meta { + display: flex; + align-items: center; + gap: 0.4rem; + flex-wrap: wrap; + margin-top: 0.25rem; + font-size: 0.72rem; + color: var(--ink-3); +} + +.article-price { + font-size: 0.9rem; + color: var(--ink-2); + white-space: nowrap; +} + +/* ── Add Row ─────────────────────────────────────────────────── */ +.add-row { + display: flex; + align-items: flex-end; + gap: 0.75rem; + margin-top: 0.75rem; + flex-wrap: wrap; +} + +.add-btn { margin-bottom: 0; align-self: flex-end; } + +/* ── Amount input with unit suffix ───────────────────────────── */ +.amount-input-wrap { + display: flex; + align-items: center; + gap: 0.4rem; +} + +.amount-unit { + font-size: 0.75rem; + color: var(--ink-3); + white-space: nowrap; +} + +/* ── Text Position Layout ────────────────────────────────────── */ +.text-pos-fields { + display: flex; + align-items: flex-end; + gap: 0.75rem; + flex-wrap: wrap; + margin-bottom: 0.75rem; +} + +.text-pos-actions { + display: flex; + gap: 0.5rem; + align-items: center; +} + +/* ── Positions Table ─────────────────────────────────────────── */ +.table-scroll { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + + table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; + + thead tr { border-bottom: 2px solid var(--border); } + + th { + font-family: var(--mono); + font-size: 0.6rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--ink-3); + padding: 0.55rem 0.75rem; + text-align: left; + white-space: nowrap; + } + + td { + padding: 0.6rem 0.75rem; + border-bottom: 1px solid var(--border-sub); + color: var(--ink-2); + vertical-align: middle; + + &:last-child { border-bottom-color: transparent; } + } + + tbody tr { + &:last-child td { border-bottom: none; } + } + + .numeric { text-align: right; } + .mono { font-family: var(--mono); } + } +} + +.col-entry-actions { + width: 5.5rem; + text-align: right; + padding-right: 0.75rem !important; +} + +/* Inline-Edit row */ +.row-editing { + background: var(--accent-light); + + td { border-bottom-color: var(--accent-highlight); } +} + +/* ── Icon Buttons (edit / delete) ────────────────────────────── */ +.btn-icon { + background: none; + border: none; + cursor: pointer; + padding: 0.2rem 0.35rem; + border-radius: var(--radius-sm); + font-size: 0.8rem; + color: var(--ink-3); + transition: color 0.12s, background 0.12s; + + &:hover { + color: var(--accent-primary); + background: var(--accent-light); + } +} + +.btn-icon-del:hover { + color: var(--status-empty); + background: var(--status-empty-bg); +} + +/* ── Total ───────────────────────────────────────────────────── */ +.entry-total { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1.25rem; + border-top: 2px solid var(--border); + background: var(--bg-subtle); +} + +.entry-total-label { + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--ink-3); +} + +.entry-total-val { + font-size: 1.25rem; + color: var(--ink); +} + +/* ── Annotations ─────────────────────────────────────────────── */ +.annotation-row { + padding: 0.4rem 0; + border-bottom: 1px solid var(--border-sub); + font-size: 0.875rem; + color: var(--ink-2); + + &:last-child { border-bottom: none; } +} + +/* ── Action Bar ──────────────────────────────────────────────── */ +.editor-action-bar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; + padding: 1rem 1.25rem; + border-top: 1px solid var(--border); + background: var(--bg-subtle); +} + +.action-bar-left { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.action-bar-right { + display: flex; + gap: 0.5rem; + align-items: center; + flex-wrap: wrap; +} + +.btn-delete-slip { + font-family: var(--mono); + font-size: 0.7rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--status-empty, #dc2626); + background: transparent; + border: 1px solid var(--status-empty-border, #fca5a5); + border-radius: var(--radius-pill); + padding: 0.4rem 0.9rem; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; + + &:hover { + background: var(--status-empty-bg, #fef2f2); + border-color: var(--status-empty, #dc2626); + } + + &:disabled { + opacity: 0.45; + cursor: not-allowed; + } +} diff --git a/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.ts b/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.ts new file mode 100644 index 0000000..709d081 --- /dev/null +++ b/apps/sim-system/src/app/components/slipsheet-editor/slipsheet-editor.component.ts @@ -0,0 +1,234 @@ +import { + ChangeDetectionStrategy, Component, ElementRef, ViewChild, + inject, signal, computed, input, output, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormControl } from '@angular/forms'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { debounceTime, switchMap, catchError, of, filter } from 'rxjs'; +import { ArticleService } from '../../services/article.service'; +import { SlipsheetService } from '../../services/slipsheet.service'; +import { Article } from '../../models/article.model'; +import { Customer } from '../../models/customer.model'; +import { Slipsheet, Order } from '../../models/bill.model'; + +@Component({ + selector: 'app-slipsheet-editor', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './slipsheet-editor.component.html', + styleUrl: './slipsheet-editor.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SlipsheetEditorComponent { + @ViewChild('codeInput') codeInputRef!: ElementRef; + + private articleService = inject(ArticleService); + private slipsheetService = inject(SlipsheetService); + + // ── Inputs ──────────────────────────────────────────────────── + readonly slipsheet = input.required(); + readonly customer = input.required(); + + // ── Output ──────────────────────────────────────────────────── + /** Emitted after every successful mutation with the updated Slipsheet */ + readonly updated = output(); + /** Emitted when the slipsheet was deleted */ + readonly deleted = output(); + + // ── UI State ────────────────────────────────────────────────── + readonly codeCtrl = new FormControl(''); + readonly pendingAmount = signal(1); + readonly submitting = signal(false); + readonly error = signal(''); + readonly editingOrder = signal(null); + readonly editAmount = signal(1); + readonly showTextInput = signal(false); + readonly textPositionText = signal(''); + readonly textPositionAmount = signal(1); + readonly textPositionPrice = signal(0); + readonly showAnnotationInput = signal(false); + readonly deleting = signal(false); + readonly annotationText = signal(''); + + // ── Computed ────────────────────────────────────────────────── + /** True when the slipsheet is already linked to a bill */ + readonly isBilled = computed(() => { + const s = this.slipsheet(); + return s != null && !s.isOpen(); + }); + + /** True: offener LS ohne Positionen → Löschen erlaubt */ + readonly canDelete = computed(() => { + const s = this.slipsheet(); + return s != null && s.isOpen() && (s.orderEntries?.length ?? 0) === 0; + }); + + readonly total = computed(() => + (this.slipsheet()?.orderEntries ?? []).reduce((s, o) => s + o.getPrice(), 0) + ); + + readonly pendingArticle = toSignal( + this.codeCtrl.valueChanges.pipe( + debounceTime(300), + filter(v => !!v && v.length > 1), + switchMap(code => + this.articleService.getByCode(code!).pipe(catchError(() => of(null))) + ) + ), + { initialValue: null as Article | null } + ); + + // ── Artikel hinzufügen ──────────────────────────────────────── + addPending() { + const article = this.pendingArticle(); + const customer = this.customer(); + const slip = this.slipsheet(); + if (!article || !customer) return; + + this.submitting.set(true); + this.error.set(''); + + const op$ = slip + ? this.slipsheetService.addOrder(slip, new Order({ article, amount: this.pendingAmount(), price: article.price })) + : this.slipsheetService.getOrCreate({ article, amount: this.pendingAmount(), customer }); + + op$.subscribe({ + next: updated => { + this.updated.emit(updated); + this.submitting.set(false); + this.pendingAmount.set(1); + this.codeCtrl.setValue('', { emitEvent: false }); + setTimeout(() => this.codeInputRef?.nativeElement?.focus(), 50); + }, + error: (err: { message?: string }) => { + this.error.set(err?.message || 'Fehler beim Hinzufügen'); + this.submitting.set(false); + }, + }); + } + + + // ── Textposition ────────────────────────────────────────────── + addTextPosition() { + const text = this.textPositionText().trim(); + const slip = this.slipsheet(); + const customer = this.customer(); + if (!text || !customer) return; + + this.submitting.set(true); + const order = new Order({ text, amount: this.textPositionAmount(), price: this.textPositionPrice() }); + + const op$ = slip + ? this.slipsheetService.addOrder(slip, order) + : this.slipsheetService.getOrCreateWithText(order, customer); + + op$.subscribe({ + next: updated => { + this.updated.emit(updated); + this.submitting.set(false); + this.closeTextInput(); + }, + error: (err: { message?: string }) => { this.error.set(err?.message || 'Fehler'); this.submitting.set(false); }, + }); + } + + closeTextInput() { + this.showTextInput.set(false); + this.textPositionText.set(''); + this.textPositionAmount.set(1); + this.textPositionPrice.set(0); + } + + + // ── Positionen bearbeiten ───────────────────────────────────── + startEdit(order: Order) { + this.editingOrder.set(order); + this.editAmount.set(order.amount); + } + + cancelEdit() { this.editingOrder.set(null); } + + saveEdit() { + const slip = this.slipsheet(); + const order = this.editingOrder(); + if (!slip || !order) return; + this.submitting.set(true); + this.slipsheetService.updateOrder(slip, new Order({ ...order, amount: this.editAmount() })).subscribe({ + next: updated => { + this.updated.emit(updated); + this.submitting.set(false); + this.editingOrder.set(null); + }, + error: (err: { message?: string }) => { this.error.set(err?.message || 'Fehler'); this.submitting.set(false); }, + }); + } + + deleteOrder(order: Order) { + const slip = this.slipsheet(); + if (!slip) return; + this.slipsheetService.updateOrder(slip, new Order({ ...order, amount: 0 })).subscribe({ + next: updated => this.updated.emit(updated), + error: (err: { message?: string }) => this.error.set(err?.message || 'Fehler beim Löschen'), + }); + } + + // ── Annotation ──────────────────────────────────────────────── + submitAnnotation() { + const slip = this.slipsheet(); + const text = this.annotationText().trim(); + if (!slip || !text) return; + this.slipsheetService.addAnnotation(slip, text).subscribe({ + next: updated => { + this.updated.emit(updated); + this.annotationText.set(''); + this.showAnnotationInput.set(false); + }, + error: (err: { message?: string }) => this.error.set(err?.message || 'Fehler'), + }); + } + + // ── PDF ─────────────────────────────────────────────────────── + downloadPdf() { + const slip = this.slipsheet(); + if (!slip) return; + this.slipsheetService.getPdf(slip.id).subscribe({ + next: blob => { + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `lieferschein-${slip.slipsheetnumber || slip.id}.pdf`; + a.click(); + URL.revokeObjectURL(url); + + this.slipsheetService.getById(slip.id).subscribe({ + next: updated => this.updated.emit(updated), + error: () => this.error.set('Lieferschein konnte nach PDF-Erzeugung nicht aktualisiert werden.'), + }); + }, + error: () => this.error.set('PDF konnte nicht geladen werden.'), + }); + } + + // ── Löschen ─────────────────────────────────────────────────── + deleteSlipsheet() { + const slip = this.slipsheet(); + if (!slip || !this.canDelete()) return; + this.deleting.set(true); + this.slipsheetService.delete(slip.id).subscribe({ + next: () => { this.deleting.set(false); this.deleted.emit(); }, + error: (err: { message?: string }) => { + this.error.set(err?.message || 'Fehler beim Löschen'); + this.deleting.set(false); + }, + }); + } + + // ── Template helpers (no arrow functions in bindings) ───────── + toggleTextInput() { this.showTextInput.update(v => !v); } + toggleAnnotationInput() { this.showAnnotationInput.update(v => !v); } + + fmt(value: number): string { + return new Intl.NumberFormat('de-AT', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value); + } +} diff --git a/apps/sim-system/src/app/helpers/auth.guard.ts b/apps/sim-system/src/app/helpers/auth.guard.ts new file mode 100644 index 0000000..9c406ca --- /dev/null +++ b/apps/sim-system/src/app/helpers/auth.guard.ts @@ -0,0 +1,14 @@ +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { AuthenticationService } from '../services/authentication.service'; + +export const authGuard: CanActivateFn = (route, state) => { + const authService = inject(AuthenticationService); + const router = inject(Router); + + if (authService.currentUserValue != null) { + return true; + } + router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); + return false; +}; diff --git a/apps/sim-system/src/app/helpers/jwt.interceptor.ts b/apps/sim-system/src/app/helpers/jwt.interceptor.ts new file mode 100644 index 0000000..7cdb44d --- /dev/null +++ b/apps/sim-system/src/app/helpers/jwt.interceptor.ts @@ -0,0 +1,7 @@ +import { HttpInterceptorFn } from '@angular/common/http'; +import { environment } from '../../environments/environment'; + +export const apiInterceptor: HttpInterceptorFn = (req, next) => { + if (req.url.startsWith('http')) return next(req); + return next(req.clone({ url: `${environment.apiUrl}/${req.url}` })); +}; diff --git a/apps/sim-system/src/app/models/article.model.ts b/apps/sim-system/src/app/models/article.model.ts new file mode 100644 index 0000000..174bedd --- /dev/null +++ b/apps/sim-system/src/app/models/article.model.ts @@ -0,0 +1,43 @@ +export class ArticleGroup { + id!: number; + name: string; + constructor(init?: Partial) { + this.name = ''; + Object.assign(this, init); + } +} + +export class Article { + id!: number; + name: string; + description: string; + type: string; + artNumber: string; + code: string; + supplier: string; + imgPath!: string; + price!: number; + stock!: number; + unit: string; + inventoryDate!: string; + inventoryStock!: number; + singlePos: boolean; + trackStock: boolean; + noDiscount: boolean; + articleGroup: ArticleGroup; + + constructor(init?: Partial
) { + this.name = ''; + this.code = ''; + this.description = ''; + this.artNumber = ''; + this.type = ''; + this.supplier = ''; + this.unit = ''; + this.singlePos = false; + this.trackStock = false; + this.noDiscount = false; + Object.assign(this, init); + this.articleGroup = new ArticleGroup(init?.articleGroup); + } +} diff --git a/apps/sim-system/src/app/models/bill.model.ts b/apps/sim-system/src/app/models/bill.model.ts new file mode 100644 index 0000000..d986bf3 --- /dev/null +++ b/apps/sim-system/src/app/models/bill.model.ts @@ -0,0 +1,110 @@ +export class Order { + id!: number; + amount: number; + price: number; + article: any; + text!: string; + articleGroupRabatt: number; + customerRabatt: number; + + constructor(init?: Partial) { + this.amount = 0; + this.price = 0; + this.articleGroupRabatt = 0; + this.customerRabatt = 0; + Object.assign(this, init); + } + + getPrice(): number { + return (this.amount || 0) * (this.price || 0); + } +} + +export class Slipsheet { + id!: number; + slipsheetnumber!: string; + state: string; + billId?: number | null; + bill?: { id?: number | null } | null; + createdAt!: Date; + updatedAt!: Date; + customer: any; + orderEntries: Order[]; + annotations!: any[]; + printDate: any; + + constructor(init?: Partial) { + this.state = 'open'; + Object.assign(this, init); + this.bill = init?.bill ?? null; + this.billId = init?.billId ?? init?.bill?.id ?? null; + this.orderEntries = (init?.orderEntries || []).map(x => new Order(x)); + } + + hasBill(): boolean { + return !!(this.bill?.id ?? this.billId); + } + + isCompleted(): boolean { + return this.state === 'closed' && this.hasBill(); + } + + isOpen(): boolean { + return !this.isCompleted(); + } + + getStateLabel(): string { + switch (this.state) { + case 'open': return 'offen'; + case 'closed': return 'fertig'; + case 'payed': return 'bezahlt'; + case 'changed': return 'bearbeitet'; + case 'edit': return 'bearbeitet'; + case 'deliveryPrint': return 'Lieferschein'; + default: return this.state; + } + } + + getPrice(): number { + return (this.orderEntries || []).reduce((sum, o) => sum + o.getPrice(), 0); + } +} + +export class Bill { + id!: number; + billNumber: string; + billDate: string; + state: string; + path!: string; + billPath!: string; + createdAt!: Date; + updatedAt!: Date; + paymentAt!: Date; + slipsheets: Slipsheet[]; + customer: any; + + constructor(init?: Partial) { + this.billNumber = ''; + this.billDate = ''; + this.state = 'open'; + Object.assign(this, init); + this.slipsheets = (init?.slipsheets || []).map(x => new Slipsheet(x)); + } + + getNumber(): string { + return this.billNumber || `RE-${this.id}`; + } + + getTotal(): number { + return (this.slipsheets || []).reduce((sum, s) => sum + s.getPrice(), 0); + } +} + +export class User { + id!: number; + username!: string; + firstName!: string; + lastName!: string; + token!: string; + constructor(init?: Partial) { Object.assign(this, init); } +} diff --git a/apps/sim-system/src/app/models/customer.model.ts b/apps/sim-system/src/app/models/customer.model.ts new file mode 100644 index 0000000..76f272f --- /dev/null +++ b/apps/sim-system/src/app/models/customer.model.ts @@ -0,0 +1,51 @@ +export class Discount { + id!: number; + value!: number; + articleGroup!: { id: number; name: string }; + constructor(init?: Partial) { Object.assign(this, init); } +} + +export class Customer { + id!: number; + email: string; + customerNumber: string; + firstName: string; + lastName: string; + companyName!: string; + phoneCompany: string; + phonePrivate!: string; + customerDiscount: number; + address: string; + postcode: string; + country: string; + uid: string; + discounts: Discount[]; + createdAt!: Date; + + constructor(init?: Partial) { + this.email = ''; + this.customerNumber = ''; + this.firstName = ''; + this.lastName = ''; + this.phoneCompany = ''; + this.address = ''; + this.postcode = ''; + this.country = ''; + this.customerDiscount = 0; + this.uid = ''; + Object.assign(this, init); + this.discounts = (init?.discounts || []).map(x => new Discount(x)); + } + + getName(): string { + if (!this.companyName || this.companyName === '') { + return `${this.lastName} ${this.firstName}`.trim(); + } + return this.companyName; + } + + getInitials(): string { + const name = this.getName(); + return name.split(' ').slice(0, 2).map(w => w[0] || '').join('').toUpperCase(); + } +} diff --git a/apps/sim-system/src/app/models/settings.model.ts b/apps/sim-system/src/app/models/settings.model.ts new file mode 100644 index 0000000..5f241fc --- /dev/null +++ b/apps/sim-system/src/app/models/settings.model.ts @@ -0,0 +1,36 @@ +export interface BankAccount { + name: string; + iban: string; + bic: string; +} + +export type LetterheadMode = 'generated' | 'disabled' | 'template_pdf'; + +export class CompanySettings { + id?: number; + companyName: string = ''; + street: string = ''; + zip: string = ''; + city: string = ''; + country: string = 'Oesterreich'; + phone: string = ''; + email: string = ''; + website: string = ''; + firmenbuchnummer: string = ''; + vatId: string = ''; + issueCity: string = ''; + vatRate: number = 20; + paymentTermDays: number = 14; + paymentFooterText: string = ''; + bankAccounts: BankAccount[] = []; + logoPath: string | null = null; + badge1Path: string | null = null; + badge2Path: string | null = null; + letterheadMode: LetterheadMode = 'generated'; + templatePdfPath: string | null = null; + updatedAt?: Date; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/apps/sim-system/src/app/nx-welcome.ts b/apps/sim-system/src/app/nx-welcome.ts new file mode 100644 index 0000000..4437558 --- /dev/null +++ b/apps/sim-system/src/app/nx-welcome.ts @@ -0,0 +1,953 @@ +import { Component, ViewEncapsulation } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-nx-welcome', + imports: [CommonModule], + template: ` + + + + +
+
+ +
+

+ Hello there, + Welcome sim-system 👋 +

+
+ +
+
+

+ + + + You're up and running +

+ What's next? +
+
+ + + +
+
+ + + +
+

Next steps

+

Here are some things you can do with Nx:

+
+ + + + + Build, test and lint your app + +
# Build
+nx build 
+# Test
+nx test 
+# Lint
+nx lint 
+# Run them together!
+nx run-many -t build test lint
+
+
+ + + + + View project details + +
nx show project sim-system
+
+ +
+ + + + + View interactive project graph + +
nx graph
+
+ +
+ + + + + Add UI library + +
# Generate UI lib
+nx g @nx/angular:lib ui
+# Add a component
+nx g @nx/angular:component ui/src/lib/button
+
+
+

+ Carefully crafted with + + + +

+
+
+ `, + styles: [], + encapsulation: ViewEncapsulation.None, +}) +export class NxWelcome {} diff --git a/apps/sim-system/src/app/services/article-group.service.ts b/apps/sim-system/src/app/services/article-group.service.ts new file mode 100644 index 0000000..2a38be5 --- /dev/null +++ b/apps/sim-system/src/app/services/article-group.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ArticleGroup } from '../models/article.model'; + +@Injectable({ providedIn: 'root' }) +export class ArticleGroupService { + constructor(private http: HttpClient) {} + + getAll(): Observable { + return this.http.get('articlegroups').pipe( + map(o => o.success ? o.data.map((a: any) => new ArticleGroup(a)) : []) + ); + } +} diff --git a/apps/sim-system/src/app/services/article.service.ts b/apps/sim-system/src/app/services/article.service.ts new file mode 100644 index 0000000..e763bd5 --- /dev/null +++ b/apps/sim-system/src/app/services/article.service.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Article } from '../models/article.model'; + +@Injectable({ providedIn: 'root' }) +export class ArticleService { + constructor(private http: HttpClient) {} + + getAll(): Observable { + return this.http.get('articles').pipe( + map(o => o.success ? o.data.map((a: any) => new Article(a)) : []) + ); + } + + getByCode(code: string): Observable
{ + const params = new HttpParams().set('code', code); + return this.http.get('articles', { params }).pipe( + map(o => { + if (o.success && o.data?.[0]) return new Article(o.data[0]); + throw new Error(o.message || 'Artikel nicht gefunden'); + }) + ); + } + + getById(id: number): Observable { + const params = new HttpParams().set('id', String(id)); + return this.http.get('articles', { params }).pipe( + map(o => o.success ? o.data : throwError(() => new Error(o.message))) + ); + } + + create(article: Partial
): Observable
{ + return this.http.post('articles', article).pipe( + map(o => { + if (o.success) return o.data; + throw new Error(o.message || 'Fehler beim Anlegen'); + }) + ); + } + + update(article: Article): Observable
{ + return this.http.patch(`articles/${article.id}`, article).pipe( + map(o => { + if (o.success) return o.data; + throw new Error(o.message || 'Fehler beim Speichern'); + }) + ); + } + + delete(article: Article): Observable { + return this.http.delete(`articles/${article.id}`).pipe( + map(o => { + if (o.success) return true; + throw new Error(o.message || 'Fehler beim Löschen'); + }) + ); + } + + createInventory(article: Article, newStock: number): Observable
{ + return this.http.post(`articles/${article.id}/inventory`, { newStock }).pipe( + map(o => { + if (o.success) return new Article(o.data); + throw new Error(o.message || 'Fehler bei der Inventur'); + }) + ); + } + + importArticle(preview: boolean, formData: FormData): Observable { + let params = new HttpParams(); + if (preview) params = params.set('preview', 'true'); + return this.http.post('articles/import', formData, { params }); + } +} diff --git a/apps/sim-system/src/app/services/authentication.service.ts b/apps/sim-system/src/app/services/authentication.service.ts new file mode 100644 index 0000000..bc86710 --- /dev/null +++ b/apps/sim-system/src/app/services/authentication.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { User } from '../models/bill.model'; + +@Injectable({ providedIn: 'root' }) +export class AuthenticationService { + private currentUserSubject: BehaviorSubject; + public currentUser: Observable; + + constructor(private http: HttpClient) { + const stored = localStorage.getItem('currentUser'); + this.currentUserSubject = new BehaviorSubject(stored ? JSON.parse(stored) : null); + this.currentUser = this.currentUserSubject.asObservable(); + } + + get currentUserValue(): User | null { + return this.currentUserSubject.value; + } + + login(login: string, password: string) { + return this.http.post('users/login', { login, password }).pipe( + map(res => { + if (res.success === true) { + localStorage.setItem('currentUser', JSON.stringify(res.user)); + this.currentUserSubject.next(res.user); + } + return res; + }) + ); + } + + logout() { + localStorage.removeItem('currentUser'); + this.currentUserSubject.next(null); + } + + refresh() { + return this.http.get('users/me/refresh').pipe( + map(res => { + if (res.success === true) { + localStorage.setItem('currentUser', JSON.stringify(res.user)); + this.currentUserSubject.next(res.user); + } + return res; + }) + ); + } +} diff --git a/apps/sim-system/src/app/services/bill.service.ts b/apps/sim-system/src/app/services/bill.service.ts new file mode 100644 index 0000000..4b14693 --- /dev/null +++ b/apps/sim-system/src/app/services/bill.service.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Bill, Slipsheet } from '../models/bill.model'; + +@Injectable({ providedIn: 'root' }) +export class BillService { + constructor(private http: HttpClient) {} + + getById(id: number): Observable { + return this.http.get(`bills/${id}`).pipe( + map(o => { + if (o.success) return new Bill(o.data); + throw new Error(o.message); + }) + ); + } + + getAll(): Observable { + return this.http.get('bills').pipe( + map(o => o.success ? o.data.map((b: any) => new Bill(b)) : []) + ); + } + + getByState(state: string): Observable { + const params = new HttpParams().set('state', state); + return this.http.get('bills', { params }).pipe( + map(o => o.success ? o.data.map((b: any) => new Bill(b)) : []) + ); + } + + generate(slipsheetIds: number[]): Observable { + return this.http.post('bills/generate', slipsheetIds).pipe( + map(o => { + if (o.success) return new Bill(o.data); + throw new Error(o.message); + }) + ); + } + + recreate(bill: Bill): Observable { + return this.http.post(`bills/${bill.id}`, bill).pipe( + map(o => { + if (o.success) return new Bill(o.data); + throw new Error(o.message); + }) + ); + } + + update(bill: Partial): Observable { + return this.http.put(`bills/${bill.id}`, bill).pipe( + map(o => { + if (o.success) return new Bill(o.data); + throw new Error(o.message); + }) + ); + } + + delete(id: number): Observable { + return this.http.delete(`bills/${id}`).pipe( + map(o => { + if (o.success) return; + throw new Error(o.message); + }) + ); + } + + // GET /bills/:id/pdf – read-only, does NOT regenerate + getPdf(id: number): Observable { + return this.http.get(`bills/${id}/pdf`, { responseType: 'blob' }); + } +} diff --git a/apps/sim-system/src/app/services/customer.service.ts b/apps/sim-system/src/app/services/customer.service.ts new file mode 100644 index 0000000..bf2637a --- /dev/null +++ b/apps/sim-system/src/app/services/customer.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Customer, Discount } from '../models/customer.model'; +import { Slipsheet, Bill } from '../models/bill.model'; + +@Injectable({ providedIn: 'root' }) +export class CustomerService { + private currentCustomerSubject = new BehaviorSubject(undefined); + + get currentCustomer(): Customer | undefined { + return this.currentCustomerSubject.value; + } + + constructor(private http: HttpClient) {} + + selectCustomer(customer: Customer) { + this.currentCustomerSubject.next(customer); + } + + getCustomer(): Observable { + return this.currentCustomerSubject.asObservable(); + } + + getAll(): Observable { + return this.http.get('customers').pipe( + map(o => o.success ? o.data.map((x: any) => new Customer(x)) : []) + ); + } + + getById(id: number): Observable { + return this.http.get(`customers/${id}`).pipe( + map(o => { + if (o.success) return new Customer(o.data); + throw new Error(o.message || 'Fehler beim Laden'); + }) + ); + } + + getSlipsheets(customer: Customer): Observable { + return this.http.get(`customers/${customer.id}/slipsheets`).pipe( + map(o => o.success ? o.data.map((s: any) => new Slipsheet(s)) : []) + ); + } + + getBills(customer: Customer): Observable { + return this.http.get(`customers/${customer.id}/bills`).pipe( + map(o => o.success ? o.data.map((b: any) => new Bill(b)) : []) + ); + } + + create(customer: Partial): Observable { + return this.http.post('customers/', customer); + } + + update(customer: Customer): Observable { + return this.http.patch(`customers/${customer.id}`, customer).pipe( + map(o => { + if (o.success) return o.data; + throw new Error(o.message || 'Fehler beim Speichern'); + }) + ); + } + + delete(customer: Customer): Observable { + return this.http.delete(`customers/${customer.id}`).pipe( + map(o => { + if (o.success) return true; + throw new Error(o.message || 'Fehler beim Löschen'); + }) + ); + } + + updateOrCreateDiscount(customer: Customer, discount: Discount): Observable { + return this.http.put(`customers/${customer.id}/discounts`, discount).pipe( + map(o => { + if (o.success) return o.data; + throw new Error(o.message || 'Fehler'); + }) + ); + } + + deleteDiscount(customer: Customer, discount: Discount): Observable { + return this.http.delete(`customers/${customer.id}/discounts/${discount.id}`); + } +} diff --git a/apps/sim-system/src/app/services/dashboard.service.ts b/apps/sim-system/src/app/services/dashboard.service.ts new file mode 100644 index 0000000..b3d89b0 --- /dev/null +++ b/apps/sim-system/src/app/services/dashboard.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export interface DashboardSummaryRequest { + days?: number; + threshold?: number; + articleGroupId?: number; + from?: string; + to?: string; +} + +export interface DashboardSummary { + generatedAt: string; + filters: { from: string; to: string; days: number; threshold: number; articleGroupId: number | null }; + kpis: { + totalArticles: number; trackedArticles: number; lowStockCount: number; + outOfStockCount: number; inventoryAdjustmentsTodayCount: number; + inventoryAdjustmentsTodayDelta: number; openSlipsheets: number; + changedSlipsheets: number; openBills: number; + }; + lowStockItems: Array<{ id: number; name: string; code: string; unit: string; stock: number; inventoryStock: number; articleGroupName: string | null }>; + recentInventoryActivities: Array<{ id: number; createdAt: string; amountNew: number; diff: number; article: { id: number; name: string; code: string; unit: string } | null }>; + documentQueue: { + openSlipsheets: Array<{ id: number; slipsheetnumber: string; state: string; createdAt: string; customerId: number | null; customerName: string }>; + changedSlipsheets: Array<{ id: number; slipsheetnumber: string; state: string; createdAt: string; customerId: number | null; customerName: string }>; + openBills: Array<{ id: number; billNumber: string; state: string; billDate: string; slipsheetCount: number; customerId: number | null; customerName: string }>; + }; + topMovingArticles7: Array<{ id: number; name: string; code: string; unit: string; totalAmount: number }>; + topMovingArticles30: Array<{ id: number; name: string; code: string; unit: string; totalAmount: number }>; + stockTrend: Array<{ day: string; label: string; outgoing: number; adjustmentDelta: number }>; +} + +@Injectable({ providedIn: 'root' }) +export class DashboardService { + constructor(private http: HttpClient) {} + + getSummary(query: DashboardSummaryRequest): Observable { + let params = new HttpParams(); + if (query?.days != null) params = params.set('days', String(query.days)); + if (query?.threshold != null) params = params.set('threshold', String(query.threshold)); + if (query?.articleGroupId != null) params = params.set('articleGroupId', String(query.articleGroupId)); + if (query?.from) params = params.set('from', query.from); + if (query?.to) params = params.set('to', query.to); + + return this.http.get('dashboard/summary', { params }).pipe( + map(res => { + if (res?.success) return res.data as DashboardSummary; + throw new Error(res?.message || 'Dashboard konnte nicht geladen werden'); + }) + ); + } +} diff --git a/apps/sim-system/src/app/services/settings.service.ts b/apps/sim-system/src/app/services/settings.service.ts new file mode 100644 index 0000000..6c00e0e --- /dev/null +++ b/apps/sim-system/src/app/services/settings.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { CompanySettings } from '../models/settings.model'; + +@Injectable({ providedIn: 'root' }) +export class SettingsService { + constructor(private http: HttpClient) {} + + get(): Observable { + return this.http.get('settings').pipe( + map(o => o.success ? new CompanySettings(o.data ?? {}) : new CompanySettings()), + ); + } + + save(settings: Partial): Observable { + return this.http.put('settings', settings).pipe( + map(o => { + if (o.success) return new CompanySettings(o.data); + throw new Error(o.message || 'Fehler beim Speichern'); + }), + ); + } + + uploadLogo(file: File): Observable { + return this._upload('settings/logo', file); + } + + uploadBadge1(file: File): Observable { + return this._upload('settings/badge1', file); + } + + uploadBadge2(file: File): Observable { + return this._upload('settings/badge2', file); + } + + uploadTemplatePdf(file: File): Observable { + return this._upload('settings/template-pdf', file); + } + + private _upload(endpoint: string, file: File): Observable { + const fd = new FormData(); + fd.append('file', file); + return this.http.post(endpoint, fd).pipe( + map(o => { + if (o.success) return new CompanySettings(o.data); + throw new Error(o.message || 'Upload fehlgeschlagen'); + }), + ); + } +} diff --git a/apps/sim-system/src/app/services/slipsheet.service.ts b/apps/sim-system/src/app/services/slipsheet.service.ts new file mode 100644 index 0000000..6c87570 --- /dev/null +++ b/apps/sim-system/src/app/services/slipsheet.service.ts @@ -0,0 +1,141 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { Slipsheet, Order } from '../models/bill.model'; + +@Injectable({ providedIn: 'root' }) +export class SlipsheetService { + private currentSlipsheetSubject = new BehaviorSubject(undefined); + + constructor(private http: HttpClient) {} + + getCurrentSlipsheet(): Observable { + return this.currentSlipsheetSubject.asObservable(); + } + + getAll(): Observable { + return this.http.get('slipsheets').pipe( + map(o => o.success ? o.data.map((s: any) => new Slipsheet(s)) : []) + ); + } + + getByState(state: string): Observable { + const params = new HttpParams().set('state', state); + return this.http.get('slipsheets', { params }).pipe( + map(o => o.success ? o.data.map((s: any) => new Slipsheet(s)) : []) + ); + } + + getByCustomer(customerId: number): Observable { + const params = new HttpParams().set('customerId', String(customerId)); + return this.http.get('slipsheets', { params }).pipe( + map(o => o.success ? o.data.map((s: any) => new Slipsheet(s)) : []) + ); + } + + getById(id: number): Observable { + return this.http.get(`slipsheets/${id}`).pipe( + map(o => { + if (o.success) return new Slipsheet(o.data); + throw new Error(o.message || 'Fehler beim Laden'); + }) + ); + } + + /** + * POST /slipsheets für Textpositionen ohne bestehenden Lieferschein. + * Das Backend-DTO verlangt price/customerRabatt/articleGroupRabatt wenn kein article + * vorhanden ist (@ValidateIf). getOrCreate sendet diese Felder nicht → 400. + */ + getOrCreateWithText(order: Order, customer: { id: number }): Observable { + return this.http.post('slipsheets', { + customer: { id: customer.id }, + text: order.text, + amount: order.amount, + price: order.price, + customerRabatt: order.customerRabatt ?? 0, + articleGroupRabatt: order.articleGroupRabatt ?? 0, + }).pipe( + map(o => { + if (o.success) { + const slip = new Slipsheet(o.data); + this.currentSlipsheetSubject.next(slip); + return slip; + } + throw new Error(o.message); + }) + ); + } + + /** POST /slipsheets — GetOrCreate: findet offenen LS für Kunden oder erstellt neuen, fügt direkt erste Position hinzu */ + getOrCreate(data: { article: any; amount: number; customer: any }): Observable { + return this.http.post('slipsheets', data).pipe( + map(o => { + if (o.success) { + const slip = new Slipsheet(o.data); + this.currentSlipsheetSubject.next(slip); + return slip; + } + throw new Error(o.message); + }) + ); + } + + /** @deprecated use getOrCreate */ + create(data: { article: any; amount: number; customer: any }): Observable { + return this.getOrCreate(data); + } + + addOrder(slipsheet: Slipsheet, order: Order): Observable { + return this.http.post(`slipsheets/${slipsheet.id}`, order).pipe( + map(o => { + if (o.success) return new Slipsheet(o.data); + throw new Error(o.message); + }) + ); + } + + updateOrder(slipsheet: Slipsheet, order: Order): Observable { + return this.http.put(`order-entries/${order.id}`, order).pipe( + map(o => { + if (o.success) { + const newEntries = order.amount !== 0 + ? slipsheet.orderEntries.map(m => m.id === order.id ? new Order(o.data) : m) + : slipsheet.orderEntries.filter(m => m.id !== order.id); + return new Slipsheet({ + ...slipsheet, + orderEntries: newEntries, + state: o.data?.slipsheet?.state || 'changed', + }); + } + throw new Error(o.message); + }) + ); + } + + addAnnotation(slipsheet: Slipsheet, text: string): Observable { + return this.http.post(`slipsheets/${slipsheet.id}/annotation`, { text }).pipe( + switchMap(() => this.getById(slipsheet.id)) + ); + } + + print(id: number): Observable { + return this.http.post(`slipsheets/${id}/print`, {}).pipe( + map(o => { + if (o.success) return true; + throw new Error(o.message); + }) + ); + } + + delete(id: number): Observable { + return this.http.delete(`slipsheets/${id}`).pipe( + map(o => { if (!o.success) throw new Error(o.message); }) + ); + } + + getPdf(id: number): Observable { + return this.http.get(`slipsheets/${id}/pdf`, { responseType: 'blob' }); + } +} diff --git a/apps/sim-system/src/app/views/article-detail/article-detail.component.html b/apps/sim-system/src/app/views/article-detail/article-detail.component.html new file mode 100644 index 0000000..32e2644 --- /dev/null +++ b/apps/sim-system/src/app/views/article-detail/article-detail.component.html @@ -0,0 +1,136 @@ +
+
+
Artikel
+

{{ isNew() ? 'Neuer Artikel' : 'Artikel bearbeiten' }}

+
+ +
+ +@if (loading()) {
} + +@if (error()) { +
{{ error() }}
+} + +@if (!loading()) { +
+ @if (!isNew() && inventoryArticle(); as article) { +
+
+
Inventur
+

Lagerstand wird nur über Inventur gepflegt

+

+ Bestand und Inventurdatum sind schreibgeschützt und werden ausschliesslich in der Inventurfunktion aktualisiert. +

+
+ + Lagerstand + {{ article.stock }} {{ article.unit }} + + + Letzte Inventur + {{ article.inventoryDate ? (article.inventoryDate | date:'dd.MM.yyyy') : '-' }} + +
+
+ +
+ } + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+
+ +
+
+
+ +
+ @if (!isNew()) { +
+ +
+ } +
+
+
+} diff --git a/apps/sim-system/src/app/views/article-detail/article-detail.component.scss b/apps/sim-system/src/app/views/article-detail/article-detail.component.scss new file mode 100644 index 0000000..5b6c6ba --- /dev/null +++ b/apps/sim-system/src/app/views/article-detail/article-detail.component.scss @@ -0,0 +1,163 @@ +.mb-3 { margin-bottom: 1rem; } + +.article-form-card { + padding: 1.25rem; +} + +.inventory-summary { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1.25rem; + background: + radial-gradient(circle at top right, rgba(20, 184, 166, 0.16), transparent 38%), + linear-gradient(135deg, rgba(15, 61, 58, 0.96), rgba(19, 78, 74, 0.92)); + border: 1px solid rgba(20, 184, 166, 0.24); + border-radius: var(--radius-md); + color: var(--white); + box-shadow: var(--shadow-md); +} + +@media (min-width: 900px) { + .inventory-summary { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} + +.inventory-summary-copy { + max-width: 54rem; +} + +.inventory-summary-title { + margin: 0 0 0.45rem; + font-family: var(--serif); + font-size: clamp(1.35rem, 3vw, 1.75rem); + font-weight: 400; + line-height: 1.05; +} + +.inventory-summary-text { + margin: 0; + color: rgba(255, 255, 255, 0.78); + max-width: 42rem; +} + +.inventory-summary-meta { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 1rem; +} + +.inventory-pill { + display: inline-flex; + align-items: center; + gap: 0.6rem; + padding: 0.65rem 0.85rem; + border: 1px solid rgba(255, 255, 255, 0.16); + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + font-size: 0.84rem; + color: rgba(255, 255, 255, 0.82); + + strong { + font-family: var(--mono); + color: var(--white); + font-weight: 500; + } +} + +.inventory-link { + align-self: flex-start; + white-space: nowrap; + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.22); + color: var(--white); + + &:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.16); + border-color: rgba(255, 255, 255, 0.4); + color: var(--white); + } +} + +.checkbox-grid { + display: grid; + gap: 0.85rem; +} + +@media (min-width: 900px) { + .checkbox-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +.checkbox-card { + position: relative; + display: flex; + gap: 0.85rem; + align-items: flex-start; + padding: 1rem; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: linear-gradient(180deg, var(--white), var(--bg-subtle)); + cursor: pointer; + transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease; + + &:hover { + border-color: rgba(20, 184, 166, 0.4); + box-shadow: var(--shadow-sm); + transform: translateY(-1px); + } + + input { + position: absolute; + inset: 0; + opacity: 0; + cursor: pointer; + } + + input:focus-visible + .checkbox-indicator { + outline: 2px solid rgba(20, 184, 166, 0.35); + outline-offset: 2px; + } + + input:checked + .checkbox-indicator { + background: var(--accent-highlight); + border-color: var(--accent-highlight); + box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.9); + } + + input:checked ~ .checkbox-copy strong { + color: var(--accent-primary); + } +} + +.checkbox-indicator { + flex: 0 0 auto; + width: 1.1rem; + height: 1.1rem; + margin-top: 0.15rem; + border: 1.5px solid var(--border-strong, var(--border)); + border-radius: 0.35rem; + background: var(--white); + transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; +} + +.checkbox-copy { + display: flex; + flex-direction: column; + gap: 0.25rem; + + strong { + color: var(--ink); + font-weight: 600; + } + + small { + color: var(--ink-3); + line-height: 1.35; + } +} diff --git a/apps/sim-system/src/app/views/article-detail/article-detail.component.ts b/apps/sim-system/src/app/views/article-detail/article-detail.component.ts new file mode 100644 index 0000000..ddf1b63 --- /dev/null +++ b/apps/sim-system/src/app/views/article-detail/article-detail.component.ts @@ -0,0 +1,108 @@ +import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { switchMap, map, catchError, of, filter } from 'rxjs'; +import { ArticleService } from '../../services/article.service'; +import { ArticleGroupService } from '../../services/article-group.service'; +import { Article, ArticleGroup } from '../../models/article.model'; + +@Component({ + selector: 'app-article-detail', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './article-detail.component.html', + styleUrl: './article-detail.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ArticleDetailComponent { + private route = inject(ActivatedRoute); + private router = inject(Router); + private articleService = inject(ArticleService); + private articleGroupService = inject(ArticleGroupService); + + readonly form = new FormGroup({ + id: new FormControl(null), + name: new FormControl('', [Validators.required]), + code: new FormControl('', [Validators.required]), + artNumber: new FormControl(''), + description: new FormControl(''), + supplier: new FormControl('', [Validators.required]), + type: new FormControl('', [Validators.required]), + price: new FormControl(0, [Validators.required, Validators.min(0)]), + unit: new FormControl('', [Validators.required]), + singlePos: new FormControl(false), + trackStock: new FormControl(true), + noDiscount: new FormControl(false), + articleGroup: new FormControl(null), + }); + + readonly saving = signal(false); + readonly error = signal(''); + + readonly articleGroups = toSignal(this.articleGroupService.getAll(), { initialValue: [] as ArticleGroup[] }); + + private readonly routeId = toSignal( + this.route.paramMap.pipe(map(p => p.get('id'))), + { initialValue: null } + ); + + readonly isNew = computed(() => this.routeId() === 'new'); + + private readonly loadedArticle = toSignal( + this.route.paramMap.pipe( + map(p => p.get('id')), + filter(id => id !== 'new' && id !== null), + switchMap(id => + this.articleService.getById(+id!).pipe( + map((data: any) => Array.isArray(data) ? data[0] : data), + catchError(() => { this.error.set('Artikel konnte nicht geladen werden.'); return of(null); }) + ) + ) + ) + ); + + readonly loading = computed(() => !this.isNew() && this.loadedArticle() === undefined && !this.error()); + readonly inventoryArticle = computed(() => this.loadedArticle()); + readonly canGoToInventory = computed(() => !!this.inventoryArticle()?.code); + + constructor() { + effect(() => { + const article = this.loadedArticle(); + if (article) { + this.form.patchValue({ ...article, articleGroup: article.articleGroup?.id ?? null }); + } + }); + } + + save() { + if (this.form.invalid) { this.form.markAllAsTouched(); return; } + this.saving.set(true); + const val = this.form.getRawValue(); + const article = new Article({ ...val, articleGroup: val.articleGroup ? { id: +val.articleGroup } : undefined } as any); + const op = val.id ? this.articleService.update(article) : this.articleService.create(article); + op.subscribe({ + next: () => { this.saving.set(false); this.router.navigate(['/articles']); }, + error: err => { this.saving.set(false); this.error.set(err.message || 'Fehler beim Speichern'); } + }); + } + + delete() { + if (!this.form.value.id) return; + if (!confirm('Artikel wirklich löschen?')) return; + const article = new Article(this.form.getRawValue() as any); + this.articleService.delete(article).subscribe({ + next: () => this.router.navigate(['/articles']), + error: err => this.error.set(err.message || 'Fehler beim Löschen') + }); + } + + goToInventory() { + const code = this.form.controls.code.value || this.inventoryArticle()?.code || ''; + if (!code) return; + this.router.navigate(['/inventory'], { queryParams: { code } }); + } + + back() { this.router.navigate(['/articles']); } +} diff --git a/apps/sim-system/src/app/views/articles/articles.component.html b/apps/sim-system/src/app/views/articles/articles.component.html new file mode 100644 index 0000000..d522a8e --- /dev/null +++ b/apps/sim-system/src/app/views/articles/articles.component.html @@ -0,0 +1,83 @@ +
+
+
Stammdaten
+

Artikel

+
+ +
+ + +
+
+ +
+
+ +
+
+ +@if (loading()) { +
+
+
+
+
+
+
+
+
+
+ @for (i of [1,2,3,4,5,6]; track i) { +
+
+
+
+
+
+
+
+
+ } +
+} + +@if (!loading() && filtered().length === 0) { +
+
📦
+
Keine Artikel gefunden
+
Passe die Suche an oder lege einen neuen Artikel an.
+
+} + +@if (!loading() && filtered().length > 0) { +
+ + + + + + + + + + + + + + @for (article of filtered(); track article.id) { + + + + + + + + + + } + +
BezeichnungArtikel-Nr.BarcodeLagerstandEinheitStatusInventurdatum
{{ article.name }}{{ article.artNumber }}{{ article.code }}{{ article.stock }}{{ article.unit }} + {{ getBadgeLabel(article.stock) }} + {{ article.inventoryDate ? (article.inventoryDate | date:'dd.MM.yyyy') : '—' }}
+
+} diff --git a/apps/sim-system/src/app/views/articles/articles.component.scss b/apps/sim-system/src/app/views/articles/articles.component.scss new file mode 100644 index 0000000..200ead0 --- /dev/null +++ b/apps/sim-system/src/app/views/articles/articles.component.scss @@ -0,0 +1 @@ +// Keine eigenen Styles nötig diff --git a/apps/sim-system/src/app/views/articles/articles.component.ts b/apps/sim-system/src/app/views/articles/articles.component.ts new file mode 100644 index 0000000..669a8de --- /dev/null +++ b/apps/sim-system/src/app/views/articles/articles.component.ts @@ -0,0 +1,75 @@ +import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormControl } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { map } from 'rxjs'; +import { debounceTime, startWith } from 'rxjs/operators'; +import { ArticleService } from '../../services/article.service'; +import { Article } from '../../models/article.model'; + +@Component({ + selector: 'app-articles', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './articles.component.html', + styleUrl: './articles.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ArticlesComponent { + private articleService = inject(ArticleService); + private router = inject(Router); + private route = inject(ActivatedRoute); + + readonly searchCtrl = new FormControl(''); + readonly codeCtrl = new FormControl(''); + + private readonly allArticles = toSignal(this.articleService.getAll(), { initialValue: [] as Article[] }); + private readonly routeCode = toSignal( + this.route.queryParamMap.pipe(map(params => params.get('code') ?? '')), + { initialValue: '' } + ); + private readonly searchTerm = toSignal( + this.searchCtrl.valueChanges.pipe(debounceTime(200), startWith('')), + { initialValue: '' } + ); + private readonly codeTerm = toSignal( + this.codeCtrl.valueChanges.pipe(debounceTime(200), startWith('')), + { initialValue: '' } + ); + + readonly loading = computed(() => this.allArticles() === undefined); + + constructor() { + effect(() => { + const code = this.routeCode(); + if (this.codeCtrl.value !== code) { + this.codeCtrl.setValue(code, { emitEvent: true }); + } + }); + } + + readonly filtered = computed(() => { + const name = (this.searchTerm() ?? '').toLowerCase(); + const code = (this.codeTerm() ?? '').toLowerCase(); + return (this.allArticles()).filter(a => + a.name.toLowerCase().includes(name) && + a.code.toLowerCase().includes(code) + ); + }); + + getBadgeClass(stock: number): string { + if (stock <= 0) return 'sims-badge sims-badge-error'; + if (stock < 10) return 'sims-badge sims-badge-warning'; + return 'sims-badge sims-badge-success'; + } + + getBadgeLabel(stock: number): string { + if (stock <= 0) return 'Kein Bestand'; + if (stock < 10) return 'Niedrig'; + return 'Verfügbar'; + } + + goToArticle(id: number) { this.router.navigate(['/articles', id]); } + newArticle() { this.router.navigate(['/articles', 'new']); } +} diff --git a/apps/sim-system/src/app/views/bill-detail/bill-detail.component.html b/apps/sim-system/src/app/views/bill-detail/bill-detail.component.html new file mode 100644 index 0000000..363c485 --- /dev/null +++ b/apps/sim-system/src/app/views/bill-detail/bill-detail.component.html @@ -0,0 +1,136 @@ +
+
+
Rechnungen
+

Rechnung #{{ bill()?.getNumber() || '...' }}

+
+ +
+ +@if (loading()) { +
+} + +@if (error()) { +
{{ error() }}
+} + +@if (bill() && !loading()) { +
+
+
+
Rechnungsdatum
+ @if (!editingDate()) { +
+ {{ bill()!.billDate | date:'dd.MM.yyyy' }} + +
+ } @else { +
+ + + +
+
Speichern aktualisiert auch das Rechnungspdf.
+ } +
+
+
Status
+
+ {{ badgeLabel(bill()!.state) }} +
+
+
+
Lieferscheine
+
{{ bill()!.slipsheets.length }}
+
+
+
Rechnungsnummer
+
{{ bill()!.getNumber() }}
+
+
+
+ + + @if (bill()!.slipsheets.length) { +
+ + + + + + + + + + @for (slipsheet of bill()!.slipsheets; track slipsheet.id) { + + + + + + } + +
Lieferschein-Nr.DatumStatus
{{ slipsheet.slipsheetnumber }}{{ slipsheet.createdAt | date:'dd.MM.yyyy' }} + + {{ slipsheet.getStateLabel() }} + +
+
+ } + +
+ + +
+ +
+
+
Rueckgaengig machen
+

Rechnung loeschen und Lieferscheine wieder freigeben

+

+ Die Rechnung wird entfernt. Zugeordnete Lieferscheine bleiben abgeschlossen, koennen danach aber wieder neu verrechnet werden. +

+
+ +
+ +} diff --git a/apps/sim-system/src/app/views/bill-detail/bill-detail.component.scss b/apps/sim-system/src/app/views/bill-detail/bill-detail.component.scss new file mode 100644 index 0000000..ec2d185 --- /dev/null +++ b/apps/sim-system/src/app/views/bill-detail/bill-detail.component.scss @@ -0,0 +1,121 @@ +.mb-3 { + margin-bottom: 1rem; +} + +.error-card { + color: var(--status-error); + border-color: var(--status-error-border); +} + +.bill-overview { + padding: 1.25rem; +} + +.date-display { + display: inline-flex; + align-items: center; + gap: 0.6rem; + margin-top: 0.35rem; +} + +.date-value { + font-family: var(--mono); + font-size: 1rem; + color: var(--ink); +} + +.date-edit-button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + padding: 0; + border: 0; + border-radius: 999px; + background: var(--white); + color: var(--ink); + box-shadow: none; + transition: transform 0.15s ease, opacity 0.15s ease, background 0.15s ease; + + svg { + width: 0.95rem; + height: 0.95rem; + } + + &:hover:not(:disabled) { + background: var(--ink-2); + transform: translateY(-1px); + } + + &:disabled { + opacity: 0.45; + cursor: not-allowed; + } +} + +.date-editor { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-top: 0.35rem; +} + +@media (min-width: 768px) { + .date-editor { + flex-direction: row; + align-items: center; + } + + .date-editor .sims-input { + max-width: 15rem; + } +} + +.date-hint { + margin-top: 0.5rem; + color: var(--ink-3); + font-size: 0.9rem; +} + +.meta-value { + margin-top: 0.35rem; +} + +.release-card { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1.25rem; + border-color: rgba(190, 24, 93, 0.16); + background: + radial-gradient(circle at top right, rgba(251, 113, 133, 0.12), transparent 40%), + linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(255, 244, 246, 0.96)); +} + +@media (min-width: 900px) { + .release-card { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} + +.release-title { + margin: 0.2rem 0 0.4rem; + font-family: var(--serif); + font-size: clamp(1.2rem, 2vw, 1.45rem); + font-weight: 400; +} + +.release-copy { + margin: 0; + max-width: 42rem; + color: var(--ink-3); +} + +.sticky-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} diff --git a/apps/sim-system/src/app/views/bill-detail/bill-detail.component.ts b/apps/sim-system/src/app/views/bill-detail/bill-detail.component.ts new file mode 100644 index 0000000..bd72aaa --- /dev/null +++ b/apps/sim-system/src/app/views/bill-detail/bill-detail.component.ts @@ -0,0 +1,254 @@ +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + computed, + inject, + signal, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { catchError, map, of, switchMap } from 'rxjs'; +import { Bill } from '../../models/bill.model'; +import { BillService } from '../../services/bill.service'; + +@Component({ + selector: 'app-bill-detail', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './bill-detail.component.html', + styleUrl: './bill-detail.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BillDetailComponent { + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly billService = inject(BillService); + private readonly destroyRef = inject(DestroyRef); + + readonly bill = signal(null); + readonly loading = signal(true); + readonly error = signal(''); + readonly downloading = signal(false); + readonly recreating = signal(false); + readonly savingDate = signal(false); + readonly deleting = signal(false); + readonly editingDate = signal(false); + readonly billDateDraft = signal(''); + + readonly busy = computed( + () => + this.downloading() || + this.recreating() || + this.savingDate() || + this.deleting(), + ); + + readonly hasDateChanges = computed(() => { + const bill = this.bill(); + if (!bill) { + return false; + } + + return this.billDateDraft() !== this.toDateInputValue(bill.billDate); + }); + + constructor() { + this.route.paramMap + .pipe( + map((params) => +(params.get('id') || '0')), + switchMap((id) => { + if (!Number.isFinite(id) || id <= 0) { + this.error.set('Rechnung nicht gefunden.'); + this.loading.set(false); + return of(null); + } + + this.loading.set(true); + this.error.set(''); + + return this.billService.getById(id).pipe( + catchError(() => { + this.error.set('Rechnung konnte nicht geladen werden.'); + return of(null); + }), + ); + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe((bill) => { + this.setBill(bill); + this.loading.set(false); + }); + } + + downloadPdf() { + const bill = this.bill(); + if (!bill) { + return; + } + + this.downloading.set(true); + this.error.set(''); + + this.billService.getPdf(bill.id).subscribe({ + next: (blob) => { + this.downloading.set(false); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `rechnung-${bill.getNumber()}.pdf`; + link.click(); + URL.revokeObjectURL(url); + }, + error: () => { + this.downloading.set(false); + this.error.set('PDF nicht gefunden. Bitte neu erzeugen.'); + }, + }); + } + + recreatePdf() { + const bill = this.bill(); + if (!bill) { + return; + } + + this.recreating.set(true); + this.error.set(''); + + this.billService.recreate(bill).subscribe({ + next: (updatedBill) => { + this.setBill(updatedBill); + this.recreating.set(false); + this.downloadPdf(); + }, + error: () => { + this.recreating.set(false); + this.error.set('PDF konnte nicht neu erzeugt werden.'); + }, + }); + } + + saveBillDate() { + const bill = this.bill(); + const billDate = this.billDateDraft(); + if (!bill || !billDate || !this.hasDateChanges()) { + return; + } + + this.savingDate.set(true); + this.error.set(''); + + this.billService + .update({ + id: bill.id, + billNumber: bill.billNumber, + billDate, + }) + .subscribe({ + next: (updatedBill) => { + this.setBill(updatedBill); + this.editingDate.set(false); + this.savingDate.set(false); + }, + error: () => { + this.savingDate.set(false); + this.error.set('Rechnungsdatum konnte nicht gespeichert werden.'); + }, + }); + } + + releaseBill() { + const bill = this.bill(); + if (!bill) { + return; + } + + const confirmed = window.confirm( + 'Rechnung wirklich zurueckholen? Die Rechnung wird geloescht und alle Lieferscheine werden wieder freigegeben.', + ); + if (!confirmed) { + return; + } + + this.deleting.set(true); + this.error.set(''); + + this.billService.delete(bill.id).subscribe({ + next: () => { + this.deleting.set(false); + this.router.navigate(['/bills']); + }, + error: () => { + this.deleting.set(false); + this.error.set('Rechnung konnte nicht zurueckgeholt werden.'); + }, + }); + } + + back() { + this.router.navigate(['/bills']); + } + + startEditBillDate() { + const bill = this.bill(); + if (!bill) { + return; + } + + this.billDateDraft.set(this.toDateInputValue(bill.billDate)); + this.editingDate.set(true); + } + + cancelEditBillDate() { + const bill = this.bill(); + this.billDateDraft.set(this.toDateInputValue(bill?.billDate)); + this.editingDate.set(false); + } + + badgeClass(state: string): string { + if (state === 'open') { + return 'sims-badge sims-badge-warning'; + } + if (state === 'closed' || state === 'payed') { + return 'sims-badge sims-badge-success'; + } + return 'sims-badge sims-badge-neutral'; + } + + badgeLabel(state: string): string { + if (state === 'open') { + return 'offen'; + } + if (state === 'closed') { + return 'abgeschlossen'; + } + if (state === 'payed') { + return 'bezahlt'; + } + return state; + } + + private setBill(bill: Bill | null) { + this.bill.set(bill); + this.billDateDraft.set(this.toDateInputValue(bill?.billDate)); + } + + private toDateInputValue(value?: string | Date | null): string { + if (!value) { + return ''; + } + + const date = new Date(value); + if (Number.isNaN(date.getTime())) { + return ''; + } + + const year = date.getFullYear(); + const month = `${date.getMonth() + 1}`.padStart(2, '0'); + const day = `${date.getDate()}`.padStart(2, '0'); + return `${year}-${month}-${day}`; + } +} diff --git a/apps/sim-system/src/app/views/bills/bills.component.html b/apps/sim-system/src/app/views/bills/bills.component.html new file mode 100644 index 0000000..7c38b39 --- /dev/null +++ b/apps/sim-system/src/app/views/bills/bills.component.html @@ -0,0 +1,61 @@ +
+
+
Belege
+

Rechnungen

+
+
+ +@if (loading()) { +
+
+
+
+
+
+
+
+ @for (i of [1,2,3,4,5]; track i) { +
+
+
+
+
+
+
+ } +
+} + +@if (!loading() && bills().length === 0) { +
+
🧾
+
Keine Rechnungen
+
+} + +@if (!loading() && bills().length > 0) { +
+ + + + + + + + + + + + @for (b of bills(); track b.id) { + + + + + + + + } + +
RechnungsnummerDatumKundeLieferscheineStatus
{{ b.getNumber() }}{{ b.billDate | date:'dd.MM.yyyy' }}{{ b.customer?.companyName || b.customer?.lastName }}{{ b.slipsheets.length }}{{ badgeLabel(b.state) }}
+
+} diff --git a/apps/sim-system/src/app/views/bills/bills.component.scss b/apps/sim-system/src/app/views/bills/bills.component.scss new file mode 100644 index 0000000..3afbff9 --- /dev/null +++ b/apps/sim-system/src/app/views/bills/bills.component.scss @@ -0,0 +1 @@ +// keine eigenen Styles diff --git a/apps/sim-system/src/app/views/bills/bills.component.ts b/apps/sim-system/src/app/views/bills/bills.component.ts new file mode 100644 index 0000000..493791b --- /dev/null +++ b/apps/sim-system/src/app/views/bills/bills.component.ts @@ -0,0 +1,37 @@ +import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { BillService } from '../../services/bill.service'; + +@Component({ + selector: 'app-bills', + standalone: true, + imports: [CommonModule], + templateUrl: './bills.component.html', + styleUrl: './bills.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BillsComponent { + private billService = inject(BillService); + private router = inject(Router); + + readonly bills = toSignal(this.billService.getAll(), { initialValue: [] }); + readonly loading = computed(() => this.bills() === undefined); + + goTo(id: number) { this.router.navigate(['/bills', id]); } + + badgeClass(state: string): string { + if (state === 'open') return 'sims-badge sims-badge-warning'; + if (state === 'closed') return 'sims-badge sims-badge-success'; + if (state === 'payed') return 'sims-badge sims-badge-success'; + return 'sims-badge sims-badge-neutral'; + } + + badgeLabel(state: string): string { + if (state === 'open') return 'offen'; + if (state === 'closed') return 'erstellt'; + if (state === 'payed') return 'bezahlt'; + return state; + } +} diff --git a/apps/sim-system/src/app/views/customer-detail/customer-detail.component.html b/apps/sim-system/src/app/views/customer-detail/customer-detail.component.html new file mode 100644 index 0000000..28f43b5 --- /dev/null +++ b/apps/sim-system/src/app/views/customer-detail/customer-detail.component.html @@ -0,0 +1,290 @@ + +
+
+
+ Kunden + + Detailansicht +
+

{{ customer()?.getName() || 'Kundenansicht' }}

+
+
+ + +
+
+ + +@if (toast(); as t) { +
+ {{ t.msg }} +
+} + + +@if (loading()) {
} +@if (loadError()) {
{{ loadError() }}
} + + +@if (customer(); as c) { + + +
+
+
Kunde
+
{{ c.getName() }}
+
{{ c.postcode }} {{ c.country }}@if (c.email) { · {{ c.email }} }
+
+
+
Offen
+
{{ openCount() }}
+
{{ fmt(openTotal()) }} € Volumen
+
+
+
Verrechnet
+
{{ billedCount() }}
+
{{ bills().length }} Rechnung{{ bills().length !== 1 ? 'en' : '' }}
+
+
+
Kundenrabatt
+
{{ c.customerDiscount }}%
+
{{ c.customerNumber || '—' }}
+
+
+ + +
+ +
+
+
Belege
+

Lieferscheine

+
+
+ + @if (selectedCount() > 0) { + {{ selectedCount() }} ausgewählt + } + +
+
+ + + @if (slipsheets().length === 0) { +
+
🚚
+
Keine Lieferscheine
+
+ } @else { +
+ + + + + + + + + + + + + @for (s of filteredSlips(); track s.id) { + + + + + + + + + + } + +
Lieferschein-Nr.DatumBetragStatus
+ + {{ s.slipsheetnumber }}{{ s.createdAt | date:'dd.MM.yyyy' }}{{ fmt(s.getPrice()) }} € + {{ slipBillLabel(s) }} + + +
+
+ + + @if (activeSlip(); as slip) { +
+ +
+
+
Dokument
+
Lieferschein
+
+
+
{{ slip.slipsheetnumber }}
+
{{ slip.createdAt | date:'dd.MM.yyyy' }}
+ + {{ slip.isOpen() ? 'Offen' : 'Verrechnet' }} + +
+ + @if (editingSlipId() !== slip.id) { + + } @else { + + } +
+ + + @if (editingSlipId() === slip.id) { + + } @else { + +
+
+
+
Kunde
+
{{ c.getName() }}
+ @if (c.address) {
{{ c.address }}, {{ c.postcode }} {{ c.country }}
} +
+ @if (!slip.isOpen()) { +
+
Rechnung
+
RE-{{ slip.bill?.id }}
+
+ } +
+ + + @if (slip.orderEntries.length > 0) { +
+ + + + + + + + + + + @for (e of slip.orderEntries; track e.id) { + + + + + + + } + +
Art.-Nr.BezeichnungMengeBetrag
{{ e.article?.artNumber || '—' }}{{ e.text || e.article?.name || '—' }}{{ e.amount }}{{ fmt(e.getPrice()) }} €
+
+ } @else { +
Keine Positionen vorhanden.
+ } +
+ + + + } +
+ } + } +
+ + +
+
+
+
Belege
+

Rechnungen

+
+
+ + @if (bills().length === 0) { +
+
Keine Rechnungen
+
Rechnungen entstehen aus ausgewählten Lieferscheinen.
+
+ } @else { +
+ + + + + + + + + + + + @for (b of bills(); track b.id) { + + + + + + + + } + +
RechnungEnthaltene LieferscheineStatusGesamt
+
{{ b.getNumber() }}
+
{{ b.billDate | date:'dd.MM.yyyy' }}
+
+ @for (s of b.slipsheets; track s.id) { + {{ s.slipsheetnumber }} + } + @if (!b.slipsheets.length) { + + } + + {{ billStateLabel(b.state) }} + {{ fmt(b.getTotal()) }} € + +
+
+ } +
+ +} diff --git a/apps/sim-system/src/app/views/customer-detail/customer-detail.component.scss b/apps/sim-system/src/app/views/customer-detail/customer-detail.component.scss new file mode 100644 index 0000000..06a215b --- /dev/null +++ b/apps/sim-system/src/app/views/customer-detail/customer-detail.component.scss @@ -0,0 +1,514 @@ +/* ── Page Header ──────────────────────────────────────────────── */ +.billing-page-header { + display: flex; + flex-direction: column; + gap: 1rem; + margin-bottom: 1.5rem; +} + +@media (min-width: 1024px) { + .billing-page-header { + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + gap: 2rem; + margin-bottom: 2rem; + } +} + +.billing-header-left { flex: 1 1 auto; } + +.header-breadcrumb { + display: flex; + align-items: center; + gap: 0.5rem; + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--ink-3); + margin-bottom: 0.5rem; +} +.crumb-sep { color: var(--border); } + +.billing-title { + font-family: var(--serif); + font-size: clamp(1.75rem, 4vw, 2.5rem); + font-weight: 400; + color: var(--ink); + line-height: 1.1; + margin: 0; +} + +.header-actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + flex-shrink: 0; +} + +/* Selection count chip */ +.sel-count-chip { + display: inline-flex; + align-items: center; + background: var(--accent-primary); + color: var(--white); + border-radius: var(--radius-pill); + padding: 0.25rem 0.8rem; + font-size: 0.72rem; + letter-spacing: 0.1em; + text-transform: uppercase; + white-space: nowrap; +} + +/* ── Toast ────────────────────────────────────────────────────── */ +.inline-toast { + font-size: 0.9rem; + padding: 0.75rem 1.25rem; + border-radius: var(--radius-md); + margin-bottom: 1rem; + animation: fadeUp 0.25s ease forwards; + border-left: 3px solid transparent; + + &.toast-success { + background: var(--status-ok-bg); + color: #0f766e; + border-color: var(--accent-highlight); + } + &.toast-error { + background: var(--status-empty-bg); + color: #b91c1c; + border-color: var(--status-empty); + } +} + +.error-card { + color: var(--status-empty); + border-left: 3px solid var(--status-empty); +} +.mb-4 { margin-bottom: 1.5rem; } + +/* ── Kontext-Cards ────────────────────────────────────────────── */ +.ctx-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + margin-bottom: 1.5rem; +} + +@media (min-width: 768px) { + .ctx-grid { grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 2rem; } +} + +.ctx-card { + background: var(--white); + border: 1px solid var(--border); + border-left-width: 3px; + border-radius: var(--radius-md); + padding: 1rem; + box-shadow: var(--shadow-sm); + transition: box-shadow 0.18s; + + &:hover { box-shadow: var(--shadow-md); } +} +.ctx-teal { border-left-color: var(--accent-highlight); } +.ctx-amber { border-left-color: var(--status-low); } +.ctx-slate { border-left-color: var(--border); } + +.ctx-label { + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--ink-3); + margin-bottom: 0.5rem; +} +.ctx-value { + font-size: 1.6rem; + font-weight: 400; + color: var(--ink); + line-height: 1; + margin-bottom: 0.4rem; + &.mono { font-family: var(--mono); } +} +.ctx-meta { font-size: 0.8rem; color: var(--ink-3); } + +/* ── Section Cards ────────────────────────────────────────────── */ +.section-card { + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + overflow: hidden; +} + +.section-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + padding: 1.25rem 1.5rem 1rem; + border-bottom: 1px solid var(--border); +} + +.section-header-actions { + display: flex; + align-items: center; + gap: 0.5rem; + flex-shrink: 0; +} + +.eyebrow { + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--ink-3); + margin-bottom: 0.3rem; +} + +.section-title { + font-family: var(--serif); + font-size: 1.25rem; + font-weight: 400; + color: var(--ink); + margin: 0; +} + +/* Filter Button */ +.filter-btn { + font-size: 0.82rem; + padding: 0.45rem 1rem; + transition: background 0.15s, border-color 0.15s, color 0.15s; + + &.filter-active { + background: var(--accent-light); + border-color: var(--accent-highlight); + color: var(--accent-primary); + } +} + +/* ── Table ────────────────────────────────────────────────────── */ +.table-scroll { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + + table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; + + thead tr { + border-bottom: 2px solid var(--border); + } + + th { + font-family: var(--mono); + font-size: 0.62rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--ink-3); + padding: 0.6rem 0.75rem; + text-align: left; + white-space: nowrap; + } + + td { + padding: 0.65rem 0.75rem; + /* border-bottom: 1px solid var(--border-sub);*/ + color: var(--ink-2); + vertical-align: middle; + } + + tbody tr { + cursor: pointer; + transition: background 0.12s; + + &:hover td { background: var(--accent-light); } + &:last-child td { border-bottom: none; } + } + + td.numeric, th.numeric { font-family: var(--mono); } + } +} + +/* Row states */ +tbody tr.row-selected td { + background: var(--accent-light) !important; +} + +tbody tr.row-selected td:first-child { + box-shadow: inset 3px 0 0 var(--accent-highlight); +} + +tbody tr.row-active td { + background: var(--bg-subtle); +} + +tbody tr.row-billed { + opacity: 0.65; + cursor: default; + + &:hover td { background: transparent; } +} + +tbody tr.row-not-billable { + .sel-indicator { + opacity: 0.55; + } +} + +/* Column sizing */ +.col-sel { + width: 2rem; + padding: 0 0 0 0.75rem !important; +} + +.col-actions { + width: 5rem; + text-align: right; + padding-right: 0.75rem !important; +} + +/* Row action button (PDF etc.) */ +.btn-row-action { + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--accent-primary); + background: transparent; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + padding: 0.25rem 0.65rem; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, color 0.15s; + white-space: nowrap; + + &:hover { + background: var(--accent-light); + border-color: var(--accent-highlight); + color: var(--accent-primary); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +.sel-indicator { + display: inline-block; + width: 0.6rem; + height: 0.6rem; + border-radius: 50%; + border: 1.5px solid var(--border); + background: var(--white); + transition: background 0.12s, border-color 0.12s; + vertical-align: middle; + + &.sel-on { + background: var(--accent-highlight); + border-color: var(--accent-highlight); + } + + &.sel-disabled { + background: var(--bg-subtle); + border-color: var(--border); + } +} + +/* ── Document Panel (below table) ────────────────────────────── */ +.doc-panel { + border-top: 2px solid var(--border); + animation: fadeUp 0.2s ease forwards; +} + +.doc-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + background: var(--accent-secondary); + padding: 1.25rem 1.5rem; + color: var(--white); +} + +/* Bearbeiten button inside the teal doc-header */ +.btn-doc-edit { + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.1em; + text-transform: uppercase; + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.85); + border: 1px solid rgba(255, 255, 255, 0.25); + border-radius: var(--radius-pill); + padding: 0.35rem 0.9rem; + cursor: pointer; + align-self: flex-start; + transition: background 0.15s, border-color 0.15s; + white-space: nowrap; + + &:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.5); + } +} + +.btn-doc-close { + align-self: flex-start; + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.7); + background: transparent; + border-color: rgba(255, 255, 255, 0.25); + + &:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--white); + } +} + +.doc-eyebrow { + font-family: var(--mono); + font-size: 0.6rem; + letter-spacing: 0.16em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.45); + margin-bottom: 0.2rem; +} + +.doc-type { + font-family: var(--serif); + font-size: 1.75rem; + font-weight: 400; + color: var(--white); + margin: 0; +} + +.doc-header-meta { text-align: right; } + +.doc-nr { + font-size: 1.1rem; + color: var(--accent-highlight); +} + +.doc-date { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.5); + margin-top: 0.2rem; +} + +.doc-body { padding: 1.25rem 1.5rem; } + +.doc-info-row { + display: flex; + gap: 2rem; + flex-wrap: wrap; + margin-bottom: 1.25rem; +} + +.doc-info-val { + font-size: 0.95rem; + font-weight: 500; + color: var(--ink); + margin-top: 0.25rem; +} + +.doc-info-sub { + font-size: 0.82rem; + color: var(--ink-3); +} + +.doc-table-wrap { + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; +} + +.doc-no-entries { + font-size: 0.875rem; + color: var(--ink-3); + padding: 0.75rem 0; +} + +.doc-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem 1.5rem; + background: var(--bg-subtle); + border-top: 1px solid var(--border); +} + +.doc-footer-label { + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--ink-3); +} + +.doc-footer-total { + font-size: 1.35rem; + color: var(--ink); +} + +/* ── Empty State ─────────────────────────────────────────────── */ +.sims-empty-state { + padding: 2.5rem 1.5rem; + text-align: center; +} + +/* ── Bills table ─────────────────────────────────────────────── */ + +/* Bill rows */ +.bill-row-main { + cursor: pointer; + &:hover td { background: var(--accent-light); } +} + +.bill-number { + font-size: 0.875rem; + font-weight: 500; + color: var(--ink); + white-space: nowrap; +} + +.bill-date { + font-size: 0.72rem; + color: var(--ink-3); + letter-spacing: 0.04em; + margin-top: 0.2rem; +} + +.bill-total { + font-size: 0.9rem; + color: var(--ink); +} + +/* Slipsheet number chips inside bill row */ +.bill-slips { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; + align-items: center; +} + +.slip-chip { + display: inline-block; + background: var(--bg-subtle); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 0.1rem 0.45rem; + font-size: 0.68rem; + letter-spacing: 0.04em; + color: var(--ink-2); + white-space: nowrap; +} + +.no-slips { + font-size: 0.8rem; + color: var(--ink-3); +} diff --git a/apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts b/apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts new file mode 100644 index 0000000..c254107 --- /dev/null +++ b/apps/sim-system/src/app/views/customer-detail/customer-detail.component.ts @@ -0,0 +1,336 @@ +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + computed, + inject, + signal, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormControl } from '@angular/forms'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { SlipsheetEditorComponent } from '../../components/slipsheet-editor/slipsheet-editor.component'; +import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + switchMap, map, catchError, of, filter, + Subject, combineLatest, startWith, debounceTime, +} from 'rxjs'; +import { CustomerService } from '../../services/customer.service'; +import { BillService } from '../../services/bill.service'; +import { SlipsheetService } from '../../services/slipsheet.service'; +import { Customer } from '../../models/customer.model'; +import { Slipsheet, Bill } from '../../models/bill.model'; + +@Component({ + selector: 'app-customer-detail', + standalone: true, + imports: [CommonModule, ReactiveFormsModule, RouterModule, SlipsheetEditorComponent], + templateUrl: './customer-detail.component.html', + styleUrl: './customer-detail.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CustomerDetailComponent { + private route = inject(ActivatedRoute); + private router = inject(Router); + private customerService = inject(CustomerService); + private billService = inject(BillService); + private slipsheetService = inject(SlipsheetService); + private destroyRef = inject(DestroyRef); + + // ── Customer ───────────────────────────────────────────────── + readonly loadError = signal(''); + + readonly customer = toSignal( + this.route.paramMap.pipe( + map(p => p.get('id')), + filter((id): id is string => id !== null), + switchMap(id => + this.customerService.getById(+id).pipe( + catchError(() => { this.loadError.set('Kunde konnte nicht geladen werden.'); return of(null); }) + ) + ) + ) + ); + + readonly loading = computed(() => this.customer() === undefined && !this.loadError()); + + // ── Slipsheets + Bills (refreshable) ───────────────────────── + readonly slipsheets = signal([]); + readonly bills = signal([]); + private readonly refresh$ = new Subject(); + + // ── UI State ───────────────────────────────────────────────── + readonly activeSlipId = signal(null); + readonly editingSlipId = signal(null); + readonly selectedSlipIds = signal>(new Set()); + readonly showOnlyOpen = signal(true); + readonly queryCtrl = new FormControl(''); + readonly generatingBill = signal(false); + readonly toast = signal<{ type: 'success' | 'error'; msg: string } | null>(null); + + private readonly preselectId = toSignal( + this.route.queryParamMap.pipe(map(p => p.get('preselect'))), + { initialValue: null } + ); + private preselectApplied = false; + + private readonly query = toSignal( + this.queryCtrl.valueChanges.pipe(debounceTime(150), startWith('')), + { initialValue: '' } + ); + + // ── Computed ───────────────────────────────────────────────── + readonly openSlips = computed(() => this.slipsheets().filter(s => s.isOpen())); + readonly openCount = computed(() => this.openSlips().length); + readonly openTotal = computed(() => this.openSlips().reduce((s, x) => s + x.getPrice(), 0)); + readonly billedCount = computed(() => this.slipsheets().filter(s => !s.isOpen()).length); + + readonly filteredSlips = computed(() => { + const q = (this.query() ?? '').toLowerCase(); + return this.slipsheets() + .filter(s => this.showOnlyOpen() ? s.isOpen() : true) + .filter(s => !q || (s.slipsheetnumber ?? '').toLowerCase().includes(q)); + }); + + readonly activeSlip = computed(() => { + const id = this.activeSlipId(); + return id ? (this.slipsheets().find(s => s.id === id) ?? null) : null; + }); + + readonly selectedSlips = computed(() => + this.slipsheets().filter(s => this.selectedSlipIds().has(s.id) && this.isBillableSlip(s)) + ); + readonly selectedTotal = computed(() => + this.selectedSlips().reduce((s, x) => s + x.getPrice(), 0) + ); + readonly selectedCount = computed(() => this.selectedSlipIds().size); + readonly canGenerate = computed(() => this.selectedCount() > 0 && !this.generatingBill()); + + constructor() { + // Slipsheets laden + combineLatest([ + this.refresh$.pipe(startWith(null as null)), + toObservable(this.customer), + ]).pipe( + map(([, c]) => c), + filter((c): c is Customer => c != null), + switchMap(c => + this.customerService.getSlipsheets(c).pipe(catchError(() => of([] as Slipsheet[]))) + ), + takeUntilDestroyed(this.destroyRef) + ).subscribe(ss => { + this.slipsheets.set(ss); + this.syncSelectedSlipIds(ss); + + // Preselect-Logik: einmalig nach erstem Laden + if (!this.preselectApplied) { + const preId = this.preselectId(); + if (preId) { + this.preselectApplied = true; + const found = ss.find(s => s.id === +preId && s.isOpen()); + if (found) { + this.activeSlipId.set(found.id); + if (this.isBillableSlip(found)) { + this.selectedSlipIds.set(new Set([found.id])); + } + return; + } + } + } + + // Default: ersten offenen Lieferschein als aktiv setzen (wenn noch keiner gesetzt) + if (!this.activeSlipId()) { + const first = ss.find(s => s.isOpen()) ?? ss[0]; + if (first) this.activeSlipId.set(first.id); + } + }); + + // Bills laden + combineLatest([ + this.refresh$.pipe(startWith(null as null)), + toObservable(this.customer), + ]).pipe( + map(([, c]) => c), + filter((c): c is Customer => c != null), + switchMap(c => + this.customerService.getBills(c).pipe(catchError(() => of([] as Bill[]))) + ), + takeUntilDestroyed(this.destroyRef) + ).subscribe(bs => this.bills.set(bs)); + } + + // ── Actions ────────────────────────────────────────────────── + /** Klick auf Zeile: zeigt Dokument + togglet Selection für offene Lieferscheine */ + setActive(slip: Slipsheet) { + this.activeSlipId.set(slip.id); + if (!this.isBillableSlip(slip)) return; + const next = new Set(this.selectedSlipIds()); + if (next.has(slip.id)) next.delete(slip.id); + else next.add(slip.id); + this.selectedSlipIds.set(next); + } + + isSelected(id: number): boolean { + return this.selectedSlipIds().has(id); + } + + makeBill() { + const ids = [...this.selectedSlipIds()]; + if (!ids.length) return; + this.generatingBill.set(true); + this.billService.generate(ids).subscribe({ + next: bill => { + this.showToast('success', `${bill.getNumber()} wurde erfolgreich erstellt.`); + this.selectedSlipIds.set(new Set()); + this.generatingBill.set(false); + this.preselectApplied = true; + this.refresh$.next(); + }, + error: (err: { message?: string }) => { + this.showToast('error', err?.message || 'Fehler beim Erstellen der Rechnung.'); + this.generatingBill.set(false); + }, + }); + } + + editCustomer() { + const c = this.customer(); + if (c) this.router.navigate(['/customers', c.id, 'edit']); + } + + toggleOpenFilter() { this.showOnlyOpen.update(v => !v); } + + back() { this.router.navigate(['/customers']); } + + editSlip(id: number) { this.editingSlipId.set(id); } + + closeEditor() { this.editingSlipId.set(null); } + + onSlipDeleted() { + this.activeSlipId.set(null); + this.editingSlipId.set(null); + this.selectedSlipIds.set(new Set()); + this.preselectApplied = true; + this.refresh$.next(); + this.showToast('success', 'Lieferschein wurde gelöscht.'); + } + + onSlipUpdated(updated: Slipsheet) { + // Update local slipsheets array so changes are immediately visible + this.slipsheets.update(list => list.map(s => s.id === updated.id ? updated : s)); + // Remove the slip from the selection if it is no longer billable. + if (!this.isBillableSlip(updated)) { + const next = new Set(this.selectedSlipIds()); + next.delete(updated.id); + this.selectedSlipIds.set(next); + } + } + + readonly pdfLoadingId = signal(null); + readonly billPdfLoadingId = signal(null); + + openSlipPdf(slip: Slipsheet, event: Event) { + event.stopPropagation(); + if (this.pdfLoadingId() === slip.id) return; + this.pdfLoadingId.set(slip.id); + this.slipsheetService.getPdf(slip.id).subscribe({ + next: blob => { + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + setTimeout(() => URL.revokeObjectURL(url), 10000); + this.slipsheetService.getById(slip.id).subscribe({ + next: updated => { + this.onSlipUpdated(updated); + this.pdfLoadingId.set(null); + }, + error: () => { + this.pdfLoadingId.set(null); + this.showToast('error', 'Lieferschein konnte nach PDF-Erzeugung nicht aktualisiert werden.'); + }, + }); + }, + error: () => { + this.pdfLoadingId.set(null); + this.showToast('error', 'PDF konnte nicht geladen werden.'); + }, + }); + } + + openBillPdf(bill: Bill, event: Event) { + event.stopPropagation(); + if (this.billPdfLoadingId() === bill.id) return; + this.billPdfLoadingId.set(bill.id); + this.billService.getPdf(bill.id).subscribe({ + next: blob => { + this.billPdfLoadingId.set(null); + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + setTimeout(() => URL.revokeObjectURL(url), 10000); + }, + error: () => { + this.billPdfLoadingId.set(null); + this.showToast('error', 'Rechnung PDF konnte nicht geladen werden.'); + }, + }); + } + + navigateToBill(b: Bill) { + this.router.navigate(['/bills', b.id]); + } + + billStateLabel(state: string): string { + switch (state) { + case 'open': return 'Offen'; + case 'closed': return 'Abgeschlossen'; + case 'payed': return 'Bezahlt'; + default: return state; + } + } + + private showToast(type: 'success' | 'error', msg: string) { + this.toast.set({ type, msg }); + setTimeout(() => this.toast.set(null), 5000); + } + + // ── Badge helpers ───────────────────────────────────────────── + isBillableSlip(s: Slipsheet): boolean { + return s.state === 'closed' && !s.hasBill(); + } + + slipNeedsGeneration(s: Slipsheet): boolean { + return !s.hasBill() && s.state !== 'closed'; + } + + slipBillLabel(s: Slipsheet): string { + if (s.isCompleted()) return 'Verrechnet'; + if (this.isBillableSlip(s)) return 'Bereit'; + if (s.hasBill()) return 'Bearbeitet'; + return 'Noch nicht erzeugt'; + } + + slipBillBadge(s: Slipsheet): string { + if (s.isCompleted()) return 'sims-badge sims-badge-ok'; + if (this.isBillableSlip(s)) return 'sims-badge sims-badge-accent'; + if (s.hasBill()) return 'sims-badge sims-badge-warning'; + return 'sims-badge sims-badge-low'; + } + + billStateBadge(state: string): string { + if (state === 'open') return 'sims-badge sims-badge-low'; + if (state === 'closed' || state === 'payed') return 'sims-badge sims-badge-ok'; + return 'sims-badge sims-badge-neutral'; + } + + // ── Formatter ──────────────────────────────────────────────── + fmt(value: number): string { + return new Intl.NumberFormat('de-AT', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value); + } + + private syncSelectedSlipIds(slips: Slipsheet[]) { + const billableIds = new Set(slips.filter(s => this.isBillableSlip(s)).map(s => s.id)); + const next = new Set([...this.selectedSlipIds()].filter(id => billableIds.has(id))); + if (next.size !== this.selectedSlipIds().size) { + this.selectedSlipIds.set(next); + } + } +} diff --git a/apps/sim-system/src/app/views/customer-edit/customer-edit.component.html b/apps/sim-system/src/app/views/customer-edit/customer-edit.component.html new file mode 100644 index 0000000..8afa57b --- /dev/null +++ b/apps/sim-system/src/app/views/customer-edit/customer-edit.component.html @@ -0,0 +1,76 @@ +
+
+
Kunden
+

{{ isNew() ? 'Neuer Kunde' : (customer()?.getName() || 'Kunde bearbeiten') }}

+
+ +
+ +@if (loading()) {
} +@if (error()) {
{{ error() }}
} + +@if (!loading()) { +
+
+
Kundendaten
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + +
+
+} diff --git a/apps/sim-system/src/app/views/customer-edit/customer-edit.component.scss b/apps/sim-system/src/app/views/customer-edit/customer-edit.component.scss new file mode 100644 index 0000000..a6925b1 --- /dev/null +++ b/apps/sim-system/src/app/views/customer-edit/customer-edit.component.scss @@ -0,0 +1,33 @@ +.mb-3 { margin-bottom: 1rem; } + +.error-card { + color: var(--status-empty); + border-left: 3px solid var(--status-empty); +} + +.form-eyebrow { + font-family: var(--mono); + font-size: 0.6rem; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--ink-3); + margin-bottom: 1.25rem; +} + +.sticky-actions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; + margin-top: 0.5rem; +} + +@media (max-width: 767px) { + .sticky-actions { + position: sticky; + bottom: calc(60px + env(safe-area-inset-bottom)); + background: var(--bg-light); + padding: 0.75rem 0; + border-top: 1px solid var(--border); + margin-top: 1.5rem; + } +} diff --git a/apps/sim-system/src/app/views/customer-edit/customer-edit.component.ts b/apps/sim-system/src/app/views/customer-edit/customer-edit.component.ts new file mode 100644 index 0000000..cd24d88 --- /dev/null +++ b/apps/sim-system/src/app/views/customer-edit/customer-edit.component.ts @@ -0,0 +1,93 @@ +import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { map, filter, switchMap, catchError, of } from 'rxjs'; +import { CustomerService } from '../../services/customer.service'; +import { Customer } from '../../models/customer.model'; + +@Component({ + selector: 'app-customer-edit', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './customer-edit.component.html', + styleUrl: './customer-edit.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CustomerEditComponent { + private route = inject(ActivatedRoute); + private router = inject(Router); + private customerService = inject(CustomerService); + + readonly form = new FormGroup({ + id: new FormControl(null), + firstName: new FormControl(''), + lastName: new FormControl(''), + companyName: new FormControl(''), + email: new FormControl(''), + customerNumber: new FormControl(''), + phoneCompany: new FormControl(''), + phonePrivate: new FormControl(''), + address: new FormControl(''), + postcode: new FormControl(''), + country: new FormControl(''), + uid: new FormControl(''), + customerDiscount: new FormControl(0), + }); + + readonly saving = signal(false); + readonly error = signal(''); + + /** null = route customers/new (kein :id-Param), string = edit-Route */ + private readonly customerId = toSignal( + this.route.paramMap.pipe(map(p => p.get('id'))), + { initialValue: null } + ); + + readonly isNew = computed(() => !this.customerId()); + + readonly customer = toSignal( + this.route.paramMap.pipe( + map(p => p.get('id')), + filter((id): id is string => id !== null), + switchMap(id => + this.customerService.getById(+id).pipe( + catchError(() => { this.error.set('Kunde konnte nicht geladen werden.'); return of(null); }) + ) + ) + ) + ); + + readonly loading = computed(() => !this.isNew() && this.customer() === undefined && !this.error()); + + constructor() { + effect(() => { + const c = this.customer(); + if (c) this.form.patchValue(c as any); + }); + } + + save() { + if (this.form.invalid) { this.form.markAllAsTouched(); return; } + this.saving.set(true); + const val = this.form.getRawValue(); + const customer = new Customer(val as any); + const op = val.id ? this.customerService.update(customer) : this.customerService.create(customer); + op.subscribe({ + next: () => { + this.saving.set(false); + const id = this.customerId(); + if (id) this.router.navigate(['/customers', id]); + else this.router.navigate(['/customers']); + }, + error: err => { this.saving.set(false); this.error.set(err.message || 'Fehler beim Speichern'); }, + }); + } + + back() { + const id = this.customerId(); + if (id) this.router.navigate(['/customers', id]); + else this.router.navigate(['/customers']); + } +} diff --git a/apps/sim-system/src/app/views/customers/customers.component.html b/apps/sim-system/src/app/views/customers/customers.component.html new file mode 100644 index 0000000..e2397a7 --- /dev/null +++ b/apps/sim-system/src/app/views/customers/customers.component.html @@ -0,0 +1,45 @@ +
+
+
Stammdaten
+

Kunden

+
+ +
+ +
+ +
+ +@if (loading()) {
} + +@if (!loading() && filtered().length === 0) { +
+
👤
+
Keine Kunden gefunden
+
+} + +@if (!loading() && filtered().length > 0) { +
+ + + + + + + + + + + @for (c of filtered(); track c.id) { + + + + + + + } + +
Name / FirmaKundennummerE-MailOrt
{{ c.getName() }}{{ c.customerNumber }}{{ c.email }}{{ c.postcode }} {{ c.country }}
+
+} diff --git a/apps/sim-system/src/app/views/customers/customers.component.scss b/apps/sim-system/src/app/views/customers/customers.component.scss new file mode 100644 index 0000000..4ebe06c --- /dev/null +++ b/apps/sim-system/src/app/views/customers/customers.component.scss @@ -0,0 +1 @@ +// keine eigenen Styles nötig diff --git a/apps/sim-system/src/app/views/customers/customers.component.ts b/apps/sim-system/src/app/views/customers/customers.component.ts new file mode 100644 index 0000000..dd044c1 --- /dev/null +++ b/apps/sim-system/src/app/views/customers/customers.component.ts @@ -0,0 +1,43 @@ +import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormControl } from '@angular/forms'; +import { Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { debounceTime, startWith } from 'rxjs/operators'; +import { CustomerService } from '../../services/customer.service'; +import { Customer } from '../../models/customer.model'; + +@Component({ + selector: 'app-customers', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './customers.component.html', + styleUrl: './customers.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CustomersComponent { + private customerService = inject(CustomerService); + private router = inject(Router); + + readonly searchCtrl = new FormControl(''); + + private readonly allCustomers = toSignal(this.customerService.getAll(), { initialValue: [] as Customer[] }); + private readonly searchTerm = toSignal( + this.searchCtrl.valueChanges.pipe(debounceTime(200), startWith('')), + { initialValue: '' } + ); + + readonly loading = computed(() => this.allCustomers() === undefined); + + readonly filtered = computed(() => { + const q = (this.searchTerm() ?? '').toLowerCase(); + return (this.allCustomers()).filter(c => + c.companyName?.toLowerCase().startsWith(q) || + c.firstName?.toLowerCase().startsWith(q) || + c.lastName?.toLowerCase().startsWith(q) + ); + }); + + goToCustomer(id: number) { this.router.navigate(['/customers', id]); } + newCustomer() { this.router.navigate(['/customers', 'new']); } +} diff --git a/apps/sim-system/src/app/views/dashboard/dashboard.component.html b/apps/sim-system/src/app/views/dashboard/dashboard.component.html new file mode 100644 index 0000000..6e7c7ce --- /dev/null +++ b/apps/sim-system/src/app/views/dashboard/dashboard.component.html @@ -0,0 +1,247 @@ +
+
+
Übersicht
+

Dashboard

+
+
+ + + +
+
+ +@if (loading()) { +
+ @for (i of [1,2,3,4,5,6]; track i) { +
+ } +
+} + +@if (error()) { +
{{ error() }}
+} + +@if (summary() && !loading()) { + +
+
+
+
Artikel
+
{{ summary()!.kpis.totalArticles }}
+
+
+
+
+
Niedriger Bestand
+
{{ summary()!.kpis.lowStockCount }}
+
+
+
+
+
Nicht auf Lager
+
{{ summary()!.kpis.outOfStockCount }}
+
+
+
+
+
Offene LS
+
{{ summary()!.kpis.openSlipsheets }}
+ @if (summary()!.kpis.changedSlipsheets > 0) { +
{{ summary()!.kpis.changedSlipsheets }} geändert
+ } +
+
+
+
+
Offene Rechnungen
+
{{ summary()!.kpis.openBills }}
+
+
+
+
+
Inventur heute
+
{{ summary()!.kpis.inventoryAdjustmentsTodayCount }}
+ @if (summary()!.kpis.inventoryAdjustmentsTodayDelta !== 0) { +
+ {{ summary()!.kpis.inventoryAdjustmentsTodayDelta > 0 ? '+' : '' }}{{ summary()!.kpis.inventoryAdjustmentsTodayDelta }} +
+ } +
+
+
+ + + @if (trendPoints().length > 0) { +
+
Verbrauch & Inventur-Delta (letzte {{ trendPoints().length }} Tage)
+
+ @for (p of trendPoints(); track p.day) { +
+
+
+
+
+
+
+
{{ p.label }}
+
+ } +
+
+ Verbrauch + Inventur-Delta +
+
+ } + + + @if (summary()!.lowStockItems.length) { +
+
Niedriger Bestand
+ @for (item of summary()!.lowStockItems; track item.id) { +
+ {{ item.code }} + {{ item.name }} + + {{ item.stock }} {{ item.unit }} + +
+ } +
+ } + + +
+
+
+
Offene Lieferscheine
+ @if (!summary()!.documentQueue.openSlipsheets.length) { +
Keine
+ } + @for (slip of summary()!.documentQueue.openSlipsheets; track slip.id) { +
+ {{ slip.slipsheetnumber }} + {{ slip.customerName }} + offen +
+ } +
+
+
+
+
Geänderte Lieferscheine
+ @if (!summary()!.documentQueue.changedSlipsheets.length) { +
Keine
+ } + @for (slip of summary()!.documentQueue.changedSlipsheets; track slip.id) { +
+ {{ slip.slipsheetnumber }} + {{ slip.customerName }} + geändert +
+ } +
+
+
+
+
Offene Rechnungen
+ @if (!summary()!.documentQueue.openBills.length) { +
Keine
+ } + @for (bill of summary()!.documentQueue.openBills; track bill.id) { +
+ {{ bill.billNumber }} + {{ bill.customerName }} + offen +
+ } +
+
+
+ + +
+
+
+
Top-Bewegungen (7 Tage)
+ @if (!summary()!.topMovingArticles7.length) { +
Keine Daten
+ } + @for (a of summary()!.topMovingArticles7; track a.id; let i = $index) { +
+ {{ i + 1 }}. + {{ a.name }} + {{ a.totalAmount }} {{ a.unit }} +
+ } +
+
+
+
+
Top-Bewegungen (30 Tage)
+ @if (!summary()!.topMovingArticles30.length) { +
Keine Daten
+ } + @for (a of summary()!.topMovingArticles30; track a.id; let i = $index) { +
+ {{ i + 1 }}. + {{ a.name }} + {{ a.totalAmount }} {{ a.unit }} +
+ } +
+
+
+ + + @if (summary()!.recentInventoryActivities.length) { +
+
Letzte Inventuraktivitäten
+
+ + + + + + + + + + + @for (inv of summary()!.recentInventoryActivities; track inv.id) { + + + + + + + } + +
ArtikelDatumNeue MengeDifferenz
{{ inv.article?.name || '—' }}{{ inv.createdAt | date:'dd.MM.yyyy HH:mm' }}{{ inv.amountNew }} {{ inv.article?.unit }} + + {{ inv.diff > 0 ? '+' : '' }}{{ inv.diff }} + +
+
+
+ } +} diff --git a/apps/sim-system/src/app/views/dashboard/dashboard.component.scss b/apps/sim-system/src/app/views/dashboard/dashboard.component.scss new file mode 100644 index 0000000..8b25ed5 --- /dev/null +++ b/apps/sim-system/src/app/views/dashboard/dashboard.component.scss @@ -0,0 +1,20 @@ +.kpi-card { + cursor: pointer; +} +.kpi-label { margin-bottom: 0.5rem; } +.kpi-value { + font-family: var(--mono); + font-size: 1.75rem; + font-weight: 500; + color: var(--ink); + letter-spacing: -0.02em; +} +.kpi-warn .kpi-value { color: var(--status-low); } +.kpi-error .kpi-value { color: var(--status-empty); } +.low-stock-row, .doc-row { + display: flex; + align-items: center; + padding: 0.6rem 0; + border-bottom: 1px solid var(--border-sub); + &:last-child { border-bottom: none; } +} diff --git a/apps/sim-system/src/app/views/dashboard/dashboard.component.ts b/apps/sim-system/src/app/views/dashboard/dashboard.component.ts new file mode 100644 index 0000000..84ef3a8 --- /dev/null +++ b/apps/sim-system/src/app/views/dashboard/dashboard.component.ts @@ -0,0 +1,92 @@ +import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router, RouterModule } from '@angular/router'; +import { toSignal, toObservable } from '@angular/core/rxjs-interop'; +import { switchMap, catchError, of } from 'rxjs'; +import { DashboardService } from '../../services/dashboard.service'; +import { ArticleGroupService } from '../../services/article-group.service'; +import { ArticleGroup } from '../../models/article.model'; + +@Component({ + selector: 'app-dashboard', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './dashboard.component.html', + styleUrl: './dashboard.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DashboardComponent { + private router = inject(Router); + private dashboardService = inject(DashboardService); + private articleGroupService = inject(ArticleGroupService); + + // Filter-Signale + readonly days = signal(30); + readonly threshold = signal(10); + readonly articleGroupId = signal(null); + readonly autoRefreshSeconds = signal(0); + readonly error = signal(''); + + readonly articleGroups = toSignal(this.articleGroupService.getAll(), { initialValue: [] as ArticleGroup[] }); + + // Refresh-Tick erzwingt neuen API-Call ohne Filter-Änderung + private readonly _refreshTick = signal(0); + + // Reaktive Parameter → automatisch neu laden wenn Filter oder Tick sich ändern + private readonly params = computed(() => ({ + days: this.days(), + threshold: this.threshold(), + articleGroupId: this.articleGroupId() ?? undefined, + _tick: this._refreshTick(), // nur für Reaktivität, wird nicht ans Backend geschickt + })); + + readonly summary = toSignal( + toObservable(this.params).pipe( + switchMap(p => + this.dashboardService.getSummary(p).pipe( + catchError(() => { this.error.set('Dashboard konnte nicht geladen werden.'); return of(null); }) + ) + ) + ) + ); + + readonly loading = computed(() => this.summary() === undefined && !this.error()); + + // Auto-Refresh via effect + onCleanup + constructor() { + effect((onCleanup) => { + const secs = this.autoRefreshSeconds(); + if (secs <= 0) return; + const handle = setInterval(() => this.triggerRefresh(), secs * 1000); + onCleanup(() => clearInterval(handle)); + }); + } + + triggerRefresh() { this._refreshTick.update(n => n + 1); } + refreshNow() { this.triggerRefresh(); } + + // Trend-Ableitungen + readonly trendPoints = computed(() => { + const t = this.summary()?.stockTrend ?? []; + return t.slice(Math.max(0, t.length - 14)); + }); + readonly maxOutgoing = computed(() => + Math.max(1, ...this.trendPoints().map(x => Number(x.outgoing || 0))) + ); + readonly maxAdjustmentAbs = computed(() => + Math.max(1, ...this.trendPoints().map(x => Math.abs(Number(x.adjustmentDelta || 0)))) + ); + + getOutgoingH(v: number) { return Math.max(2, Math.round((v / this.maxOutgoing()) * 72)); } + getAdjustmentH(v: number) { return Math.max(2, Math.round((Math.abs(v) / this.maxAdjustmentAbs()) * 72)); } + + goToArticle(code?: string) { + code ? this.router.navigate(['/articles'], { queryParams: { code } }) : this.router.navigate(['/articles']); + } + goToBills() { this.router.navigate(['/bills']); } + goToSlipsheets() { this.router.navigate(['/slipsheets']); } + goToInventory() { this.router.navigate(['/inventory']); } + goToCustomer(id?: number) { + id ? this.router.navigate([`/customers/${id}`]) : this.router.navigate(['/customers']); + } +} diff --git a/apps/sim-system/src/app/views/inventory/inventory.component.html b/apps/sim-system/src/app/views/inventory/inventory.component.html new file mode 100644 index 0000000..85f153c --- /dev/null +++ b/apps/sim-system/src/app/views/inventory/inventory.component.html @@ -0,0 +1,131 @@ +
+
+
Werkzeuge
+

Inventur

+
+
+ + +
+ +
+
+ +
+ +
+
+ + +@if (article()) { +
+ + +
+
{{ article()!.name }}
+ +
+ + + + @if (article()!.type) { + + + + + } + + + + + @if (article()!.supplier) { + + + + + } + @if (article()!.description) { + + + + + } + +
+ + +
+
+
Soll
+
{{ shouldVal() }} {{ article()!.unit }}
+
+
+
+
Ist
+
+ + + + {{ article()!.unit }} +
+
+
+ + +
+ Differenz + + {{ diff() > 0 ? '+' : '' }}{{ diff() }} {{ article()!.unit }} + +
+ + +} + + +@if (recentLog().length > 0) { +
+
Zuletzt gebucht
+ @for (entry of recentLog(); track entry.code) { +
+ + {{ entry.articleName }} + {{ entry.isVal }} {{ entry.unit }} + + {{ entry.diff > 0 ? '+' : '' }}{{ entry.diff }} + +
+ } +
+} diff --git a/apps/sim-system/src/app/views/inventory/inventory.component.scss b/apps/sim-system/src/app/views/inventory/inventory.component.scss new file mode 100644 index 0000000..5683793 --- /dev/null +++ b/apps/sim-system/src/app/views/inventory/inventory.component.scss @@ -0,0 +1,140 @@ +/* ── Scan ── */ +.scan-card { margin-bottom: 0.75rem; padding: 0.875rem; } + +.scan-row { + display: flex; gap: 0.625rem; align-items: center; + margin-top: 0.5rem; +} +.scan-input-wrap { + flex: 1; display: flex; align-items: center; + background: var(--bg-light); + border: 1.5px solid var(--accent-highlight); + border-radius: var(--radius-md); + padding: 0 12px; height: 46px; + box-shadow: 0 0 0 3px rgba(20, 184, 166, 0.1); +} +.scan-input { + border: none; background: transparent; outline: none; + font-family: var(--mono); font-size: 0.95rem; letter-spacing: 0.04em; + color: var(--ink); width: 100%; min-height: 44px; +} +.scan-camera-btn { + width: 46px; height: 46px; flex-shrink: 0; + background: var(--accent-primary); border: none; + border-radius: var(--radius-md); + display: flex; align-items: center; justify-content: center; + color: white; cursor: pointer; +} + +/* ── Artikel-Card ── */ +.article-card { + margin-bottom: 0.625rem; + padding: 0.875rem; +} +.article-card-header { + margin-bottom: 0.625rem; +} +.article-name { + font-size: 0.9375rem; font-weight: 600; color: var(--ink); line-height: 1.3; +} +.article-meta { + display: flex; align-items: center; gap: 0.25rem; + font-family: var(--mono); font-size: 0.7rem; + color: var(--ink-3); margin-top: 0.2rem; +} +.article-meta .sep { color: var(--border); } + +.article-info-table { + width: 100%; border-collapse: collapse; +} +.article-info-table th { + font-family: var(--mono); font-size: 0.58rem; + text-transform: uppercase; letter-spacing: 0.1em; + color: var(--ink-3); padding: 4px 10px 4px 0; + white-space: nowrap; width: 1%; vertical-align: middle; +} +.article-info-table td { + font-size: 0.825rem; color: var(--ink-2); + padding: 4px 0; vertical-align: middle; +} +.stock-val { + font-size: 0.9375rem !important; font-weight: 600; color: var(--ink) !important; + small { font-size: 0.7rem; color: var(--ink-3); margin-left: 2px; } +} + +/* small badge variant inside table */ +.badge-sm { + font-size: 0.6rem !important; + padding: 1px 6px !important; + &::before { display: none; } +} + +/* ── Soll/Ist ── */ +.count-card { + display: flex; overflow: hidden; + border: 1px solid var(--border); border-radius: var(--radius-md); + background: white; box-shadow: var(--shadow-sm); + margin-bottom: 0.625rem; +} +.count-half { + flex: 1; padding: 10px 12px; + display: flex; flex-direction: column; gap: 3px; +} +.count-divider { width: 1px; background: var(--border); flex-shrink: 0; } +.count-val { + font-size: 1.3rem; color: var(--ink); + small { font-size: 0.7rem; color: var(--ink-3); margin-left: 2px; } +} +.count-val.muted { color: var(--ink-3); } +.count-ist-row { + display: flex; align-items: center; gap: 6px; +} +.step-btn { + width: 30px; height: 30px; border-radius: 50%; + border: 1px solid var(--border); background: var(--bg-light); + font-size: 1.1rem; color: var(--ink-2); + display: flex; align-items: center; justify-content: center; + cursor: pointer; flex-shrink: 0; line-height: 1; +} +.is-input { + border: none; outline: none; background: transparent; + font-family: var(--mono); font-size: 1.3rem; + color: var(--accent-primary); width: 4.5rem; text-align: center; + min-height: 36px; +} +.unit-label { color: var(--ink-3); font-size: 0.7rem; } + +/* ── Differenz ── */ +.diff-banner { + border-radius: var(--radius-md); margin-bottom: 0.75rem; + padding: 0.6rem 0.875rem; + display: flex; align-items: center; justify-content: space-between; + &.negative { background: var(--status-empty-bg); } + &.positive { background: var(--status-low-bg); } + &.zero { background: var(--status-ok-bg); } +} +.diff-val { font-size: 0.9rem; font-weight: 500; } + +/* ── Submit ── */ +.inv-submit { width: 100%; margin-bottom: 0.5rem; } +.inv-submit:disabled { opacity: 0.35; pointer-events: none; } + +/* ── Log ── */ +.log-section { + margin-top: 1.25rem; + .eyebrow { margin-bottom: 0.5rem; } +} +.log-item { + display: flex; align-items: center; gap: 0.5rem; + padding: 0.5rem 0.75rem !important; + margin-bottom: 0.375rem; +} +.log-check { color: var(--status-ok); flex-shrink: 0; } +.log-name { flex: 1; font-size: 0.825rem; } +.log-qty { font-size: 0.75rem; color: var(--ink-3); margin-right: 0.25rem; } +.log-diff { + font-size: 0.7rem; + &.ok { color: var(--status-ok); } + &.neg { color: var(--status-empty); } + &.pos { color: var(--status-low); } +} diff --git a/apps/sim-system/src/app/views/inventory/inventory.component.ts b/apps/sim-system/src/app/views/inventory/inventory.component.ts new file mode 100644 index 0000000..e96c4b0 --- /dev/null +++ b/apps/sim-system/src/app/views/inventory/inventory.component.ts @@ -0,0 +1,111 @@ +import { ChangeDetectionStrategy, Component, computed, effect, ElementRef, inject, signal, ViewChild } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormControl } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { debounceTime, switchMap, catchError, of, filter, map } from 'rxjs'; +import { ArticleService } from '../../services/article.service'; + +interface LogEntry { + articleName: string; + code: string; + isVal: number; + diff: number; + unit: string; +} + +@Component({ + selector: 'app-inventory', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './inventory.component.html', + styleUrl: './inventory.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InventoryComponent { + @ViewChild('codeInput') codeInputRef!: ElementRef; + + private articleService = inject(ArticleService); + private route = inject(ActivatedRoute); + + readonly codeCtrl = new FormControl(''); + readonly isVal = signal(0); + readonly submitting = signal(false); + readonly recentLog = signal([]); + private readonly routeCode = toSignal( + this.route.queryParamMap.pipe(map(params => params.get('code') ?? '')), + { initialValue: '' } + ); + + readonly article = toSignal( + this.codeCtrl.valueChanges.pipe( + debounceTime(300), + filter(code => !!code), + switchMap(code => + this.articleService.getByCode(code!).pipe(catchError(() => of(null))) + ) + ), + { initialValue: null } + ); + + readonly shouldVal = computed(() => this.article()?.stock ?? 0); + readonly diff = computed(() => this.isVal() - this.shouldVal()); + readonly diffClass = computed(() => { + const d = this.diff(); + if (d < 0) return 'negative'; + if (d > 0) return 'positive'; + return 'zero'; + }); + + constructor() { + effect(() => { + const code = this.routeCode(); + if (this.codeCtrl.value !== code) { + this.codeCtrl.setValue(code, { emitEvent: true }); + setTimeout(() => this.codeInputRef?.nativeElement?.focus(), 50); + } + }); + + // Wenn Artikel wechselt: isVal auf Soll vorbelegen + effect(() => { + const stock = this.shouldVal(); + this.isVal.set(stock); + }); + } + + stepIs(delta: number) { this.isVal.update(v => Math.max(0, v + delta)); } + + onIsInput(event: Event) { + const val = +(event.target as HTMLInputElement).value; + this.isVal.set(isNaN(val) ? 0 : val); + } + + onSubmit() { + const art = this.article(); + if (!art) return; + this.submitting.set(true); + this.articleService.createInventory(art, this.isVal()).subscribe({ + next: () => { + this.submitting.set(false); + const entry: LogEntry = { + articleName: art.name, + code: art.code, + isVal: this.isVal(), + diff: this.diff(), + unit: art.unit, + }; + this.recentLog.update(log => [entry, ...log].slice(0, 5)); + this.codeCtrl.setValue('', { emitEvent: false }); + this.isVal.set(0); + setTimeout(() => this.codeInputRef?.nativeElement?.focus(), 50); + }, + error: () => { this.submitting.set(false); } + }); + } + + logDiffClass(diff: number): string { + if (diff < 0) return 'neg'; + if (diff > 0) return 'pos'; + return 'ok'; + } +} diff --git a/apps/sim-system/src/app/views/login/login.component.html b/apps/sim-system/src/app/views/login/login.component.html new file mode 100644 index 0000000..82f607f --- /dev/null +++ b/apps/sim-system/src/app/views/login/login.component.html @@ -0,0 +1,22 @@ + diff --git a/apps/sim-system/src/app/views/login/login.component.scss b/apps/sim-system/src/app/views/login/login.component.scss new file mode 100644 index 0000000..10475ba --- /dev/null +++ b/apps/sim-system/src/app/views/login/login.component.scss @@ -0,0 +1,24 @@ +.login-fullscreen { + min-height: 100vh; + background: var(--bg-light); + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; +} +.login-card { max-width: 24rem; width: 100%; } +.login-logo { + font-size: 1rem; + letter-spacing: 0.3em; + color: var(--accent-primary); + margin-bottom: 1rem; +} +.login-error { + background: var(--status-error-light); + color: var(--status-error); + border: 1px solid var(--status-error-border); + border-radius: 0.75rem; + padding: 0.75rem 1rem; + font-size: 0.875rem; + margin-bottom: 1rem; +} diff --git a/apps/sim-system/src/app/views/login/login.component.ts b/apps/sim-system/src/app/views/login/login.component.ts new file mode 100644 index 0000000..d8cc8f6 --- /dev/null +++ b/apps/sim-system/src/app/views/login/login.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { AuthenticationService } from '../../services/authentication.service'; + +@Component({ + selector: 'app-login', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './login.component.html', + styleUrl: './login.component.scss', +}) +export class LoginComponent { + loginForm = new FormGroup({ + login: new FormControl('', [Validators.required]), + password: new FormControl('', [Validators.required]), + }); + error = ''; + loading = false; + + constructor(private authService: AuthenticationService, private router: Router) {} + + onSubmit() { + if (this.loginForm.invalid) return; + this.loading = true; + this.error = ''; + const { login, password } = this.loginForm.value; + this.authService.login(login!, password!).subscribe({ + next: res => { + this.loading = false; + if (res.success) { + this.router.navigate(['/dashboard']); + } else { + this.error = res.message || 'Anmeldung fehlgeschlagen'; + } + }, + error: () => { + this.loading = false; + this.error = 'Anmeldung fehlgeschlagen'; + } + }); + } +} diff --git a/apps/sim-system/src/app/views/order-new/order-new.component.html b/apps/sim-system/src/app/views/order-new/order-new.component.html new file mode 100644 index 0000000..2b8cc67 --- /dev/null +++ b/apps/sim-system/src/app/views/order-new/order-new.component.html @@ -0,0 +1,51 @@ +
+
+
Neue Bestellung
+

+ @if (slipsheet()) { LS {{ slipsheet()!.slipsheetnumber }} } + @else { Neuer Lieferschein } +

+
+ +
+ + +
+ @if (selectedCustomer()) { +
{{ selectedCustomer()!.getInitials() }}
+
+
+ {{ selectedCustomer()!.getName() }} +
+
{{ selectedCustomer()!.customerNumber }}
+
+ + } @else { +
+ + @if (filteredCustomers().length && customerSearchCtrl.value) { +
+ @for (c of filteredCustomers().slice(0, 8); track c.id) { + + } +
+ } +
+ } +
+ + +@if (selectedCustomer()) { +
+ +
+} diff --git a/apps/sim-system/src/app/views/order-new/order-new.component.scss b/apps/sim-system/src/app/views/order-new/order-new.component.scss new file mode 100644 index 0000000..d3005bf --- /dev/null +++ b/apps/sim-system/src/app/views/order-new/order-new.component.scss @@ -0,0 +1,154 @@ +.mb-3 { margin-bottom: 1rem; } +.mt-3 { margin-top: 1rem; } + +/* ── Kunden-Strip ─────────────────────────────────────────────────── */ +.customer-strip { + display: flex; + align-items: center; + gap: 0.75rem; + background: var(--bg-card, #fff); + border: 1px solid var(--border); + border-radius: 1rem; + padding: 0.75rem 1rem; + position: relative; +} + +.customer-avatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background: var(--accent-primary); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 0.875rem; + flex-shrink: 0; +} + +.customer-dropdown { + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + background: var(--bg-card, #fff); + border: 1px solid var(--border); + border-radius: 0.75rem; + box-shadow: 0 8px 24px rgba(0,0,0,0.12); + z-index: 100; + overflow: hidden; +} + +.customer-dropdown-item { + display: flex; + align-items: center; + width: 100%; + padding: 0.6rem 1rem; + background: none; + border: none; + text-align: left; + cursor: pointer; + font-family: inherit; + border-bottom: 1px solid var(--border); + + &:last-child { border-bottom: none; } + &:hover { background: var(--accent-light, #eff6ff); } +} + +/* ── Artikel-Infokarte ────────────────────────────────────────────── */ +.article-found-card { + display: flex; + align-items: flex-start; + gap: 0.75rem; + background: var(--accent-light, #eff6ff); + border: 1px solid var(--accent-highlight, #3b82f6); + border-radius: 0.75rem; + padding: 0.75rem 1rem; + margin-top: 0.75rem; +} + +/* ── Stepper ─────────────────────────────────────────────────────── */ +.step-btn { + width: 2rem; + height: 2rem; + border: 1px solid var(--border); + background: var(--bg-card, #fff); + border-radius: 50%; + font-size: 1rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + + &:active { background: var(--accent-light, #eff6ff); } +} + +.step-btn-sm { + width: 1.75rem; + height: 1.75rem; + border: 1px solid var(--border); + background: var(--bg-card, #fff); + border-radius: 50%; + font-size: 0.9rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + + &:active { background: var(--accent-light, #eff6ff); } +} + +/* ── Icon-Buttons ────────────────────────────────────────────────── */ +.btn-sims-icon { + background: none; + border: none; + cursor: pointer; + padding: 0.25rem; + border-radius: 0.5rem; + font-size: 1rem; + opacity: 0.7; + + &:hover { opacity: 1; background: var(--bg-light, #f7f7f4); } +} + +/* ── Inline-Edit Zeile ───────────────────────────────────────────── */ +.editing-row { + background: var(--accent-light, #eff6ff); +} + +/* ── Summe ───────────────────────────────────────────────────────── */ +.order-summary { + background: var(--bg-card, #fff); + border: 1px solid var(--border); + border-radius: 1rem; + padding: 0.75rem 1.25rem; +} + +.sum-line { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.35rem 0; + font-size: 0.9rem; + color: var(--ink-2); +} + +.sum-total { + font-size: 1.05rem; + font-weight: 600; + color: var(--ink); + border-top: 1px solid var(--border); + margin-top: 0.35rem; + padding-top: 0.5rem; +} + +/* ── Aktionsleiste ───────────────────────────────────────────────── */ +.order-action-bar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; + padding: 1rem 0 calc(1rem + env(safe-area-inset-bottom)); +} diff --git a/apps/sim-system/src/app/views/order-new/order-new.component.ts b/apps/sim-system/src/app/views/order-new/order-new.component.ts new file mode 100644 index 0000000..d2e8fce --- /dev/null +++ b/apps/sim-system/src/app/views/order-new/order-new.component.ts @@ -0,0 +1,88 @@ +import { + ChangeDetectionStrategy, Component, computed, + inject, signal, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormControl } from '@angular/forms'; +import { Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { debounceTime, startWith } from 'rxjs'; +import { CustomerService } from '../../services/customer.service'; +import { SlipsheetService } from '../../services/slipsheet.service'; +import { Customer } from '../../models/customer.model'; +import { Slipsheet } from '../../models/bill.model'; +import { SlipsheetEditorComponent } from '../../components/slipsheet-editor/slipsheet-editor.component'; + +@Component({ + selector: 'app-order-new', + standalone: true, + imports: [CommonModule, ReactiveFormsModule, SlipsheetEditorComponent], + templateUrl: './order-new.component.html', + styleUrl: './order-new.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class OrderNewComponent { + private router = inject(Router); + private customerService = inject(CustomerService); + private slipsheetService = inject(SlipsheetService); + + // ── Kunden-Suche ────────────────────────────────────────────── + readonly customerSearchCtrl = new FormControl(''); + + readonly allCustomers = toSignal(this.customerService.getAll(), { initialValue: [] as Customer[] }); + + private readonly customerSearch = toSignal( + this.customerSearchCtrl.valueChanges.pipe(debounceTime(150), startWith('')), + { initialValue: '' } + ); + + readonly filteredCustomers = computed(() => { + const q = (this.customerSearch() ?? '').toLowerCase(); + if (!q) return this.allCustomers(); + return this.allCustomers().filter(c => + c.companyName?.toLowerCase().startsWith(q) || + c.firstName?.toLowerCase().startsWith(q) || + c.lastName?.toLowerCase().startsWith(q) + ); + }); + + readonly selectedCustomer = signal( + this.customerService.currentCustomer ?? null + ); + + // ── Aktueller Lieferschein ──────────────────────────────────── + readonly slipsheet = signal(null); + + constructor() { + const preselected = this.selectedCustomer(); + if (preselected) this.loadOpenSlipsheet(preselected.id); + } + + private loadOpenSlipsheet(customerId: number) { + this.slipsheetService.getByCustomer(customerId).subscribe({ + next: slips => this.slipsheet.set(slips[0] ?? null), + }); + } + + selectCustomer(c: Customer) { + this.selectedCustomer.set(c); + this.customerService.selectCustomer(c); + this.customerSearchCtrl.setValue('', { emitEvent: false }); + this.loadOpenSlipsheet(c.id); + } + + clearCustomer() { + this.selectedCustomer.set(null); + this.slipsheet.set(null); + } + + onSlipUpdated(slip: Slipsheet) { + this.slipsheet.set(slip); + } + + onSlipDeleted() { + this.slipsheet.set(null); + } + + back() { this.router.navigate(['/slipsheets']); } +} diff --git a/apps/sim-system/src/app/views/settings/settings.component.html b/apps/sim-system/src/app/views/settings/settings.component.html new file mode 100644 index 0000000..deb3e73 --- /dev/null +++ b/apps/sim-system/src/app/views/settings/settings.component.html @@ -0,0 +1,310 @@ +
+
+
System
+

Einstellungen

+
+
+ +@if (error()) { +
{{ error() }}
+} + +@if (saveSuccess()) { +
Einstellungen gespeichert
+} + +
+ + + +
+ +@if (loading()) { +
+} + +@if (!loading()) { +
+ @if (activeTab() === 'firma') { +
+
Firmenstammdaten
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ } + + @if (activeTab() === 'zahlung') { +
+
Zahlungskonditionen
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
Bankverbindungen
+ +
+ @for (bank of bankAccountsArray.controls; track $index) { +
+
+ Bank {{ $index + 1 }} + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ } +
+ + +
+ } + + @if (activeTab() === 'logos') { +
+
Briefpapier
+
+ + + + + +
+ +

+ Die Auswahl wird erst mit "Einstellungen speichern" aktiv. +

+ + @if (isLetterheadMode('template_pdf')) { +
+ +
+
+ PDF hochladen + Jede Seite wird als Hintergrund fuer die PDF-Erzeugung genutzt. +
+
+ + + @if (templatePdfUrl()) { + + {{ templatePdfLabel() || 'Aktuelles Template ansehen' }} + + } +
+ } +
+ +
+
Logo und Badges
+

+ Diese Uploads werden nur im Modus "Generiert" verwendet. +

+ +
+
+ +
+ @if (logoPreviewUrl()) { + Logo + } @else { +
+ Logo hochladen + SVG, PNG oder JPG +
+ } +
+ +
+ +
+ +
+ @if (badge1PreviewUrl()) { + Badge links + } @else { +
+ Badge hochladen + SVG oder PNG +
+ } +
+ +
+ +
+ +
+ @if (badge2PreviewUrl()) { + Badge rechts + } @else { +
+ Badge hochladen + SVG, PNG oder JPG +
+ } +
+ +
+
+
+ } + +
+ +
+
+} diff --git a/apps/sim-system/src/app/views/settings/settings.component.scss b/apps/sim-system/src/app/views/settings/settings.component.scss new file mode 100644 index 0000000..8d63fcc --- /dev/null +++ b/apps/sim-system/src/app/views/settings/settings.component.scss @@ -0,0 +1,210 @@ +.mb-2 { margin-bottom: 0.5rem; } +.mb-3 { margin-bottom: 1rem; } +.mt-2 { margin-top: 0.75rem; } + +.error-card { + color: var(--status-empty); + border-left: 3px solid var(--status-empty); +} + +.success-card { + color: var(--status-ok); + border-left: 3px solid var(--status-ok); +} + +.form-eyebrow { + font-family: var(--mono); + font-size: 0.6rem; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--ink-3); + margin-bottom: 1.25rem; +} + +.settings-tabs { + display: flex; + gap: 2px; + background: var(--border); + padding: 2px; + border-radius: var(--radius-md); + width: fit-content; +} + +.settings-tab { + font-family: var(--sans); + font-size: 0.85rem; + padding: 0.45rem 1rem; + border: none; + border-radius: calc(var(--radius-md) - 2px); + background: transparent; + color: var(--ink-3); + cursor: pointer; + transition: background 0.15s, color 0.15s; + white-space: nowrap; +} + +.settings-tab.active { + background: white; + color: var(--ink); + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08); +} + +.settings-tab:hover:not(.active) { + color: var(--ink-2); +} + +.letterhead-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 0.85rem; +} + +.letterhead-card { + border: 1px solid var(--border); + background: var(--bg-subtle); + border-radius: var(--radius-md); + padding: 1rem; + text-align: left; + cursor: pointer; + transition: border-color 0.15s, background 0.15s, transform 0.15s; +} + +.letterhead-card:hover { + border-color: var(--accent-highlight); + transform: translateY(-1px); +} + +.letterhead-card.active { + border-color: var(--accent-highlight); + background: var(--accent-light); + box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06); +} + +.letterhead-copy { + font-size: 0.85rem; + color: var(--ink-2); + line-height: 1.5; + margin-top: 0.35rem; +} + +.settings-subtle { + font-size: 0.82rem; + color: var(--ink-3); + margin: 0.85rem 0 0; +} + +.template-upload { + margin-top: 1rem; +} + +.template-link { + display: inline-block; + margin-top: 0.65rem; + color: var(--accent-highlight); + text-decoration: none; + font-weight: 600; +} + +.template-link:hover { + text-decoration: underline; +} + +.bank-entry { + background: var(--bg-subtle); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 1rem; +} + +.bank-entry-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; +} + +.bank-label { + font-weight: 600; + font-size: 0.88rem; + color: var(--ink); +} + +.btn-sm { + font-size: 0.78rem; + padding: 0.25rem 0.7rem; +} + +.uploads-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1.25rem; + margin-top: 1rem; +} + +.upload-item { + display: flex; + flex-direction: column; + gap: 0.3rem; +} + +.upload-zone { + border: 2px dashed var(--border); + border-radius: var(--radius-md); + background: var(--bg-subtle); + min-height: 120px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: border-color 0.15s, background 0.15s; + overflow: hidden; +} + +.upload-zone.compact { + min-height: 96px; +} + +.upload-zone:hover { + border-color: var(--accent-highlight); + background: var(--accent-light); +} + +.upload-placeholder { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + color: var(--ink-3); + font-size: 0.85rem; + text-align: center; + padding: 1rem; +} + +.upload-preview { + max-width: 100%; + max-height: 120px; + object-fit: contain; + padding: 0.5rem; +} + +.sticky-actions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; + position: sticky; + bottom: 1rem; + padding: 0.75rem 0; +} + +.row { display: flex; flex-wrap: wrap; margin: -0.375rem; } +.row.g-3 > * { padding: 0.375rem; } +[class^="col-"] { width: 100%; } + +@media (min-width: 768px) { + .col-md-3 { width: 25%; } + .col-md-4 { width: 33.333%; } + .col-md-6 { width: 50%; } +} + +.col-6 { width: 50%; } +.col-12 { width: 100%; } diff --git a/apps/sim-system/src/app/views/settings/settings.component.ts b/apps/sim-system/src/app/views/settings/settings.component.ts new file mode 100644 index 0000000..823e2e2 --- /dev/null +++ b/apps/sim-system/src/app/views/settings/settings.component.ts @@ -0,0 +1,222 @@ +import { + ChangeDetectionStrategy, + Component, + OnInit, + inject, + signal, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { + FormArray, + FormControl, + FormGroup, + ReactiveFormsModule, +} from '@angular/forms'; +import { CompanySettings, LetterheadMode } from '../../models/settings.model'; +import { SettingsService } from '../../services/settings.service'; + +@Component({ + selector: 'app-settings', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './settings.component.html', + styleUrl: './settings.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SettingsComponent implements OnInit { + private settingsService = inject(SettingsService); + + readonly activeTab = signal<'firma' | 'zahlung' | 'logos'>('firma'); + readonly saving = signal(false); + readonly loading = signal(true); + readonly error = signal(''); + readonly saveSuccess = signal(false); + + readonly logoPreviewUrl = signal(null); + readonly badge1PreviewUrl = signal(null); + readonly badge2PreviewUrl = signal(null); + readonly templatePdfUrl = signal(null); + readonly templatePdfLabel = signal(''); + + readonly form = new FormGroup({ + companyName: new FormControl(''), + street: new FormControl(''), + zip: new FormControl(''), + city: new FormControl(''), + country: new FormControl('Oesterreich'), + phone: new FormControl(''), + email: new FormControl(''), + website: new FormControl(''), + firmenbuchnummer: new FormControl(''), + vatId: new FormControl(''), + issueCity: new FormControl(''), + vatRate: new FormControl(20), + paymentTermDays: new FormControl(14), + paymentFooterText: new FormControl(''), + letterheadMode: new FormControl('generated', { nonNullable: true }), + bankAccounts: new FormArray([]), + }); + + get bankAccountsArray(): FormArray { + return this.form.get('bankAccounts') as FormArray; + } + + ngOnInit() { + this.settingsService.get().subscribe({ + next: (settings) => { + this.patchForm(settings); + this.loading.set(false); + }, + error: () => { + this.error.set('Einstellungen konnten nicht geladen werden'); + this.loading.set(false); + }, + }); + } + + private patchForm(settings: CompanySettings) { + this.form.patchValue({ + companyName: settings.companyName, + street: settings.street, + zip: settings.zip, + city: settings.city, + country: settings.country, + phone: settings.phone, + email: settings.email, + website: settings.website, + firmenbuchnummer: settings.firmenbuchnummer, + vatId: settings.vatId, + issueCity: settings.issueCity, + vatRate: settings.vatRate, + paymentTermDays: settings.paymentTermDays, + paymentFooterText: settings.paymentFooterText, + letterheadMode: settings.letterheadMode, + }); + + this.bankAccountsArray.clear(); + (settings.bankAccounts ?? []).forEach((bank) => + this.bankAccountsArray.push(this.newBankGroup(bank)), + ); + + this.logoPreviewUrl.set(this.toUploadUrl(settings.logoPath)); + this.badge1PreviewUrl.set(this.toUploadUrl(settings.badge1Path)); + this.badge2PreviewUrl.set(this.toUploadUrl(settings.badge2Path)); + this.templatePdfUrl.set(this.toUploadUrl(settings.templatePdfPath)); + this.templatePdfLabel.set(this.extractFileName(settings.templatePdfPath)); + } + + private newBankGroup(init?: { name: string; iban: string; bic: string }): FormGroup { + return new FormGroup({ + name: new FormControl(init?.name ?? ''), + iban: new FormControl(init?.iban ?? ''), + bic: new FormControl(init?.bic ?? ''), + }); + } + + private toUploadUrl(path: string | null | undefined): string | null { + if (!path) { + return null; + } + + const relative = path.split('uploads/').pop(); + return relative ? `/uploads/${relative}` : null; + } + + private extractFileName(path: string | null | undefined): string { + if (!path) { + return ''; + } + + const normalized = path.replace(/\\/g, '/'); + return normalized.split('/').pop() ?? ''; + } + + addBank() { + this.bankAccountsArray.push(this.newBankGroup()); + } + + removeBank(index: number) { + this.bankAccountsArray.removeAt(index); + } + + save() { + if (this.form.invalid) { + this.form.markAllAsTouched(); + return; + } + + this.saving.set(true); + this.error.set(''); + this.saveSuccess.set(false); + + this.settingsService.save(this.form.getRawValue() as any).subscribe({ + next: (settings) => { + this.patchForm(settings); + this.saving.set(false); + this.saveSuccess.set(true); + setTimeout(() => this.saveSuccess.set(false), 3000); + }, + error: (err) => { + this.saving.set(false); + this.error.set(err.message || 'Fehler beim Speichern'); + }, + }); + } + + onFileChange(event: Event, field: 'logo' | 'badge1' | 'badge2' | 'templatePdf') { + const input = event.target as HTMLInputElement; + const file = input.files?.[0]; + if (!file) { + return; + } + + this.error.set(''); + + const upload$ = + field === 'logo' + ? this.settingsService.uploadLogo(file) + : field === 'badge1' + ? this.settingsService.uploadBadge1(file) + : field === 'badge2' + ? this.settingsService.uploadBadge2(file) + : this.settingsService.uploadTemplatePdf(file); + + upload$.subscribe({ + next: (settings) => { + this.patchForm(settings); + + if (field === 'templatePdf') { + this.templatePdfLabel.set(file.name); + input.value = ''; + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + const url = e.target?.result as string; + if (field === 'logo') this.logoPreviewUrl.set(url); + if (field === 'badge1') this.badge1PreviewUrl.set(url); + if (field === 'badge2') this.badge2PreviewUrl.set(url); + }; + reader.readAsDataURL(file); + input.value = ''; + }, + error: (err) => { + this.error.set(err.message || 'Upload fehlgeschlagen'); + input.value = ''; + }, + }); + } + + setTab(tab: 'firma' | 'zahlung' | 'logos') { + this.activeTab.set(tab); + } + + setLetterheadMode(mode: LetterheadMode) { + this.form.controls.letterheadMode.setValue(mode); + } + + isLetterheadMode(mode: LetterheadMode): boolean { + return this.form.controls.letterheadMode.value === mode; + } +} diff --git a/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.html b/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.html new file mode 100644 index 0000000..7b870bc --- /dev/null +++ b/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.html @@ -0,0 +1,56 @@ +
+
+
Lieferscheine
+

+ @if (slipsheet()) { LS {{ slipsheet()!.slipsheetnumber }} } + @else { Lieferschein } +

+
+ +
+ +@if (loading()) {
} +@if (error()) {
{{ error() }}
} + +@if (slipsheet(); as s) { + +
+
+
+
Kunde
+
+ {{ s.customer?.companyName || (s.customer?.lastName + ' ' + s.customer?.firstName) }} +
+
+
+
Datum
+
{{ s.createdAt | date:'dd.MM.yyyy' }}
+
+
+
Status
+
+ {{ s.getStateLabel() }} +
+
+
+
Verrechnung
+
+ @if (s.isOpen()) { + Offen + } @else { + Verrechnet · RE-{{ s.bill?.id }} + } +
+
+
+
+ + +
+ +
+} diff --git a/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.scss b/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.scss new file mode 100644 index 0000000..3529ada --- /dev/null +++ b/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.scss @@ -0,0 +1,19 @@ +.mb-3 { margin-bottom: 1rem; } + +.meta-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + + @media (min-width: 640px) { + grid-template-columns: repeat(4, 1fr); + } +} + +.meta-val { + margin-top: 0.3rem; + font-size: 0.9rem; + color: var(--ink); + + &.mono { font-family: var(--mono); } +} diff --git a/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.ts b/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.ts new file mode 100644 index 0000000..10554bd --- /dev/null +++ b/apps/sim-system/src/app/views/slipsheet-detail/slipsheet-detail.component.ts @@ -0,0 +1,69 @@ +import { + ChangeDetectionStrategy, Component, computed, DestroyRef, inject, signal, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { switchMap, map, catchError, of } from 'rxjs'; +import { SlipsheetService } from '../../services/slipsheet.service'; +import { Slipsheet } from '../../models/bill.model'; +import { Customer } from '../../models/customer.model'; +import { SlipsheetEditorComponent } from '../../components/slipsheet-editor/slipsheet-editor.component'; + +@Component({ + selector: 'app-slipsheet-detail', + standalone: true, + imports: [CommonModule, RouterModule, SlipsheetEditorComponent], + templateUrl: './slipsheet-detail.component.html', + styleUrl: './slipsheet-detail.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SlipsheetDetailComponent { + private route = inject(ActivatedRoute); + private router = inject(Router); + private slipsheetService = inject(SlipsheetService); + private destroyRef = inject(DestroyRef); + + readonly slipsheet = signal(null); + readonly loading = signal(true); + readonly error = signal(''); + + // Derive customer from slipsheet so the editor always has it + readonly customer = computed(() => this.slipsheet()?.customer ?? null); + + constructor() { + this.route.paramMap.pipe( + map(p => +p.get('id')!), + switchMap(id => + this.slipsheetService.getById(id).pipe( + catchError(() => { + this.error.set('Lieferschein konnte nicht geladen werden.'); + this.loading.set(false); + return of(null); + }) + ) + ), + takeUntilDestroyed(this.destroyRef) + ).subscribe(s => { + this.slipsheet.set(s); + this.loading.set(false); + }); + } + + onSlipUpdated(updated: Slipsheet) { + this.slipsheet.set(updated); + } + + back() { + const cId = this.slipsheet()?.customer?.id; + if (cId) this.router.navigate(['/customers', cId]); + else this.router.navigate(['/slipsheets']); + } + + badgeClass(state: string): string { + if (state === 'open') return 'sims-badge sims-badge-low'; + if (state === 'changed') return 'sims-badge sims-badge-empty'; + if (state === 'closed' || state === 'payed') return 'sims-badge sims-badge-ok'; + return 'sims-badge sims-badge-neutral'; + } +} diff --git a/apps/sim-system/src/app/views/slipsheets/slipsheets.component.html b/apps/sim-system/src/app/views/slipsheets/slipsheets.component.html new file mode 100644 index 0000000..0a0baec --- /dev/null +++ b/apps/sim-system/src/app/views/slipsheets/slipsheets.component.html @@ -0,0 +1,83 @@ +
+
+
Belege
+

Lieferscheine

+
+
+ + +
+
+ +@if (loading()) { +
+
+
+
+
+
+
+
+
+ @for (i of [1,2,3,4,5,6]; track i) { +
+
+
+
+
+
+
+
+ } +
+} + +@if (!loading() && filteredSlipsheets().length === 0) { +
+
🚚
+
+ {{ showOnlyOpen() ? 'Alle Lieferscheine verrechnet' : 'Keine Lieferscheine' }} +
+ @if (showOnlyOpen()) { +
Filter zurücksetzen um alle anzuzeigen.
+ } +
+} + +@if (!loading() && filteredSlipsheets().length > 0) { +
+ + + + + + + + + + + + + @for (s of filteredSlipsheets(); track s.id) { + + + + + + + + + } + +
NummerKundeDatumVerrechnungStatusPos.
{{ s.slipsheetnumber }}{{ s.customer?.companyName || s.customer?.lastName || '—' }}{{ s.createdAt | date:'dd.MM.yyyy' }} + {{ s.isOpen() ? 'Offen' : 'Verrechnet' }} + + {{ s.getStateLabel() }} + {{ s.orderEntries.length }}
+
+} diff --git a/apps/sim-system/src/app/views/slipsheets/slipsheets.component.scss b/apps/sim-system/src/app/views/slipsheets/slipsheets.component.scss new file mode 100644 index 0000000..6ebe14d --- /dev/null +++ b/apps/sim-system/src/app/views/slipsheets/slipsheets.component.scss @@ -0,0 +1,56 @@ +.header-actions { + display: flex; + gap: 0.5rem; + align-items: center; + flex-wrap: wrap; +} + +/* Filter-Button */ +.filter-btn { + display: inline-flex; + align-items: center; + gap: 0.4rem; + font-size: 0.875rem; + padding: 0.55rem 1.25rem; + transition: background 0.18s, border-color 0.18s, color 0.18s; + + &.filter-active { + background: var(--accent-light); + border-color: var(--accent-highlight); + color: var(--accent-primary); + } +} + +.filter-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.35rem; + height: 1.35rem; + background: var(--accent-highlight); + color: var(--white); + border-radius: 999px; + font-size: 0.65rem; + padding: 0 0.3rem; +} + +/* Offen/Verrechnet Badge */ +.slip-badge-open { + background: var(--status-low-bg); + color: #92400e; + border: 1px solid rgba(245, 158, 11, 0.3); +} + +.slip-badge-billed { + background: var(--status-ok-bg); + color: #0f766e; + border: 1px solid rgba(20, 184, 166, 0.25); +} + + +/* Offene Zeilen: subtile linke Border als Kontextsignal */ +@media (min-width: 768px) { + tbody tr.row-slip-open td:first-child { + box-shadow: inset 3px 0 0 var(--accent-highlight); + } +} diff --git a/apps/sim-system/src/app/views/slipsheets/slipsheets.component.ts b/apps/sim-system/src/app/views/slipsheets/slipsheets.component.ts new file mode 100644 index 0000000..b7c7de7 --- /dev/null +++ b/apps/sim-system/src/app/views/slipsheets/slipsheets.component.ts @@ -0,0 +1,57 @@ +import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { SlipsheetService } from '../../services/slipsheet.service'; +import { Slipsheet } from '../../models/bill.model'; + +@Component({ + selector: 'app-slipsheets', + standalone: true, + imports: [CommonModule], + templateUrl: './slipsheets.component.html', + styleUrl: './slipsheets.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SlipsheetsComponent { + private slipsheetService = inject(SlipsheetService); + private router = inject(Router); + + readonly slipsheets = toSignal(this.slipsheetService.getAll(), { initialValue: [] as Slipsheet[] }); + readonly loading = computed(() => this.slipsheets() === undefined); + + readonly showOnlyOpen = signal(false); + + readonly openCount = computed(() => this.slipsheets().filter(s => s.isOpen()).length); + + readonly filteredSlipsheets = computed(() => { + const all = this.slipsheets(); + return this.showOnlyOpen() ? all.filter(s => s.isOpen()) : all; + }); + + toggleFilter() { this.showOnlyOpen.update(v => !v); } + + goTo(s: Slipsheet) { + if (s.customer?.id) { + const extras = s.isOpen() ? { queryParams: { preselect: s.id } } : {}; + this.router.navigate(['/customers', s.customer.id], extras); + } else { + this.router.navigate(['/slipsheets', s.id]); + } + } + + newOrder() { this.router.navigate(['/order/new']); } + + stateBadgeClass(state: string): string { + if (state === 'open') return 'sims-badge sims-badge-warning'; + if (state === 'changed') return 'sims-badge sims-badge-accent'; + if (state === 'closed' || state === 'payed') return 'sims-badge sims-badge-success'; + return 'sims-badge sims-badge-neutral'; + } + + billBadgeClass(s: Slipsheet): string { + return s.isOpen() + ? 'sims-badge slip-badge-open' + : 'sims-badge slip-badge-billed'; + } +} diff --git a/apps/sim-system/src/environments/environment.prod.ts b/apps/sim-system/src/environments/environment.prod.ts new file mode 100644 index 0000000..ccc74ef --- /dev/null +++ b/apps/sim-system/src/environments/environment.prod.ts @@ -0,0 +1,4 @@ +export const environment = { + production: true, + apiUrl: '', +}; diff --git a/apps/sim-system/src/environments/environment.ts b/apps/sim-system/src/environments/environment.ts new file mode 100644 index 0000000..49aeb2c --- /dev/null +++ b/apps/sim-system/src/environments/environment.ts @@ -0,0 +1,4 @@ +export const environment = { + production: false, + apiUrl: 'http://localhost:3000/api/v1', +}; diff --git a/apps/sim-system/src/index.html b/apps/sim-system/src/index.html new file mode 100644 index 0000000..54f75f7 --- /dev/null +++ b/apps/sim-system/src/index.html @@ -0,0 +1,15 @@ + + + + + SIMS + + + + + + + + + + diff --git a/apps/sim-system/src/main.ts b/apps/sim-system/src/main.ts new file mode 100644 index 0000000..190f341 --- /dev/null +++ b/apps/sim-system/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)); diff --git a/apps/sim-system/src/styles.scss b/apps/sim-system/src/styles.scss new file mode 100644 index 0000000..e4b6268 --- /dev/null +++ b/apps/sim-system/src/styles.scss @@ -0,0 +1,10 @@ +// Bootstrap – nur Grid +@use 'bootstrap/scss/bootstrap-grid'; + +// sims Design System +@import './styles/variables'; +@import './styles/reset'; +@import './styles/typography'; +@import './styles/layout'; +@import './styles/components'; +@import './styles/mobile'; diff --git a/apps/sim-system/src/styles/_components.css b/apps/sim-system/src/styles/_components.css new file mode 100644 index 0000000..8001512 --- /dev/null +++ b/apps/sim-system/src/styles/_components.css @@ -0,0 +1,326 @@ +/* ── Touch Targets ── */ +.touch-target { + min-height: 44px; min-width: 44px; + display: flex; align-items: center; justify-content: center; +} + +/* ── Buttons ── */ +.btn-sims-primary { + background: var(--accent-primary); + color: white; + border: none; + border-radius: var(--radius-pill); + padding: 0.75rem 1.5rem; + font-family: var(--sans); + font-size: 1rem; + font-weight: 500; + min-height: 48px; + width: 100%; + transition: background 0.18s; + display: inline-flex; align-items: center; justify-content: center; gap: 6px; +} +@media (min-width: 768px) { + .btn-sims-primary { + width: auto; padding: 0.55rem 1.25rem; font-size: 0.875rem; + } + .btn-sims-primary:hover { + background: var(--accent-secondary); + } +} + +.btn-sims-outline { + background: white; + color: var(--ink-2); + border: 1px solid var(--border); + border-radius: var(--radius-pill); + padding: 0.7rem 1.5rem; + font-family: var(--sans); + font-size: 1rem; + min-height: 48px; + width: 100%; + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + transition: border-color 0.18s, color 0.18s; +} +@media (min-width: 768px) { + .btn-sims-outline { width: auto; font-size: 0.875rem; padding: 0.55rem 1.25rem; } + .btn-sims-outline:hover { border-color: var(--accent-highlight); color: var(--accent-primary); } +} + +.btn-sims-ghost { + background: none; border: none; color: var(--ink-2); + min-height: 44px; border-radius: var(--radius-pill); + padding: 0.5rem 1rem; + font-family: var(--sans); font-size: 0.875rem; + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + transition: color 0.18s, background 0.18s; +} +@media (hover: hover) { + .btn-sims-ghost:hover { color: var(--accent-primary); background: var(--accent-light); } +} + +.btn-sims-danger { + background: var(--status-empty-bg); + color: var(--status-empty); + border: 1px solid var(--status-empty-border); + border-radius: var(--radius-pill); min-height: 48px; width: 100%; + padding: 0.75rem 1.5rem; + font-family: var(--sans); font-size: 1rem; font-weight: 500; + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + transition: background 0.18s, color 0.18s; +} +@media (min-width: 768px) { + .btn-sims-danger { width: auto; font-size: 0.875rem; padding: 0.55rem 1.25rem; } + .btn-sims-danger:hover { background: var(--status-empty); color: white; } +} + +.btn-sims-icon { + background: none; border: none; + min-width: 44px; min-height: 44px; + display: flex; align-items: center; justify-content: center; + color: white; border-radius: 50%; padding: 0; + transition: background 0.15s; +} +@media (hover: hover) { + .btn-sims-icon:hover { background: rgba(255,255,255,0.12); } +} +.sims-page-content .btn-sims-icon { color: var(--ink-3); } +@media (hover: hover) { + .sims-page-content .btn-sims-icon:hover { background: var(--bg-subtle); color: var(--ink-2); } +} + +/* ── Cards ── */ +.sims-card { + background: white; + border: 1px solid var(--border); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + padding: 1.25rem; +} +@media (min-width: 768px) { + .sims-card { padding: 1.5rem; } +} +@media (hover: hover) { + .sims-card:hover { box-shadow: var(--shadow-md); } +} + +.sims-card-table { + border-radius: var(--radius-md); + overflow: hidden; + padding: 0; + background: white; + border: 1px solid var(--border); + box-shadow: var(--shadow-sm); +} + +/* ── Badges ── */ +.sims-badge { + display: inline-flex; align-items: center; gap: 0.35rem; + font-family: var(--mono); + font-size: 0.7rem; + text-transform: uppercase; letter-spacing: 0.08em; + padding: 0.28rem 0.75rem; border-radius: var(--radius-pill); white-space: nowrap; +} +.sims-badge::before { + content: ''; + display: inline-block; + width: 5px; height: 5px; + border-radius: 50%; + background: currentColor; +} +.sims-badge-ok { background: var(--status-ok-bg); color: #0f766e; border: 1px solid var(--status-ok-border); } +.sims-badge-low { background: var(--status-low-bg); color: #92400e; border: 1px solid var(--status-low-border); } +.sims-badge-empty { background: var(--status-empty-bg); color: #b91c1c; border: 1px solid var(--status-empty-border); } +.sims-badge-neutral { background: var(--bg-subtle); color: var(--ink-3); border: 1px solid var(--border); } +.sims-badge-accent { background: var(--accent-light); color: var(--accent-primary); border: 1px solid var(--status-ok-border); } + +/* Legacy aliases — werden auslaufend */ +.sims-badge-success { background: var(--status-ok-bg); color: #0f766e; border: 1px solid var(--status-ok-border); } +.sims-badge-warning { background: var(--status-low-bg); color: #92400e; border: 1px solid var(--status-low-border); } +.sims-badge-error { background: var(--status-empty-bg); color: #b91c1c; border: 1px solid var(--status-empty-border); } + +/* ── Labels ── */ +.sims-label { + display: block; + font-family: var(--mono); + font-size: var(--text-label); + font-weight: 400; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--ink-3); + margin-bottom: 0.4rem; +} + +/* ── Form Elements ── */ +.sims-input, +.sims-select, +.sims-textarea { + width: 100%; + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 0.7rem 1rem; + font-family: var(--sans); + font-size: 0.93rem; + color: var(--ink); + background: white; + transition: border-color 0.18s, box-shadow 0.18s; + min-height: 44px; + -webkit-appearance: none; + appearance: none; + outline: none; +} +.sims-input:focus, +.sims-select:focus, +.sims-textarea:focus { + border-color: var(--accent-highlight); + box-shadow: var(--shadow-focus); +} +.sims-input::placeholder { color: var(--ink-3); } +.sims-input:disabled, .sims-select:disabled, .sims-textarea:disabled { + background: var(--bg-subtle); color: var(--ink-3); +} +.sims-input.input-mono { font-family: var(--mono); font-size: 0.85rem; } + +/* ── Table (responsive cards) ── */ +@media (max-width: 767px) { + .table-responsive-cards thead { display: none; } + .table-responsive-cards tbody tr { + display: block; + background: white; + border: 1px solid var(--border); + border-radius: var(--radius-md); + margin-bottom: 0.75rem; + padding: 1rem; + } + .table-responsive-cards tbody td { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.3rem 0; + border: none; + font-size: 0.9375rem; + } + .table-responsive-cards tbody td::before { + content: attr(data-label); + font-family: var(--mono); + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--ink-3); + flex-shrink: 0; + margin-right: 1rem; + } +} + +@media (min-width: 768px) { + table { width: 100%; border-collapse: collapse; } + thead th { + background: var(--bg-subtle); + color: var(--ink-3); + font-family: var(--mono); + font-size: 0.68rem; font-weight: 400; + text-transform: uppercase; letter-spacing: 0.12em; + padding: 0.85rem 1.25rem; + text-align: left; border-bottom: 1px solid var(--border); + } + tbody tr { border-bottom: 1px solid var(--border-sub); } + tbody tr:hover { background: var(--accent-light); cursor: pointer; } + tbody td { + padding: 0.85rem 1.25rem; + color: var(--ink-2); font-size: 0.875rem; + vertical-align: middle; + } + td.numeric { + font-family: var(--mono); text-align: right; color: var(--ink); + font-size: 0.82rem; + } +} + +/* ── Modals ── */ +.sims-modal-backdrop { + position: fixed; inset: 0; + background: rgba(15, 23, 42, 0.45); + z-index: 200; + display: flex; align-items: center; justify-content: center; +} +.sims-modal { + background: white; + border-radius: var(--radius-md); + padding: 1.5rem; + width: 100%; max-width: 32rem; + box-shadow: var(--shadow-card); +} +@media (max-width: 767px) { + .sims-modal-backdrop { align-items: flex-end; } + .sims-modal { + border-radius: 1rem 1rem 0 0; + max-width: 100%; + padding-bottom: calc(1.5rem + env(safe-area-inset-bottom)); + max-height: 90vh; + overflow-y: auto; + } +} + +/* ── Sticky save actions ── */ +@media (max-width: 767px) { + .sticky-actions { + position: sticky; + bottom: calc(60px + env(safe-area-inset-bottom)); + background: var(--bg-light); + padding: 0.75rem 0; + border-top: 1px solid var(--border); + margin-top: 1.5rem; + } +} + +/* ── Empty state ── */ +.sims-empty-state { + text-align: center; + padding: var(--space-12) var(--space-6); + color: var(--ink-3); +} +.sims-empty-state .empty-icon { font-size: 2rem; margin-bottom: 0.75rem; } +.sims-empty-state .empty-title { + font-family: var(--serif); + font-size: 1.25rem; color: var(--ink-2); + margin-bottom: 0.5rem; +} +.sims-empty-state .empty-desc { font-size: 0.9rem; } + +/* ── Skeleton Table ─────────────────────────────────────────────── */ +.skel-table { + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: white; + overflow: hidden; +} +.skel-header { + display: flex; align-items: center; gap: 1.5rem; + padding: 0.7rem 1rem; + background: var(--bg-subtle); + border-bottom: 2px solid var(--border); +} +.skel-row { + display: flex; align-items: center; gap: 1.5rem; + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--border); +} +.skel-row:last-child { border-bottom: none; } +.sk { + height: 11px; + border-radius: 4px; + flex-shrink: 0; + background: linear-gradient( + 90deg, var(--bg-subtle) 25%, #e2e8f0 50%, var(--bg-subtle) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.3s ease infinite; +} +/* Staggered delay per row */ +.skel-row:nth-child(2) .sk { animation-delay: 0.07s; } +.skel-row:nth-child(3) .sk { animation-delay: 0.14s; } +.skel-row:nth-child(4) .sk { animation-delay: 0.21s; } +.skel-row:nth-child(5) .sk { animation-delay: 0.28s; } +.skel-row:nth-child(6) .sk { animation-delay: 0.35s; } +.skel-row:nth-child(7) .sk { animation-delay: 0.42s; } +/* Header bars slightly lighter */ +.skel-header .sk { opacity: 0.5; height: 9px; } diff --git a/apps/sim-system/src/styles/_layout.css b/apps/sim-system/src/styles/_layout.css new file mode 100644 index 0000000..786cd3f --- /dev/null +++ b/apps/sim-system/src/styles/_layout.css @@ -0,0 +1,114 @@ +/* ── Mobile (Basis) ── */ +.sims-topbar { + position: fixed; top: 0; left: 0; right: 0; + height: 56px; background: var(--accent-secondary); color: white; + display: flex; align-items: center; justify-content: space-between; + padding: 0 1rem; z-index: 100; +} +.sims-topbar-title { + font-family: var(--mono); + font-size: 0.78rem; letter-spacing: 0.12em; text-transform: lowercase; +} +.sims-topbar-title span { color: var(--accent-highlight); } +.sims-bottom-nav { + position: fixed; bottom: 0; left: 0; right: 0; + height: 60px; background: white; border-top: 1px solid var(--border); + display: flex; align-items: center; justify-content: space-around; + z-index: 100; + padding-bottom: env(safe-area-inset-bottom); +} +.sims-bottom-nav-item { + display: flex; flex-direction: column; align-items: center; justify-content: center; + min-width: 44px; min-height: 44px; gap: 3px; + color: var(--ink-3); background: none; border: none; padding: 0 8px; + font-family: var(--mono); font-size: 0.58rem; + text-transform: uppercase; letter-spacing: 0.06em; + cursor: pointer; +} +.sims-bottom-nav-item .nav-icon { width: 22px; height: 22px; } +.sims-bottom-nav-item .nav-icon svg { width: 22px; height: 22px; } +.sims-bottom-nav-item.active { color: var(--accent-highlight); } + +.sims-page-content { + padding-top: 56px; + padding-bottom: calc(60px + env(safe-area-inset-bottom)); + padding-left: 1rem; padding-right: 1rem; +} +.sims-sidebar { display: none; } +.sims-main { margin-left: 0; } + +/* ── Desktop (≥ 768px) ── */ +@media (min-width: 768px) { + .sims-topbar { display: none; } + .sims-bottom-nav { display: none; } + + .sims-sidebar { + display: flex; flex-direction: column; + width: 240px; background: var(--accent-secondary); + position: fixed; height: 100vh; top: 0; left: 0; z-index: 50; + overflow-y: auto; + } + + .sims-sidebar-logo { + padding: 1.5rem 1.25rem 1.25rem; + font-family: var(--mono); + font-size: 0.8rem; letter-spacing: 0.12em; text-transform: lowercase; + color: rgba(255, 255, 255, 0.5); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + margin-bottom: 0.5rem; + } + .sims-sidebar-logo span { color: var(--accent-highlight); } + + .sims-nav-section-label { + font-family: var(--mono); + font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.18em; + color: rgba(255, 255, 255, 0.25); + padding: 1rem 1rem 0.4rem; + } + + .sims-nav-item { + display: flex; align-items: center; gap: 10px; + padding: 0.55rem 0.75rem; + margin: 0.1rem 0.5rem; + border-radius: var(--radius-sm); + font-family: var(--sans); font-size: 0.875rem; + color: rgba(255, 255, 255, 0.6); + text-decoration: none; cursor: pointer; + transition: background 0.15s, color 0.15s; + } + .sims-nav-item.active { + background: rgba(20, 184, 166, 0.15); + color: var(--accent-highlight); + } + .sims-nav-item:hover:not(.active) { + color: rgba(255, 255, 255, 0.9); + background: rgba(255, 255, 255, 0.07); + } + + /* SVG icon wrapper — inherits color from parent link */ + .nav-icon { + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; width: 18px; height: 18px; + } + .nav-icon svg { display: block; } + + .sims-main { margin-left: 240px; } + + .sims-page-content { + padding: 2rem 2.5rem; + min-height: 100vh; + background: var(--bg-light); + } +} + +/* Page header */ +.sims-page-header { + margin-bottom: 1.5rem; +} +@media (min-width: 768px) { + .sims-page-header { + display: flex; align-items: flex-end; + justify-content: space-between; + margin-bottom: 2rem; + } +} diff --git a/apps/sim-system/src/styles/_mobile.css b/apps/sim-system/src/styles/_mobile.css new file mode 100644 index 0000000..c7202c3 --- /dev/null +++ b/apps/sim-system/src/styles/_mobile.css @@ -0,0 +1,30 @@ +/* Login ohne Nav */ +.no-nav .sims-page-content { padding-top: 0; padding-bottom: 0; } + +/* Skeleton */ +@keyframes shimmer { + from { background-position: -200% 0; } + to { background-position: 200% 0; } +} +.skeleton { + background: linear-gradient( + 90deg, var(--bg-subtle) 25%, var(--border) 50%, var(--bg-subtle) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.2s ease infinite; + border-radius: var(--radius-sm); +} + +/* Route-Wechsel */ +@keyframes fadeUp { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +.page-enter { animation: fadeUp 0.18s ease forwards; } + +/* Bottom Sheet */ +@keyframes slideUp { + from { transform: translateY(100%); } + to { transform: translateY(0); } +} +.bottom-sheet-enter { animation: slideUp 0.22s ease forwards; } diff --git a/apps/sim-system/src/styles/_reset.css b/apps/sim-system/src/styles/_reset.css new file mode 100644 index 0000000..fb1b9f6 --- /dev/null +++ b/apps/sim-system/src/styles/_reset.css @@ -0,0 +1,10 @@ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } +body { + font-family: 'Inter', sans-serif; + background: var(--bg-light); + color: var(--ink); + -webkit-font-smoothing: antialiased; +} +a { color: inherit; text-decoration: none; } +button { cursor: pointer; font-family: inherit; } +input, select, textarea { font-family: inherit; } diff --git a/apps/sim-system/src/styles/_typography.css b/apps/sim-system/src/styles/_typography.css new file mode 100644 index 0000000..d949356 --- /dev/null +++ b/apps/sim-system/src/styles/_typography.css @@ -0,0 +1,20 @@ +h1, .page-title { + font-family: 'DM Serif Display', serif; + font-weight: 400; + font-size: 1.4rem; + line-height: 1.1; + color: var(--ink); +} +@media (min-width: 768px) { + h1, .page-title { font-size: 1.75rem; } +} + +.eyebrow { + font-family: 'DM Mono', monospace; + font-size: 0.6rem; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--ink-3); +} + +.mono { font-family: 'DM Mono', monospace; } diff --git a/apps/sim-system/src/styles/_variables.css b/apps/sim-system/src/styles/_variables.css new file mode 100644 index 0000000..b2d35b8 --- /dev/null +++ b/apps/sim-system/src/styles/_variables.css @@ -0,0 +1,61 @@ +:root { + /* Neutrals — geteilt im abler.tirol Ökosystem */ + --white: #ffffff; + --bg-light: #f7f7f4; + --bg-subtle: #fbfbf9; + --ink: #0f172a; + --ink-2: #475569; + --ink-3: #94a3b8; + --border: #e2e8f0; + --border-sub: rgba(226, 232, 240, 0.7); + + /* Fonts */ + --sans: 'Inter', sans-serif; + --mono: 'DM Mono', monospace; + --serif: 'DM Serif Display', serif; + + /* Akzent — sims.abler.tirol (Teal) */ + --accent-primary: #134e4a; + --accent-secondary: #0f3d3a; + --accent-highlight: #14b8a6; + --accent-light: #f0fdfa; + + /* Status — sims-spezifisch */ + --status-ok: #14b8a6; + --status-ok-bg: #f0fdfa; + --status-ok-border: rgba(20, 184, 166, 0.25); + --status-low: #f59e0b; + --status-low-bg: #fffbeb; + --status-low-border: rgba(245, 158, 11, 0.25); + --status-empty: #ef4444; + --status-empty-bg: #fef2f2; + --status-empty-border: rgba(239, 68, 68, 0.25); + + /* Radius — sims: funktional, nicht dekorativ */ + --radius-pill: 999px; + --radius-md: 6px; + --radius-sm: 4px; + --radius-xs: 3px; + + /* Shadows */ + --shadow-card: 0 25px 80px -30px rgba(15, 23, 42, 0.18); + --shadow-md: 0 4px 16px rgba(15, 23, 42, 0.10); + --shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.06); + --shadow-focus: 0 0 0 3px rgba(20, 184, 166, 0.2); + + /* Spacing */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-12: 3rem; + + /* Typography */ + --text-body: 1rem; + --text-sm: 0.9375rem; + --text-xs: 0.875rem; + --text-label: 0.72rem; + --text-micro: 0.6875rem; +} diff --git a/apps/sim-system/src/test-setup.ts b/apps/sim-system/src/test-setup.ts new file mode 100644 index 0000000..434a70b --- /dev/null +++ b/apps/sim-system/src/test-setup.ts @@ -0,0 +1,6 @@ +import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless'; + +setupZonelessTestEnv({ + errorOnUnknownElements: true, + errorOnUnknownProperties: true, +}); diff --git a/apps/sim-system/tsconfig.app.json b/apps/sim-system/tsconfig.app.json new file mode 100644 index 0000000..d6a21af --- /dev/null +++ b/apps/sim-system/tsconfig.app.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": [ + "src/**/*.spec.ts", + "src/**/*.test.ts", + "jest.config.ts", + "jest.config.cts", + "src/test-setup.ts" + ] +} diff --git a/apps/sim-system/tsconfig.json b/apps/sim-system/tsconfig.json new file mode 100644 index 0000000..bb7614f --- /dev/null +++ b/apps/sim-system/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "isolatedModules": true, + "target": "es2022", + "moduleResolution": "bundler", + "emitDecoratorMetadata": false, + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/apps/sim-system/tsconfig.spec.json b/apps/sim-system/tsconfig.spec.json new file mode 100644 index 0000000..2eb8772 --- /dev/null +++ b/apps/sim-system/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"], + "moduleResolution": "node10" + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/design-guide.html b/design-guide.html new file mode 100644 index 0000000..10f8e8f --- /dev/null +++ b/design-guide.html @@ -0,0 +1,2143 @@ + + + + + +Design Guide — sims.abler.tirol + + + + + + + +
+
+

sims.abler.tirol · Design Guide

+

Design System

+

+ Vollständige Designreferenz für das SIM-System — Lagerverwaltung, Artikel-Tracking, + Inventur, Lieferschein- und Rechnungserstellung. Teal-Farbwelt, funktional-industriell, + Teil des abler.tirol Ökosystems. +

+
+
+ + + + +
+ + +
+ +

Farbsystem

+

+ sims verwendet Teal als Produktfarbe — industriell, ruhig, funktional. + Alle Produkte im abler.tirol-Ökosystem teilen dieselben Neutrals. + Maximal zwei Akzentfarben gleichzeitig sichtbar. +

+ +
+

Akzentfarben — sims (Teal)

+
+
+
+
+
--accent-primary
+
#134e4a · Teal 900
+
+
+
+
+
+
--accent-secondary
+
#0f3d3a · Teal 950
+
+
+
+
+
+
--accent-highlight
+
#14b8a6 · Teal 500
+
+
+
+
+
+
--accent-light
+
#f0fdfa · Teal 50
+
+
+
+
+ +
+

Geteilte Neutrals (alle abler.tirol Produkte)

+
+
+
+
+
--bg-light
+
#f7f7f4
+
+
+
+
+
+
--bg-subtle
+
#fbfbf9
+
+
+
+
+
+
--ink
+
#0f172a · Haupttext
+
+
+
+
+
+
--ink-2
+
#475569 · Body-Text
+
+
+
+
+
+
--ink-3
+
#94a3b8 · Muted
+
+
+
+
+
+
--border
+
#e2e8f0
+
+
+
+
+ +
+

Status-Farben (Bestandsstatus)

+
+
+
+
+
--status-ok-bg
+
#14b8a6 Text · "In Bestand"
+
+
+
+
+
+
--status-low-bg
+
#f59e0b Text · "Niedrig"
+
+
+
+
+
+
--status-empty-bg
+
#ef4444 Text · "Leer"
+
+
+
+
+ +
+

CSS Variablen — vollständig

+
/* sims.abler.tirol — CSS Custom Properties */
+:root {
+  /* Akzent (Teal) */
+  --accent-primary:   #134e4a;   /* Teal 900 */
+  --accent-secondary: #0f3d3a;   /* Teal 950 */
+  --accent-highlight: #14b8a6;   /* Teal 500 */
+  --accent-light:     #f0fdfa;   /* Teal 50 */
+
+  /* Neutrals (geteilt) */
+  --white:      #ffffff;
+  --bg-light:   #f7f7f4;
+  --bg-subtle:  #fbfbf9;
+  --ink:        #0f172a;
+  --ink-2:      #475569;
+  --ink-3:      #94a3b8;
+  --border:     #e2e8f0;
+  --border-sub: rgba(226,232,240,0.7);
+
+  /* Status */
+  --status-ok:       #14b8a6;
+  --status-low:      #f59e0b;
+  --status-empty:    #ef4444;
+  --status-ok-bg:    #f0fdfa;
+  --status-low-bg:   #fffbeb;
+  --status-empty-bg: #fef2f2;
+
+  /* Typografie */
+  --sans:  'Inter', sans-serif;
+  --mono:  'DM Mono', monospace;
+  --serif: 'DM Serif Display', serif;
+
+  /* Radius */
+  --radius-pill: 999px;
+  --radius-xl:   2rem;
+  --radius-lg:   1.5rem;
+  --radius-md:   1rem;
+  --radius-sm:   0.5rem;
+
+  /* Shadows */
+  --shadow-card: 0 25px 80px -30px rgba(15,23,42,0.18);
+  --shadow-sm:   0 1px 3px rgba(15,23,42,0.06);
+  --shadow-md:   0 4px 16px rgba(15,23,42,0.10);
+}
+
+
+ + +
+ +

Schriftsystem

+

+ Alle Fonts werden ausschließlich über api.abler.tirol geladen — nie von Google Fonts. + sims ist Inter-dominant. DM Serif Display sparsam einsetzen. + DM Mono für alle technischen Werte: Artikelnummern, Mengen, Badges. +

+ +
+

Font-Einbindung (Pflicht)

+
<!-- IMMER — nie Google Fonts -->
+<link rel="preconnect" href="https://api.abler.tirol" />
+<link rel="stylesheet" href="https://api.abler.tirol/fonts/abler-stack.css" />
+
+ +
+

DM Serif Display · 400 · Display

+

Lager­verwaltung

+
+ +
+

DM Serif Display · 400 italic · H1 Variante

+

Inventur 2024

+
+ +
+

Inter · 400 · Body-Text

+

+ Artikel werden nach Lagerbestand und Bewegungshistorie verwaltet. + Jeder Lieferschein ist einem Kunden zugeordnet und kann als PDF exportiert werden. + Die Rechnungsstellung erfolgt direkt aus dem Lieferschein. +

+
+ +
+

DM Mono · Label / Eyebrow

+

Artikelstamm · Lagerbewegung · Inventur

+
+ +
+

DM Mono · Artikelnummer / Mengenangaben

+

ART-00124 · 48 Stk. · €12,90/Stk. · Gesamt: €619,20

+
+ +
+

Größenraster

+
/* Größenraster sims */
+--text-display: clamp(3rem, 8vw, 6rem);      /* DM Serif Display */
+--text-h1:      clamp(2.2rem, 5vw, 3.75rem);  /* DM Serif Display */
+--text-h2:      clamp(1.8rem, 3.5vw, 3rem);   /* DM Serif / Inter */
+--text-h3:      1.25rem – 1.5rem;             /* Inter 500 */
+--text-body:    0.95rem – 1.05rem;            /* Inter 400 */
+--text-small:   0.8rem – 0.88rem;             /* Inter */
+--text-label:   0.7rem – 0.75rem;             /* DM Mono + uppercase */
+
+/* Zeilenhöhe */
+line-height: 1.75;   /* Fließtext */
+line-height: 1.1;    /* Headlines */
+
+/* Letter-Spacing Labels */
+letter-spacing: 0.18em – 0.24em; text-transform: uppercase;
+
+ +
+

Fontrollen

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
RolleFontVerwendung in sims
Display / H1DM Serif DisplaySeitenüberschriften, Modul-Titel
Body / UI (dominant)InterFließtext, Nav, Labels, Formulare
Code / MonoDM MonoArtikelnr., Mengen, Beträge, Badges
+
+
+
+ + +
+ +

Button-System

+

+ Buttons haben immer border-radius: 999px (Pill-Form). + Keine eckigen Buttons. Drei Ebenen: Primary, Outline, Ghost. + Zusätzlich: Highlight für Hauptaktionen und Danger für destruktive Operationen. +

+ +
+

Primary

+
+ + + +
+
+ +
+

Highlight (Teal 500)

+
+ + + +
+
+ +
+

Outline

+
+ + + +
+
+ +
+

Ghost & Danger

+
+ + + + +
+
+ +
+

CSS

+
/* Basis aller Buttons — nie ohne border-radius: 999px */
+.btn {
+  border-radius: var(--radius-pill);     /* PFLICHT */
+  font-family:   var(--sans);
+  font-size:     0.9rem;
+  font-weight:   500;
+  padding:       0.65rem 1.5rem;
+  transition:    background 0.18s, transform 0.15s;
+}
+.btn:hover { transform: translateY(-1px); }
+
+/* Primary */
+.btn-primary {
+  background: var(--accent-primary);
+  color:      white;
+}
+/* Highlight */
+.btn-highlight {
+  background: var(--accent-highlight);
+  color:      white;
+}
+/* Outline */
+.btn-outline {
+  background: white;
+  border:     1px solid var(--border);
+  color:      var(--ink-2);
+}
+/* Danger */
+.btn-danger {
+  background: var(--status-empty-bg);
+  color:      var(--status-empty);
+  border:     1px solid #fecaca;
+}
+
+
+ + +
+ +

Status-Badges

+

+ Badges verwenden immer DM Mono. Im SIM-System sind Bestandsstatus-Badges + besonders wichtig — Teal (OK), Amber (Niedrig), Rot (Leer). + Alle Badges haben border-radius: 999px. +

+ +
+

Bestandsstatus

+
+ In Bestand + Niedrig + Leer +
+
+ +
+

Dokument-Status

+
+ Bezahlt + Offen + Überfällig + Lieferschein + Rechnung +
+
+ +
+

Allgemein

+
+ Lagerverwaltung + Inventur + ART-00124 + 48 Stk. + Min-Bestand +
+
+ +
+

CSS

+
.badge {
+  font-family:   var(--mono);   /* PFLICHT */
+  font-size:     0.73rem;
+  padding:       0.28rem 0.75rem;
+  border-radius: var(--radius-pill);
+}
+/* Dot-Indikator */
+.badge-dot::before {
+  content:  '';
+  width:    6px; height: 6px;
+  border-radius: 50%;
+  background:    currentColor;
+  display:       inline-block;
+  margin-right:  0.35rem;
+}
+/* Status */
+.badge-ok    { background: var(--status-ok-bg);    color: #0f766e; border: 1px solid rgba(20,184,166,0.25); }
+.badge-low   { background: var(--status-low-bg);   color: #92400e; border: 1px solid rgba(245,158,11,0.25); }
+.badge-empty { background: var(--status-empty-bg); color: #b91c1c; border: 1px solid rgba(239,68,68,0.25); }
+
+
+ + +
+ +

Datentabellen

+

+ Tabellen sind das Herzstück von sims. Responsive (horizontal scrollbar auf Mobile), + nie versteckt. Zeilenhover mit --accent-light. + Header in DM Mono uppercase. Artikelnummern und Mengen in DM Mono. +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Artikel-Nr.BezeichnungBestandMin-BestandPreisStatus
ART-00124Schrauben M8 × 40mm482 Stk.100 Stk.€0,12In Bestand
ART-00087Dichtungsring 12mm38 Stk.50 Stk.€0,45Niedrig
ART-00201Hydraulikschlauch 1/2"0 Stk.10 Stk.€14,90Leer
ART-00056Lagergehäuse SKF 620512 Stk.5 Stk.€8,70In Bestand
+
+ +
+

CSS

+
/* Wrapper — Responsive Scroll */
+.table-wrapper {
+  overflow-x:    auto;
+  border:        1px solid var(--border);
+  border-radius: var(--radius-lg);
+}
+thead {
+  background: var(--bg-subtle);
+}
+th {
+  font-family:    var(--mono);
+  font-size:      0.7rem;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color:          var(--ink-3);
+}
+/* Hover — Teal Highlight */
+tr:hover td { background: var(--accent-light); }
+
+/* Mono-Werte (Artikelnr., Mengen) */
+.td-mono { font-family: var(--mono); font-size: 0.82rem; }
+
+
+ + +
+ +

Form-Elemente

+

+ Labels immer in DM Mono uppercase. Focus-Outline in Teal Highlight. + Inputs mit border-radius: var(--radius-lg). + Mengenfelder und Artikelnummern-Inputs mit font-family: var(--mono). +

+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ +
+

Schnellsuche (prominent platzieren)

+
+ + +
+
+ +
+

CSS

+
label {
+  font-family:    var(--mono);
+  font-size:      0.72rem;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color:          var(--ink-3);
+}
+input, select, textarea {
+  border-radius: var(--radius-lg);
+  border:        1px solid var(--border);
+  font-family:   var(--sans);
+  font-size:     0.93rem;
+}
+input:focus, select:focus {
+  border-color: var(--accent-highlight);
+  box-shadow:   0 0 0 3px rgba(20,184,166,0.15);
+  outline:      none;
+}
+/* Mono-Inputs für Artikelnr. und Mengen */
+.input-mono { font-family: var(--mono); }
+
+
+ + + + + +
+ +

Card-Komponenten

+

+ Cards haben border-radius: var(--radius-xl) (2rem). + Hover: translateY(-4px) + verstärkter Shadow. + Card-Top-Bar als Teal-Gradient für Produkt-Identität. +

+ +
+
+
+
+
+ Artikel +
+

Schrauben M8 × 40mm

+

ART-00124 · 482 Stk. · €0,12/Stk.

+
+
+
+
+
+
+ Niedrig +
+

Dichtungsring 12mm

+

ART-00087 · 38 Stk. · Min: 50 Stk.

+
+
+
+
+

Quick Stats

+

247

+

Aktive Artikel gesamt

+
+
+
+ +
+
+
+
+ +
+ OK +
+

247

+

Artikel in Bestand

+
+
+
+
+ +
+ ! +
+

12

+

Unter Mindestbestand

+
+
+
+
+ +
+ 0 +
+

5

+

Leer / nicht vorrätig

+
+
+
+
+ +
+ Neu +
+

18

+

Offene Lieferscheine

+
+
+
+ + +
+ +

Lieferschein & Rechnung

+

+ Dokument-Vorschau im App-Kontext. Dunkler Teal-Header mit Dokumenttyp in + DM Serif Display, Nummer in DM Mono Teal-Highlight. Footer mit Gesamtbetrag. +

+ +
+
+
+ +
Lieferschein
+
+
+
LS-2024-0042
+
17. März 2024
+
Geliefert
+
+
+
+
+
+

Kunde

+

Musterfirma GmbH

+

Musterstraße 12, 6020 Innsbruck

+
+
+

Lieferdatum

+

17.03.2024

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
Pos.Artikel-Nr.BezeichnungMengeE-PreisGesamt
1ART-00124Schrauben M8 × 40mm200 Stk.€0,12€24,00
2ART-00056Lagergehäuse SKF 62054 Stk.€8,70€34,80
+
+
+ +
+ +
+
+
+ +
Rechnung
+
+
+
RE-2024-0017
+
17. März 2024 · Fällig: 31. März 2024
+
Offen
+
+
+ +
+
+ + +
+ +

Raster & Abstände

+

+ Max-Width 72rem. Section-Padding 5rem Desktop / 3rem Mobile. + Spacing-Skala in 4px-Schritten. +

+ +
+

Spacing-Skala

+
+
4px — Intra-Element (Icon → Text)
+
8px — Tags, Badges
+
12px — Kompakte Abstände
+
16px — Standard-Gap
+
24px — Zwischen Gruppen
+
32px — Zwischen Sektionen
+
48px — Zwischen großen Blöcken
+
80px — Zwischen Sections (Desktop)
+
+
+ +
+

Border-Radius

+
+
+
+

999px
Buttons/Badges

+
+
+
+

0.5rem
Tags / Code

+
+
+
+

1rem
Kleine Cards

+
+
+
+

1.5rem
Inputs / Tables

+
+
+
+

2rem
Cards / Demos

+
+
+
+
+ + +
+ +

Motion-Regeln

+

+ Animationen dienen der Orientierung, nicht der Unterhaltung. + Max. 0.7s für UI-Feedback. Kein Parallax, kein Bounce. +

+ +
+

Erlaubte Animationen

+
/* Page Load — Hero-Elemente mit Stagger */
+@keyframes fadeUp {
+  from { opacity: 0; transform: translateY(18px); }
+  to   { opacity: 1; transform: translateY(0); }
+}
+/* Stagger: je 0.05s–0.15s zwischen Elementen */
+.item-1 { animation: fadeUp 0.5s ease both; }
+.item-2 { animation: fadeUp 0.5s ease 0.08s both; }
+.item-3 { animation: fadeUp 0.5s ease 0.16s both; }
+
+/* Hover — Cards */
+.card { transition: transform 0.25s ease, box-shadow 0.25s ease; }
+.card:hover { transform: translateY(-4px); }
+
+/* Hover — Buttons */
+.btn { transition: background 0.18s, transform 0.15s; }
+.btn:hover { transform: translateY(-1px); }
+
+/* Verboten */
+/* ❌ animation-duration > 0.7s für UI-Feedback */
+/* ❌ Bounce / Elastic-Easing */
+/* ❌ Scroll-Parallax */
+/* ❌ Animationen auf Text-Paragraphen */
+
+
+ + +
+ +

Do & Don't

+

+ Verbindliche Regeln für alle Komponenten und Seiten unter sims.abler.tirol. + Gelten genauso für alle anderen Produkte im abler.tirol Ökosystem. +

+ +
+
+
✓ Do
+
    +
  • Fonts immer von api.abler.tirol/fonts/ laden
  • +
  • Buttons immer Pill-Form (border-radius: 999px)
  • +
  • CSS-Variablen für alle Farbwerte verwenden
  • +
  • DM Mono für Artikelnummern, Mengen, Beträge
  • +
  • Teal als einzige Akzentfarbe (+ Neutrals)
  • +
  • Tabellen auf Mobile horizontal scrollbar machen
  • +
  • Status-Badges: Teal OK · Amber Niedrig · Rot Leer
  • +
  • Labels: DM Mono + uppercase + letter-spacing
  • +
  • Hover-State bei Cards: translateY(-4px) + Shadow
  • +
  • Focus-Outline: 3px Teal Ring (rgba(20,184,166,0.15))
  • +
  • Max. 2 Akzentfarben gleichzeitig sichtbar
  • +
  • Schnellsuche prominent platzieren
  • +
+
+
+
✗ Don't
+
    +
  • Nie Google Fonts einbinden
  • +
  • Keine eckigen Buttons
  • +
  • Kein #000000 als Textfarbe — immer --ink
  • +
  • Keine hardcodierten Farbwerte in HTML/Templates
  • +
  • Kein lila/violetter Gradient auf weißem Hintergrund
  • +
  • Keine System-Fonts (Arial, Roboto, system-ui)
  • +
  • Kein reines Weiß (#fff) als Hintergrund — --bg-light
  • +
  • Keine animation-duration > 0.7s für UI-Feedback
  • +
  • Kein Scroll-Hijacking / Parallax
  • +
  • Kein Bounce- oder Elastic-Easing
  • +
  • Nie mehr als 5 Navigationspunkte
  • +
  • Keine dunklen Muster / irreführende CTAs
  • +
+
+
+ +
+

Zweisprachigkeit DE/EN (außer App-Kern)

+
<body class="de">
+
+<p data-lang="de">Lagerverwaltung</p>
+<p data-lang="en">Inventory Management</p>
+
+/* CSS */
+[data-lang] { display: none !important; }
+body.de [data-lang="de"] { display: revert !important; }
+body.en [data-lang="en"] { display: revert !important; }
+
+/* JS Toggle */
+function setLang(lang) {
+  document.body.className = lang;
+  document.documentElement.lang = lang;
+}
+
+ +
+

Zweisprachigkeit — sims Ausnahme

+
+
Hinweis sims
+
    +
  • Öffentliche Landing-Page: zweisprachig DE/EN mit Toggle
  • +
  • App-Kern (Dashboard, Tabellen, Formulare): nur DE — gemäß Design-Guide Ausnahme für sims/klara
  • +
  • Fehlermeldungen, Statusmeldungen: DE
  • +
+
+
+
+ +
+ + +
+ sims.abler.tirol · Design Guide · Simon Abler · abler.tirol · 2026 +
+ + + + + \ No newline at end of file diff --git a/design-guide.md b/design-guide.md new file mode 100644 index 0000000..11a4205 --- /dev/null +++ b/design-guide.md @@ -0,0 +1,1147 @@ +# sims.abler.tirol — Design Guide + +> Dieses Dokument definiert das Design-System für `sims.abler.tirol` (SIMSystem). +> Es leitet sich aus dem ökosystemweiten [abler.tirol Design Guide](https://github.com/simonabler/abler-tirol) ab und konkretisiert es für eine datenintensive, **mobile-first App-Oberfläche**. + +--- + +## 1. Produktcharakter + +**sims** ist ein internes Inventur- und Rechnungssystem. Die UI ist primär eine **App-Shell** – kein Marketing, keine Hero-Sections. Der Charakter ist: + +- **Sachlich und direkt** – keine Dekoration, keine Erklärungstexte wo keine nötig sind +- **Datenorientiert** – Tabellen, Listen und Formulare sind die Hauptelemente +- **Vertrauenswürdig** – klare Zustände, keine dunklen Muster, keine irreführenden Aktionen +- **Offen und aufgeräumt** – viel Weißraum, klare Hierarchie, kein visuelles Rauschen +- **Mobile-First** – die App wird primär auf dem Handy bedient; jede Komponente beginnt mit dem Mobile-Layout und wird erst danach für Desktop erweitert + +Verglichen mit anderen abler.tirol-Produkten: näher an `klara` (ruhig, funktional) als an `api` (technisch-dramatisch). + +--- + +## 2. Farbpalette + +### Neutrals (geteilt mit allen abler.tirol-Produkten) + +```css +--white: #ffffff; +--bg-light: #f7f7f4; /* App-Hintergrund */ +--bg-subtle: #fbfbf9; /* Cards, Sidebar, Table-Header */ +--ink: #0f172a; /* Haupttext, dunkle Buttons */ +--ink-2: #475569; /* Body-Text, Descriptions, Labels */ +--ink-3: #94a3b8; /* Placeholders, Muted, Disabled */ +--border: #e2e8f0; /* Alle Borders, Divider, Table-Lines */ +--border-sub: rgba(226, 232, 240, 0.7); +``` + +### sims-Akzentfarben + +sims bekommt eine **blau-schieferfarbene** Identität – ruhig und professionell, nicht zu technisch: + +```css +--accent-primary: #1e3a5f; /* Deep Blue-Slate – Navbar, aktive Nav, CTAs */ +--accent-secondary: #2d5282; /* Blue 800 – Hover-Zustände */ +--accent-highlight: #3b82f6; /* Blue 500 – Focus-Ringe, aktive Badges, Links */ +--accent-light: #eff6ff; /* Blue 50 – subtile Hintergründe, selected rows */ +--accent-muted: #dbeafe; /* Blue 100 – Badge-Hintergründe */ +``` + +### Status-Farben + +Status-Farben werden **ausschließlich für Zustände** verwendet (nie als Akzent oder Dekoration): + +```css +/* Erfolg */ +--status-success: #059669; /* Emerald 600 */ +--status-success-light: #ecfdf5; /* Emerald 50 */ +--status-success-border: #6ee7b7; /* Emerald 300 */ + +/* Warnung */ +--status-warning: #d97706; /* Amber 600 */ +--status-warning-light: #fffbeb; /* Amber 50 */ +--status-warning-border: #fcd34d; /* Amber 300 */ + +/* Fehler */ +--status-error: #dc2626; /* Red 600 */ +--status-error-light: #fef2f2; /* Red 50 */ +--status-error-border: #fca5a5; /* Red 300 */ + +/* Neutral / Entwurf */ +--status-neutral: #64748b; /* Slate 500 */ +--status-neutral-light: #f1f5f9; /* Slate 100 */ +``` + +--- + +## 3. Typografie + +### Schriftfamilien + +Fonts werden **vom eigenen Backend** ausgeliefert (`apps/server`). Das macht sims vollständig standalone – keine Abhängigkeit zu externen Diensten, kein Google Fonts, kein `api.abler.tirol` als Voraussetzung. + +Das Backend stellt einen statischen Font-Endpunkt bereit: + +``` +GET /fonts/abler-stack.css → kombiniertes CSS mit allen @font-face-Regeln +GET /fonts/files/inter-variable.woff2 +GET /fonts/files/dm-serif-display-400.woff2 +GET /fonts/files/dm-serif-display-400-italic.woff2 +GET /fonts/files/dm-mono-400.woff2 +GET /fonts/files/dm-mono-500.woff2 +``` + +**Einbindung in `index.html`:** + +```html + + + +``` + +> In der Angular `environment.ts` kann der Font-Pfad konfiguriert werden, falls das Backend auf einem anderen Port oder Host läuft (z. B. `http://localhost:3000/fonts/abler-stack.css` im Development). + +**`abler-stack.css` (wird im Backend als statische Datei abgelegt):** + +```css +@font-face { + font-family: 'DM Serif Display'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('/api/fonts/files/dm-serif-display-400.woff2') format('woff2'); +} +@font-face { + font-family: 'DM Serif Display'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('/api/fonts/files/dm-serif-display-400-italic.woff2') format('woff2'); +} +@font-face { + font-family: 'Inter'; + font-weight: 300 600; + font-display: swap; + src: url('/api/fonts/files/inter-variable.woff2') format('woff2'); +} +@font-face { + font-family: 'DM Mono'; + font-weight: 400; + font-display: swap; + src: url('/api/fonts/files/dm-mono-400.woff2') format('woff2'); +} +@font-face { + font-family: 'DM Mono'; + font-weight: 500; + font-display: swap; + src: url('/api/fonts/files/dm-mono-500.woff2') format('woff2'); +} +``` + +> `font-display: swap` ist Pflicht – verhindert unsichtbaren Text während des Ladens. + +### Verwendung + +| Rolle | Familie | Verwendung in sims | +|---|---|---| +| **Headlines** | `DM Serif Display` | Seitentitel (H1), leere Zustände | +| **Interface** | `Inter` | Alles andere: Labels, Body, Tabellen, Inputs | +| **Mono** | `DM Mono` | Nummern (Rechnungs-Nr., Artikel-Nr.), Badges, Status-Tags | + +### Gewichtungen + +```css +font-weight: 300 /* Fließtext, Descriptions */ +font-weight: 400 /* Standard Labels, Table-Body */ +font-weight: 500 /* Button-Text, Table-Header, Nav-Items */ +font-weight: 600 /* Seitentitel H2, wichtige Werte */ +``` + +### Größenraster + +Mobile-First: die Basisgrößen gelten für Mobile (`< 768px`), Desktop-Overrides kommen via Media Query. + +```css +/* Seitentitel */ +--text-page-title: 1.4rem; /* Mobile – DM Serif Display */ +/* @media (min-width: 768px): 1.75rem */ + +/* Section-Titel */ +--text-section: 1rem; /* Mobile */ +/* @media (min-width: 768px): 1.1rem */ + +/* Body */ +--text-body: 1rem; /* 16px – Mobile, kein Zoomen beim Focus auf iOS */ +--text-sm: 0.9375rem; /* 15px – Tabellen, kompakte Listen */ +--text-xs: 0.875rem; /* 14px – Beschriftungen, Hints */ + +/* Label / Badge */ +--text-label: 0.75rem; /* 12px – DM Mono, uppercase */ +--text-micro: 0.6875rem; /* 11px – Status-Badges, Tags */ +``` + +> **Wichtig:** Font-Size auf Inputs niemals unter `16px` auf Mobile setzen – sonst zoomt iOS beim Fokus automatisch rein. + +### Typo-Regeln + +- Seitentitel nutzen `DM Serif Display`, alle anderen Texte `Inter` +- Rechnungs- und Artikelnummern **immer** in `DM Mono` +- Labels und Badges **immer** `DM Mono`, `text-transform: uppercase`, `letter-spacing: 0.12em` +- Keine System-Fonts, kein `Arial`, kein `Roboto` +- `line-height: 1.6` für Body, `1.1` für Titles + +--- + +## 4. Layout & App-Shell + +sims nutzt **Bootstrap 5** als Grid- und Utility-Grundlage. Bootstrap wird **nicht** für fertige Komponenten-Styles verwendet – es liefert ausschließlich das responsive Grid (`container`, `row`, `col-*`), Flexbox-Utilities und Spacing-Utilities. Alle visuellen Stile kommen aus den sims-eigenen CSS-Custom-Properties. + +### Bootstrap-Einbindung + +```bash +npm install --save bootstrap +``` + +```scss +// styles.scss – nur das Grid und Utilities importieren, kein vollständiges Bootstrap +@import 'bootstrap/scss/functions'; +@import 'bootstrap/scss/variables'; +@import 'bootstrap/scss/mixins'; +@import 'bootstrap/scss/grid'; +@import 'bootstrap/scss/utilities'; +@import 'bootstrap/scss/helpers'; +``` + +Bootstrap-Farbvariablen (`$primary`, `$secondary`, etc.) werden **nicht** verwendet – alle Farben kommen aus den sims CSS Custom Properties. + +### Breakpoints + +Bootstrap-Breakpoints werden übernommen und sind die einzigen erlaubten Breakpoints: + +``` +xs: < 576px → kleines Handy (Basis, kein Suffix) +sm: ≥ 576px → großes Handy / kleines Tablet +md: ≥ 768px → Tablet / großes Handy quer +lg: ≥ 992px → kleines Desktop +xl: ≥ 1200px → Desktop +``` + +### App-Shell: Mobile (< 768px) + +Auf Mobile gibt es **keine Sidebar**. Die Navigation ist eine **Bottom Navigation Bar** – fest am unteren Bildschirmrand, wie bei nativen Apps. + +``` +┌─────────────────────────────────┐ +│ Top Bar (fixed, 56px) │ +│ [≡] SIMS [+ NEU] │ +├─────────────────────────────────┤ +│ │ +│ Page Content │ +│ (scrollable) │ +│ │ +│ │ +├─────────────────────────────────┤ +│ Bottom Nav (fixed, 60px) │ +│ [Artikel][Kunden][Belege][···] │ +└─────────────────────────────────┘ +``` + +**Top Bar (Mobile):** +```css +position: fixed; +top: 0; left: 0; right: 0; +height: 56px; +background: var(--accent-primary); +color: white; +z-index: 100; +display: flex; +align-items: center; +justify-content: space-between; +padding: 0 1rem; +``` + +- Links: Hamburger-Icon öffnet Drawer für alle Unterseiten +- Mitte: "SIMS" in DM Mono, uppercase, letter-spacing 0.2em +- Rechts: Kontext-abhängige primäre Aktion (z. B. "+ Artikel") + +**Bottom Navigation (Mobile):** +```css +position: fixed; +bottom: 0; left: 0; right: 0; +height: 60px; +background: white; +border-top: 1px solid var(--border); +z-index: 100; +display: flex; +align-items: center; +justify-content: space-around; +padding-bottom: env(safe-area-inset-bottom); +``` + +- Max. 4 Einträge: Artikel · Kunden · Belege · Mehr (öffnet Drawer) +- Aktiv: `color: var(--accent-highlight)`, kleiner Label darunter in DM Mono +- Inaktiv: `color: var(--ink-3)` +- Icon-Größe: 22px, Label: 10px DM Mono uppercase +- Tap-Target mindestens 44×44px pro Item + +**Page Content (Mobile):** +```css +padding-top: 56px; /* Top Bar Höhe */ +padding-bottom: calc(60px + env(safe-area-inset-bottom)); +padding-left: 1rem; +padding-right: 1rem; +``` + +### App-Shell: Desktop (≥ 768px) + +Ab Tablet-Breite erscheint die klassische **Sidebar** links. + +``` +┌──────────────────────────────────────────────┐ +│ Sidebar (240px, fixed) │ Main Content │ +│ │ │ +│ SIMS │ Page Header │ +│ ─────────────── │ ──────────────── │ +│ Nav Links │ Content │ +│ │ │ +└──────────────────────────────────────────────┘ +``` + +**Sidebar (Desktop):** +```css +width: 240px; +background: var(--accent-primary); +color: white; +position: fixed; +height: 100vh; +top: 0; left: 0; +z-index: 50; +``` + +- Logo oben: `DM Mono`, `uppercase`, `letter-spacing: 0.2em`, weiß +- Nav-Items: `Inter 400`, 0.875rem, weiß `opacity: 0.7` inaktiv +- Aktiv: `opacity: 1`, weißer linker Border (3px), `rgba(255,255,255,0.1)` Hintergrund +- Nav-Section-Labels: DM Mono, 0.6rem, uppercase, `opacity: 0.35` + +**Main Content (Desktop):** +```css +margin-left: 240px; +padding: 2rem 2.5rem; +background: var(--bg-light); +min-height: 100vh; +``` + +### Page Header + +**Mobile:** Nur der Seitentitel. Der CTA ist in der Top Bar rechts. +**Desktop:** Seitentitel links, CTA-Button rechts, Eyebrow-Label darüber. + +``` +Desktop: +[Eyebrow (DM Mono, muted)] +[Page Title (DM Serif, 1.75rem)] [Primary Action Button] +──────────────────────────────────────────────────────────── +``` + +### Spacing-Skala + +```css +--space-1: 0.25rem; /* 4px */ +--space-2: 0.5rem; /* 8px */ +--space-3: 0.75rem; /* 12px */ +--space-4: 1rem; /* 16px */ +--space-6: 1.5rem; /* 24px */ +--space-8: 2rem; /* 32px */ +--space-12: 3rem; /* 48px */ +``` + +--- + +## 5. Komponenten + +### Touch-Targets + +**Alle interaktiven Elemente auf Mobile müssen mindestens 44×44px groß sein** (Apple HIG / WCAG 2.5.5): + +```css +.touch-target { + min-height: 44px; + min-width: 44px; + display: flex; + align-items: center; + justify-content: center; +} +``` + +### Buttons + +Alle Buttons haben `border-radius: 999px` (Pill-Form). Auf Mobile sind primäre Buttons voller Breite. + +```css +/* Primary */ +.btn-sims-primary { + background: var(--accent-primary); + color: white; + border: none; + border-radius: 999px; + padding: 0.75rem 1.5rem; + font-family: 'Inter', sans-serif; + font-size: 1rem; + font-weight: 500; + min-height: 48px; + width: 100%; + transition: background 0.18s; +} +@media (min-width: 768px) { + .btn-sims-primary { width: auto; padding: 0.55rem 1.25rem; font-size: 0.875rem; } + .btn-sims-primary:hover { background: var(--accent-secondary); transform: translateY(-1px); } +} + +/* Outline */ +.btn-sims-outline { + background: white; + color: var(--ink-2); + border: 1px solid var(--border); + border-radius: 999px; + padding: 0.7rem 1.5rem; + font-family: 'Inter', sans-serif; + font-size: 1rem; + min-height: 48px; + width: 100%; +} +@media (min-width: 768px) { + .btn-sims-outline { width: auto; font-size: 0.875rem; padding: 0.55rem 1.25rem; } +} + +/* Ghost / Danger */ +.btn-sims-ghost { background: none; border: none; color: var(--ink-2); min-height: 44px; border-radius: 999px; } +.btn-sims-danger { background: var(--status-error); color: white; border: none; border-radius: 999px; min-height: 48px; } + +/* Icon-Button */ +.btn-sims-icon { + background: none; + border: none; + min-width: 44px; + min-height: 44px; + display: flex; + align-items: center; + justify-content: center; + color: var(--ink-3); + border-radius: 50%; +} +@media (hover: hover) { + .btn-sims-icon:hover { background: var(--bg-subtle); color: var(--ink-2); } +} +``` + +### Cards + +```css +.sims-card { + background: white; + border: 1px solid var(--border); + border-radius: 1.25rem; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06); + padding: 1.25rem; +} +@media (min-width: 768px) { + .sims-card { border-radius: 1.5rem; padding: 1.5rem; } +} + +.sims-card-table { + border-radius: 1.25rem; + overflow: hidden; + padding: 0; + background: white; + border: 1px solid var(--border); +} +``` + +### Tabellen – Mobile Adaptation + +Auf Mobile werden Tabellenzeilen als **gestapelte Cards** dargestellt. + +**Strategie 1 – Card-List (Mobile-Standard, < 768px):** + +```css +@media (max-width: 767px) { + .table-responsive-cards thead { display: none; } + .table-responsive-cards tbody tr { + display: block; + background: white; + border: 1px solid var(--border); + border-radius: 1rem; + margin-bottom: 0.75rem; + padding: 1rem; + } + .table-responsive-cards tbody td { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.3rem 0; + border: none; + font-size: 0.9375rem; + } + .table-responsive-cards tbody td::before { + content: attr(data-label); + font-family: 'DM Mono', monospace; + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--ink-3); + flex-shrink: 0; + margin-right: 1rem; + } +} +``` + +Jede `` braucht ein `data-label`-Attribut: +```html +ART-00142 +340 +``` + +Die gesamte Card-Zeile muss per Tap navigierbar sein (kein kleiner Detail-Button nötig). + +**Strategie 2 – Horizontal Scroll** (für sehr schmale Tabellen): +```css +.table-scroll-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + border-radius: 1.25rem; +} +``` + +**Desktop (≥ 768px):** Normales Tabellenlayout. + +```css +@media (min-width: 768px) { + table { width: 100%; border-collapse: collapse; } + thead th { + background: var(--bg-subtle); + color: var(--ink-3); + font-family: 'DM Mono', monospace; + font-size: 0.6rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.1em; + padding: 0.75rem 1rem; + text-align: left; + border-bottom: 1px solid var(--border); + } + tbody tr { border-bottom: 1px solid var(--border-sub); } + tbody tr:hover { background: var(--accent-light); } + tbody td { padding: 0.75rem 1rem; color: var(--ink-2); font-size: 0.875rem; vertical-align: middle; } + td.numeric { font-family: 'DM Mono', monospace; text-align: right; color: var(--ink); } +} +``` + +### Status-Badges + +```css +.sims-badge { + display: inline-flex; + align-items: center; + font-family: 'DM Mono', monospace; + font-size: 0.6rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.08em; + padding: 0.25rem 0.7rem; + border-radius: 999px; + white-space: nowrap; +} +.sims-badge-success { background: var(--status-success-light); color: var(--status-success); border: 1px solid var(--status-success-border); } +.sims-badge-warning { background: var(--status-warning-light); color: var(--status-warning); border: 1px solid var(--status-warning-border); } +.sims-badge-error { background: var(--status-error-light); color: var(--status-error); border: 1px solid var(--status-error-border); } +.sims-badge-neutral { background: var(--status-neutral-light); color: var(--status-neutral); border: 1px solid var(--border); } +.sims-badge-accent { background: var(--accent-muted); color: var(--accent-primary); border: 1px solid var(--accent-light); } +``` + +### Formularelemente + +Inputs auf Mobile haben `font-size: 1rem` – verhindert Auto-Zoom auf iOS. + +```css +.sims-input, +.sims-select, +.sims-textarea { + width: 100%; + border: 1px solid var(--border); + border-radius: 0.75rem; + padding: 0.75rem 1rem; + font-family: 'Inter', sans-serif; + font-size: 1rem; /* PFLICHT: niemals < 16px auf Mobile */ + color: var(--ink); + background: white; + transition: border-color 0.15s, box-shadow 0.15s; + min-height: 48px; + -webkit-appearance: none; + appearance: none; +} +.sims-input:focus, +.sims-select:focus, +.sims-textarea:focus { + outline: none; + border-color: var(--accent-highlight); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); +} +.sims-input::placeholder { color: var(--ink-3); } + +.sims-label { + display: block; + font-family: 'Inter', sans-serif; + font-size: 0.875rem; + font-weight: 500; + color: var(--ink-2); + margin-bottom: 0.4rem; +} +``` + +### Leere Zustände (Empty States) + +``` +[Zentriert, padding: 3rem 1.5rem] + +[Icon, 32px, color: var(--ink-3)] +[Titel: DM Serif Display, 1.25rem] +[Beschreibung: Inter 300, 0.9rem, --ink-3] +[CTA: .btn-sims-primary (volle Breite Mobile, auto Desktop)] +``` + +### Modals / Dialogs + +- **Desktop:** zentriertes Modal, `border-radius: 1.5rem`, `max-width: 32rem` +- **Mobile: Bottom Sheet** – gleitet von unten rein, `border-radius: 1.5rem 1.5rem 0 0`, volle Breite + +```css +.sims-modal-backdrop { + position: fixed; inset: 0; + background: rgba(15, 23, 42, 0.45); + z-index: 200; + display: flex; align-items: center; justify-content: center; +} + +.sims-modal { + background: white; + border-radius: 1.5rem; + padding: 1.5rem; + width: 100%; + max-width: 32rem; +} + +@media (max-width: 767px) { + .sims-modal-backdrop { align-items: flex-end; } + .sims-modal { + border-radius: 1.5rem 1.5rem 0 0; + max-width: 100%; + padding-bottom: calc(1.5rem + env(safe-area-inset-bottom)); + max-height: 90vh; + overflow-y: auto; + } +} +``` + +--- + +## 6. Angular-spezifische Konventionen + +### CSS-Architektur + +``` +apps/sim-system/src/ +├── styles/ +│ ├── _variables.css ← Alle CSS Custom Properties +│ ├── _reset.css ← Minimaler Reset +│ ├── _typography.css ← Type-Regeln (keine @font-face, kommen vom Backend) +│ ├── _components.css ← sims-eigene Komponenten-Styles +│ ├── _layout.css ← Top Bar, Bottom Nav, Sidebar, Main-Content +│ └── _mobile.css ← Mobile-spezifische Overrides +└── styles.scss ← Bootstrap-Grid + alle partials +``` + +**`styles.scss`:** +```scss +// Bootstrap – nur Grid und Utilities +@import 'bootstrap/scss/functions'; +@import 'bootstrap/scss/variables'; +@import 'bootstrap/scss/mixins'; +@import 'bootstrap/scss/grid'; +@import 'bootstrap/scss/utilities'; + +// sims Design System +@import './styles/variables'; +@import './styles/reset'; +@import './styles/typography'; +@import './styles/layout'; +@import './styles/components'; +@import './styles/mobile'; +``` + +### Viewport Meta + +```html + +``` + +`viewport-fit=cover` ist nötig für Safe-Area-Support auf iPhones mit Notch / Dynamic Island. + +### Component-Styles + +- `ViewEncapsulation.Emulated` (Standard) für alle Komponenten +- Keine Bootstrap-Komponenten-Klassen in Templates (kein `.btn`, `.card`, `.navbar` von Bootstrap) +- Keine hardcodierten Hex-Werte – ausschließlich `var(--token-name)` +- Keine Inline-Styles in Templates + +--- + +## 7. Animationen + +Auf Mobile sind Animationen kürzer – sie wirken dort sonst als Latenz. + +```css +/* Route-Wechsel */ +@keyframes fadeUp { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +.page-enter { animation: fadeUp 0.18s ease forwards; } + +/* Bottom Sheet */ +@keyframes slideUp { + from { transform: translateY(100%); } + to { transform: translateY(0); } +} +.bottom-sheet-enter { animation: slideUp 0.22s ease forwards; } + +/* Hover – nur auf Pointer-Geräten */ +@media (hover: hover) { + .sims-card:hover { transform: translateY(-2px); transition: transform 0.2s ease, box-shadow 0.2s ease; } + .btn-sims-primary:hover { transform: translateY(-1px); transition: background 0.15s, transform 0.12s; } +} + +/* Skeleton Loading */ +@keyframes shimmer { + from { background-position: -200% 0; } + to { background-position: 200% 0; } +} +.skeleton { + background: linear-gradient(90deg, var(--bg-subtle) 25%, var(--border) 50%, var(--bg-subtle) 75%); + background-size: 200% 100%; + animation: shimmer 1.2s ease infinite; + border-radius: 0.5rem; +} +``` + +### Verboten + +- Scroll-basierte Parallax +- `animation-duration > 0.35s` für UI-Feedback +- Bounce- oder Elastic-Easing +- Hover-Effekte ohne `@media (hover: hover)` Guard +- Animationen auf Tabellenzellen oder reinem Text + +--- + +## 8. Mobile UX-Regeln + +### Allgemein +- Touch-Targets mindestens 44×44px für alle interaktiven Elemente +- Kein Hover-only-Feedback – alle Zustände brauchen eine Touch-Alternative +- Keine horizontalen Scrollbars auf Page-Ebene +- `@media (hover: hover)` für alle Hover-Effekte + +### Formulare +- Inputs immer `font-size: 1rem` (mindestens 16px) – kein iOS Auto-Zoom +- `inputmode`-Attribut setzen: `inputmode="numeric"` für Mengen/Preise, `inputmode="decimal"` für Dezimalzahlen +- Formular-Submit-Buttons immer am Ende, volle Breite, mindestens 48px hoch +- `autocomplete`-Attribute setzen wo sinnvoll + +### Navigation +- Bottom Navigation zeigt max. 4 Hauptbereiche +- Zurück-Navigation über Browser Back / iOS Swipe-Back +- Kein Hamburger-only: Bottom Nav ist immer sichtbar + +### Listen +- Tabellenzeilen als Card-List (Strategie 1) auf Mobile +- Primäre Aktion per Tap auf die gesamte Card – kein kleiner Detail-Button nötig +- Sekundäre Aktionen in Kontext-Menü oder Swipe-Action + +### Performance +- Lazy Loading für alle Routen (`loadChildren`) +- Icons als SVG, kein PNG für UI-Elemente +- `@supports (backdrop-filter: blur(1px))` vor `backdrop-filter`-Nutzung prüfen + +--- + +## 9. Seiten-Übersicht + +| Route | Seite | Mobile-Besonderheit | +|---|---|---| +| `/login` | Login | Vollbild, keine Navigation, CTA volle Breite | +| `/dashboard` | Dashboard | Stat-Cards einspaltig (Mobile), dreispaltig (Desktop) | +| `/articles` | Artikel | Card-List Mobile, Tabelle ab md | +| `/articles/:id` | Artikel-Detail | Formular einspaltig, Save-Button sticky bottom | +| `/customers` | Kunden | Card-List Mobile | +| `/customers/:id` | Kunden-Detail | Formular einspaltig | +| `/slipsheets` | Lieferscheine | Card-List, Status-Badge prominent | +| `/slipsheets/:id` | Lieferschein-Detail | Positionen als Card-List, Summe sticky bottom | +| `/bills` | Rechnungen | Card-List, Betrag prominent | +| `/bills/:id` | Rechnungs-Detail | PDF-Download als prominent platzierter CTA | +| `/cart` | Warenkorb / Bestellung | Step-by-Step, ein Schritt pro Screen auf Mobile | +| `/inventory` | Inventur | Primär-Mobile-Seite, Stepper-Eingabe, Barcode-Scan | +| `/inventory/:id` | Inventur-Session | Artikel-für-Artikel zählen, Fortschrittsanzeige | +| `/order/new` | Neue Bestellung | Mobile-optimierter Bestellerfassungs-Flow | + +--- + +## 10. Mobile-Primärseiten + +Diese zwei Seiten sind **Mobile-First im absoluten Sinne** – sie werden fast ausschließlich auf dem Handy verwendet und haben kein gleichwertiges Desktop-Pendant. Das Desktop-Layout ist eine vereinfachte Anpassung, nicht der Hauptfall. + +--- + +### 10.1 Inventur (`/inventory`) + +Die Inventur-Seite folgt einem **Scan-and-Count-Loop**: ein Artikel nach dem anderen. Kein Listenaufruf, keine Session mit allen Artikeln – der Nutzer scannt den Barcode, sieht die Artikelinfos, trägt den Ist-Bestand ein und bucht. Dann kommt der nächste Artikel. + +Das entspricht dem realen Lagerablauf: Handy in der Hand, Barcode-Scanner oder Kamera, Artikel für Artikel durch das Regal. + +#### Struktur + +``` +Top Bar (fixed) + [≡] INVENTUR [ ] + +Scan-Bereich (white card, border-bottom) + [Label: BARCODE SCANNEN ODER EINGEBEN] + [Barcode-Input (Pill, focus-blue)] [Kamera-Button (14px, accent-primary)] + +Artikel-Info-Card ← erscheint nach Scan/Eingabe + [● Artikelname] + [Artikel-Nr · Typ-Badge] + ───────────────────── + Lagerstand | 2.400 Blatt + Hersteller | Steinbeis + Barcode | 9002224100062 + Beschreibung| … + +Soll / Ist Card + [SOLL: 2.400 Bl] | [IST: − 2380 + Bl] + ───────────────────────────────────────── + [Differenz-Banner: − 20 Blatt] + +[Inventur buchen – primary, 54px] + +Section: "Zuletzt gebucht" + [Log-Einträge mit Diff-Badge] + +Bottom Navigation +``` + +#### Barcode-Eingabefeld + +Das Eingabefeld hat beim Öffnen der Seite sofort `autofocus` – Scannen ohne Tap. Die blaue Focus-Border und der Glow zeigen den aktiven Zustand dauerhaft: + +```css +.scan-input-wrap { + flex: 1; + display: flex; align-items: center; gap: 10px; + background: var(--bg-light); + border: 1.5px solid var(--accent-highlight); + border-radius: 14px; + padding: 0 14px; height: 54px; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} +/* Input darin: DM Mono, 1.05rem, letter-spacing: 0.04em */ +``` + +Der Kamera-Button rechts öffnet die Gerätekamera für QR/Barcode-Scan: + +```css +.scan-camera-btn { + width: 54px; height: 54px; + background: var(--accent-primary); + border: none; border-radius: 14px; + display: flex; align-items: center; justify-content: center; + color: white; flex-shrink: 0; +} +``` + +Nach einem Scan oder Enter im Textfeld: `GET /articles?code=` → Artikel-Info-Card erscheint, Ist-Feld erhält Fokus. + +#### Artikel-Info-Card + +Zeigt alle relevanten Felder aus dem alten Template als kompakte Tabelle: + +```css +.article-info-table th { + font-family: 'DM Mono', monospace; + font-size: 0.6rem; text-transform: uppercase; + letter-spacing: 0.1em; color: var(--ink-3); + padding: 7px 14px; white-space: nowrap; + width: 1%; /* fit-content */ +} +.article-info-table td { + font-size: 0.875rem; color: var(--ink-2); + padding: 7px 14px 7px 0; +} +``` + +Felder: **Lagerstand** (DM Mono, prominent), Hersteller, Barcode (DM Mono), Beschreibung. +Name und Artikel-Nr. stehen im Card-Header, nicht in der Tabelle. + +#### Soll / Ist Card + +Die zwei Werte stehen nebeneinander in einer geteilten Card – visuell sofort vergleichbar: + +```css +.count-card { border-radius: 16px; overflow: hidden; } + +.count-half { + flex: 1; padding: 12px 14px; + display: flex; flex-direction: column; gap: 4px; +} +.count-half:first-child { border-right: 1px solid var(--border); } + +/* Soll: read-only, muted */ +/* Ist: editierbar – DM Mono 1.4rem, accent-primary */ +``` + +**Ist-Eingabe:** Stepper (− / +) für kleine Anpassungen, direktes Tippen auf den Wert öffnet das nummerische Keyboard (`inputmode="numeric"`). Kein separates Soll-Input-Feld – Soll ist immer read-only und kommt vom Backend. + +#### Differenz-Banner + +Erscheint unterhalb der Soll/Ist-Card, sobald ein Ist-Wert eingegeben wurde: + +```css +.diff-banner { + border-top: 1px solid var(--border); + padding: 8px 14px; + display: flex; align-items: center; justify-content: space-between; +} +/* Farben je Zustand: */ +.diff-banner.negative { background: var(--status-error-light); } +.diff-banner.positive { background: var(--status-warning-light); } +.diff-banner.zero { background: var(--status-success-light); } +``` + +Wert in DM Mono, 1rem, mit Vorzeichen: `− 20 Blatt` / `+7 Blatt` / `±0`. + +#### "Inventur buchen"-Button + +```css +/* Deaktiviert wenn kein Artikel geladen */ +.inv-submit:disabled { opacity: 0.35; pointer-events: none; } +``` + +Nach erfolgreichem POST: Barcode-Feld leert sich, Artikel-Info-Card und Soll/Ist-Card verschwinden, Log-Eintrag erscheint oben in der "Zuletzt gebucht"-Liste, Barcode-Feld erhält wieder Fokus für den nächsten Scan. + +#### "Zuletzt gebucht"-Log + +Kompakte Zeilen mit Checkmark, Artikelname, Ist-Menge und Diff-Badge: + +```css +.log-item { + border-radius: 12px; padding: 10px 14px; + display: flex; align-items: center; gap: 10px; +} +.log-diff.ok { color: var(--status-success); font-family: 'DM Mono', monospace; } +.log-diff.neg { color: var(--status-error); font-family: 'DM Mono', monospace; } +.log-diff.pos { color: var(--status-warning); font-family: 'DM Mono', monospace; } +``` + +Max. 5 Einträge sichtbar (älteste fallen heraus). Der Log dient als visueller Kontext – "was habe ich in dieser Session schon gemacht". + +#### Angular-Implementierungshinweise + +```typescript +// Component-Struktur +export class InventoryComponent { + code = ''; // Barcode-Input, autofocus + Article: ArticleEntity | null = null; + shouldVal = 0; // read-only vom Backend + isVal = 0; // Eingabe + recentLog: LogEntry[] = []; + + onCodeChange() { + // GET /articles?code=this.code + // → this.Article setzen, this.shouldVal = Article.stock + // → isVal auf shouldVal vorbelegen (häufigster Fall: kein Fehler) + } + + onSubmit() { + // POST /articles/:id/inventory { is: this.isVal, inventoryDate: today } + // → Log-Eintrag prependen + // → Felder zurücksetzen, Fokus auf code-Input + } +} +``` + +**Wichtig:** `isVal` wird beim Laden eines Artikels auf `shouldVal` vorbelegt. Das spart Tipparbeit wenn der Bestand stimmt – der Nutzer muss nur korrigieren, nicht neu eingeben. + +--- + +### 10.2 Neue Bestellung (`/order/new`) + +Ermöglicht die schnelle Bestellaufnahme beim Kunden oder im Lager direkt vom Handy. Ersetzt den Desktop-`/cart`-Flow durch eine mobile-optimierte Variante. + +#### Struktur + +``` +Top Bar (fixed) + ← [zurück] NEUE BESTELLUNG / "Entwurf · N Positionen" [Verwerfen] + +Kunden-Strip (sticky unter Top Bar) + [Avatar-Initialen] [Kundenname] [Kundennummer] [Ändern →] + +Section: "Positionen" + [Bestell-Position] × N + [+ Artikel oder Textposition hinzufügen] (gestrichelter Button) + +[Notiz-Card] + +[Zusammenfassung: Netto / MwSt / Gesamt] + +Bottom Action Area (fixed bottom) + [Lieferschein erstellen – primary, 52px] + [Als Entwurf speichern – ghost] +``` + +#### Bestell-Position + +Zwei Typen, beide als Card mit gleichem Layout: + +**Artikel-Position:** +``` +Oberer Bereich: + [Artikelname] [Preis / Einheit] + [Artikel-Nr DM Mono] + +Unterer Bereich (bg-subtle): + [Löschen-Icon] [− Menge +] [Einheit] [Zeilensumme] +``` + +**Textposition** (für Pauschalleistungen, Porto etc.): +``` +Oberer Bereich: + [Freitext] [Preis] + [Badge: TEXTPOSITION] + +Unterer Bereich: + [Löschen-Icon] [1× Pauschal] [Preis] +``` + +```css +/* Artikel hinzufügen – gestrichelter Button */ +.add-position-btn { + width: 100%; + background: var(--accent-light); + color: var(--accent-primary); + border: 1.5px dashed var(--accent-muted); + border-radius: 14px; + height: 48px; + display: flex; align-items: center; justify-content: center; + gap: 8px; + font-family: 'Inter', sans-serif; + font-size: 0.875rem; font-weight: 500; + cursor: pointer; +} +``` + +Tap auf diesen Button öffnet ein **Bottom Sheet** mit: +- Suchfeld (Autofokus, `inputmode="search"`) +- Letzte/häufige Artikel (max. 5, aus localStorage) +- Toggle: "Artikel" / "Textposition" + +#### Kunden-Strip + +```css +.customer-strip { + background: white; + border-bottom: 1px solid var(--border); + padding: 12px 16px; + display: flex; align-items: center; gap: 10px; + position: sticky; + top: 56px; /* unter Top Bar */ + z-index: 10; +} + +.customer-avatar { + width: 36px; height: 36px; border-radius: 50%; + background: var(--accent-muted); + color: var(--accent-primary); + display: flex; align-items: center; justify-content: center; + font-family: 'DM Mono', monospace; + font-size: 0.7rem; font-weight: 500; + flex-shrink: 0; +} +``` + +Ist noch kein Kunde gewählt, zeigt der Strip einen `[Kunden wählen →]`-Button in accent-highlight. + +#### Zusammenfassung + +Zeigt Netto, MwSt und Gesamtbetrag. Werte werden live beim Ändern von Mengen aktualisiert. + +```css +.order-summary { + background: white; + border: 1px solid var(--border); + border-radius: 14px; + padding: 12px 14px; + margin: 16px 16px 0; +} +``` + +#### Bottom Action Area + +```css +.bottom-action-area { + position: sticky; + bottom: 0; + background: white; + border-top: 1px solid var(--border); + padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); + display: flex; flex-direction: column; gap: 8px; +} +``` + +- **Primär:** "Lieferschein erstellen" – `btn-sims-primary`, volle Breite, 52px Höhe +- **Sekundär:** "Als Entwurf speichern" – `btn-sims-ghost`, 36px, zentriert +- Ist die Bestellung noch unvollständig (kein Kunde, keine Positionen), ist der primäre Button deaktiviert mit `opacity: 0.4; pointer-events: none` + +#### Entwurf-Verwaltung + +- Beim Verlassen der Seite (Back-Button, "Verwerfen") erscheint ein **Bottom Sheet** als Bestätigung: "Entwurf speichern" / "Verwerfen" +- Entwürfe sind auf dem Dashboard als "Offene Entwürfe"-Badge sichtbar +- Max. 5 Entwürfe gleichzeitig (älteste wird automatisch gelöscht) + +#### Besonderheiten + +- Menge kann via Stepper oder Direkteingabe (Tap auf Wert → nummerisches Input-Field) geändert werden +- Preise werden vom Backend geladen (`GET /articles/:id` beim Hinzufügen) und lokal gecacht +- Gesamtberechnung erfolgt im Frontend (keine Round-Trip-Abhängigkeit) +- `inputmode="decimal"` für Preisfelder bei Textpositionen + +--- + +## 11. Was aktiv vermieden wird + +- ❌ Fonts von Google Fonts oder externen Diensten – ausschließlich vom eigenen Backend (`/api/fonts/`) +- ❌ Bootstrap-Komponenten-Klassen in Templates (kein `.btn`, `.card`, `.navbar` von Bootstrap) +- ❌ Eckige Buttons – immer `border-radius: 999px` +- ❌ Reines Schwarz (`#000`) – immer `var(--ink)` +- ❌ Hardcodierte Hex-Werte in Templates oder Component-CSS +- ❌ Lila/violette Gradienten +- ❌ Mehr als zwei gleichzeitig sichtbare Akzentfarben +- ❌ `animation-duration > 0.35s` für UI-Feedback +- ❌ Hover-Effekte ohne `@media (hover: hover)` Guard +- ❌ Font-Size unter 16px auf Input-Elementen +- ❌ Touch-Targets unter 44×44px +- ❌ Sidebar auf Mobile – Bottom Navigation ist der Standard +- ❌ Inline-Styles in Angular-Templates +- ❌ `!important` außer in zwingenden Reset-Fällen + +--- + +*Stand: 2026 · Simon Abler · sims.abler.tirol* \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..1fe84cc --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,56 @@ +services: + db: + image: postgres:17-alpine + container_name: sims-db-dev + env_file: + - ./conf/postgres/.env + volumes: + - sims-db-dev:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U user -d sims'] + interval: 10s + timeout: 10s + retries: 5 + ports: + - "5432:5432" + networks: + - sims-network + + backend: + build: + context: . + dockerfile: dockerfiles/Dockerfile.backend + container_name: sims-backend-dev + env_file: + - .env + volumes: + - sims-uploads:/app/uploads + environment: + UPLOAD_DIR: /app/uploads/avatars + depends_on: + db: + condition: service_healthy + ports: + - "3000:3000" + networks: + - sims-network + + frontend: + build: + context: . + dockerfile: dockerfiles/Dockerfile.frontend + container_name: sims-frontend-dev + ports: + - "80:80" + depends_on: + - backend + networks: + - sims-network + +networks: + sims-network: + driver: bridge + +volumes: + sims-uploads: + sims-db-dev: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a3b6eb2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,46 @@ +services: + frontend: + image: sims-frontend:latest + container_name: sims-frontend + restart: unless-stopped + environment: + # Absoluter Backend-URL fuer SSR HTTP-Calls (Node-seitig, kein Browser-Proxy verfuegbar) + BACKEND_URL: http://backend:3000 + labels: + - "traefik.enable=true" + - "traefik.http.routers.sims-frontend.tls=true" + - "traefik.http.routers.sims-frontend.tls.certresolver=letsEncrypt" + - "traefik.http.routers.sims-frontend.entrypoints=websecure" + - "traefik.http.routers.sims-frontend.rule=Host(`sims.abler.tirol`) && PathPrefix(`/`)" + - "traefik.http.routers.sims-frontend.priority=10" + - "traefik.http.routers.sims-frontend.middlewares=sims-frontend-compress" + - "traefik.http.middlewares.sims-frontend-compress.compress=true" + # Interner Container-Port des Frontends (ggf. anpassen) + - "traefik.http.services.sims-frontend.loadbalancer.server.port=80" + + backend: + image: sims-backend:latest + container_name: sims-backend + restart: unless-stopped + volumes: + - ./.env:/app/.env + - /home/simon/sims/data:/data + labels: + - "traefik.enable=true" + - "traefik.http.routers.sims-backend.tls=true" + - "traefik.http.routers.sims-backend.tls.certresolver=letsEncrypt" + - "traefik.http.routers.sims-backend.entrypoints=websecure" + - "traefik.http.routers.sims-backend.rule=Host(`sims.abler.tirol`) && PathPrefix(`/api`)" + # Strippt das Prefix /api bevor es an den Service geht + # - "traefik.http.middlewares.sims-backend-strip.stripprefixregex.regex=/api" + # - "traefik.http.routers.sims-backend.middlewares=sims-backend-strip@docker" + - "traefik.http.routers.sims-backend.priority=100" + # Interner Container-Port des Backends (ggf. anpassen) + - "traefik.http.services.sims-backend.loadbalancer.server.port=3000" + networks: + - postgresql + +networks: + postgresql: + name: NET_POSTGRESQL + external: true \ No newline at end of file diff --git a/error.log b/error.log new file mode 100644 index 0000000..4947e08 --- /dev/null +++ b/error.log @@ -0,0 +1,12 @@ + +error: gpg failed to sign the data: +[GNUPG:] KEYEXPIRED 1680287754? +[GNUPG:] KEYEXPIRED 1735074899? +[GNUPG:] KEY_CONSIDERED AB126E2B41891150E2BDD96C1C4AB6EC14EDB7AB 0? +[GNUPG:] BEGIN_SIGNING H8? +[GNUPG:] PINENTRY_LAUNCHED 8996 qt5 1.3.1 - xterm-256color - - 0/0 -? +gpg: Beglaubigung fehlgeschlagen: Verarbeitung wurde abgebrochen? +[GNUPG:] FAILURE sign 83886179? +gpg: signing failed: Verarbeitung wurde abgebrochen? + +fatal: failed to write commit object diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..5f08df3 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,42 @@ +import nx from '@nx/eslint-plugin'; + +export default [ + ...nx.configs['flat/base'], + ...nx.configs['flat/typescript'], + ...nx.configs['flat/javascript'], + { + ignores: ['**/dist', '**/out-tsc'], + }, + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + rules: { + '@nx/enforce-module-boundaries': [ + 'error', + { + enforceBuildableLibDependency: true, + allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'], + depConstraints: [ + { + sourceTag: '*', + onlyDependOnLibsWithTags: ['*'], + }, + ], + }, + ], + }, + }, + { + files: [ + '**/*.ts', + '**/*.tsx', + '**/*.cts', + '**/*.mts', + '**/*.js', + '**/*.jsx', + '**/*.cjs', + '**/*.mjs', + ], + // Override or add rules here + rules: {}, + }, +]; diff --git a/images/sims-preview.svg b/images/sims-preview.svg new file mode 100644 index 0000000..d8bdbee --- /dev/null +++ b/images/sims-preview.svg @@ -0,0 +1,301 @@ + + + + + + + + + + SIMS + .TIROL + + + + + + STAMMDATEN + + + + + + + + Artikel + + + + + + + Lager + + + + + + Inventur + + + BELEGE + + + + + + + + Lieferscheine + + + + + + Rechnungen + + + + + + + + + STAMMDATEN + / + Lagerübersicht + + + + + + + Artikel-Nr. oder Bezeichnung ... + + + + + Artikel anlegen + + + + CSV Export + + + + + + + + 247 + Artikel in Bestand + + + + + + ! + 12 + Unter Mindestbestand + + + + + + 5 + Leer / nicht vorrätig + + + + + + + + 18 + Offene Lieferscheine + + + + + + + + + + ARTIKEL-NR. + BEZEICHNUNG + BESTAND + MIN-BESTAND + PREIS + STATUS + AKTION + + + + + ART-00124 + Schrauben M8 × 40mm + 482 Stk. + 100 Stk. + €0,12 + + + + In Bestand + + Edit + + + + + ART-00087 + Dichtungsring 12mm + 38 Stk. + 50 Stk. + €0,45 + + + + Niedrig + + Edit + + + + + ART-00201 + Hydraulikschlauch 1/2" + 0 Stk. + 10 Stk. + €14,90 + + + + Leer + + Edit + + + + + ART-00056 + Lagergehäuse SKF 6205 + 12 Stk. + 5 Stk. + €8,70 + + + + In Bestand + + Edit + + + + + ART-00312 + O-Ring NBR 30×3mm + 204 Stk. + 50 Stk. + €0,08 + + + + In Bestand + + Edit + + + + + ART-00198 + Keilriemen A-42 + 7 Stk. + 10 Stk. + €4,20 + + + + Niedrig + + Edit + + + + + ART-00445 + Sicherungsring DIN 471 ... + + + + + + + + + + + + SIMS.ABLER.TIROL + Lieferschein + LS-2024-0042 + 17. März 2024 + + + + Geliefert + + + KUNDE + Musterfirma GmbH + Musterstraße 12, 6020 Innsbruck + + LIEFERDATUM + 17.03.2024 + + + + + + POS. + ART-NR. + BEZEICHNUNG + MENGE + SUM. + + + 1 + ART-00124 + Schrauben M8 × 40mm + 200 Stk. + €24,00 + + + 2 + ART-00056 + Lagergehäuse SKF 6205 + 4 Stk. + €34,80 + + + + + + NETTO + €58,80 + MWST. + €11,76 + BRUTTO + €70,56 + + + ↓ PDF + + + + + sims.abler.tirol · Lagerverwaltung · © 2026 Simon Abler + + + self-hosted + + diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..c49c9a9 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,6 @@ +import type { Config } from 'jest'; +import { getJestProjectsAsync } from '@nx/jest'; + +export default async (): Promise => ({ + projects: await getJestProjectsAsync(), +}); diff --git a/jest.preset.js b/jest.preset.js new file mode 100644 index 0000000..f078ddc --- /dev/null +++ b/jest.preset.js @@ -0,0 +1,3 @@ +const nxPreset = require('@nx/jest/preset').default; + +module.exports = { ...nxPreset }; diff --git a/kontext.md b/kontext.md new file mode 100644 index 0000000..4d8fa63 --- /dev/null +++ b/kontext.md @@ -0,0 +1,202 @@ +# Kontext zum Repo + +Stand: 2026-03-22 +Basis: statische Codeanalyse des aktuellen Worktrees, danach Abgleich mit `CLAUDE.md` +Hinweis: keine Tests oder Dev-Server ausgefuehrt + +## Eigene Analyse des aktuellen Repos + +### 1. Repo, Stack, Struktur + +- NX-Monorepo mit zwei relevanten Apps: + - `apps/server`: NestJS 11 Backend + - `apps/sim-system`: Angular 21 Frontend +- Zusaetzlich vorhanden: + - `apps/server-e2e`: Jest-E2E fuer Backend + - `apps/sim-system-e2e`: Playwright-Projekt, aktuell praktisch leer + - `libs/domain`: aktuell nur Platzhalter, fachlich derzeit kaum relevant +- Root enthaelt reale SQLite-Dateien `sim.db` und `sim_old.db` sowie bereits gebaute Artefakte unter `dist/` +- `package.json` hat keine nutzbaren Root-Skripte; Doku zu Migrationen in README/CLAUDE ist damit derzeit nicht direkt ueber `npm run ...` abbildbar + +### 2. Backend-Ist-Zustand + +- Bootstrap in `apps/server/src/main.ts` + - globaler Prefix: `/api/v1` + - globaler `ErrorFilter` + - globale `ValidationPipe` mit `whitelist: true` und `transform: true` + - CORS ist bereits explizit konfiguriert, nicht mehr `cors: true` +- Swagger ist aktiv unter `/api/v1/doc` und `/api/v1/doc/univ` +- Datenbank: + - TypeORM 0.3 mit `better-sqlite3` + - Standardpfad faellt auf `sim.db` zurueck + - Entity-Glob kommt aus Config, Default `dist/**/*.entity.js` + +### 3. Backend-Domaene und API + +- Fachliche Hauptbereiche: + - `article`: Artikel, Artikelgruppen, Inventur, CSV-Import + - `customer`: Kunden und Rabatte + - `bills`: Lieferscheine, Order-Entries, Rechnungen, Annotationen, Dashboard + - `users`: nur einfache User-Verwaltung, kein kompletter Auth-Flow +- Antwortformat ist systematisch auf `ReS` / `ReE` aufgebaut +- Aktuell vorhandene Controller: + - `articles` + - `articlegroups` + - `customers` + - `slipsheets` + - `bills` + - `order-entries` + - `dashboard` + - `users` + +### 4. Wichtige fachliche Beobachtungen im Backend + +- `GET /slipsheets/:id` ist bereits repariert: + - der Controller castet `id` auf Zahl und wirft `404`, wenn kein Datensatz gefunden wurde +- `GET /bills/:id/pdf` ist bereits read-only: + - PDF wird nicht mehr bei jedem GET neu erzeugt + - fehlt die Datei, kommt `404` mit Hinweis auf Neugenerierung per POST +- `generateBill()` rollt im Fehlerfall nur neu erzeugte Rechnungen zurueck: + - der fruehere "loescht bestehende Rechnung"-Fehler ist im aktuellen Code nicht mehr sichtbar +- Nummernvergabe fuer Lieferscheine und Rechnungen ist verbessert, aber nicht wirklich transaktional: + - es gibt Retry-Logik bei Unique-Constraint-Fehlern + - ein echter Lock/serialisierter Number-Allocator existiert weiter nicht +- `inventoryDate` ist bereits korrigiert: + - jetzt normales `@Column({ type: 'datetime', nullable: true })` + - nicht mehr `@CreateDateColumn()` +- Inventur: + - `makeInventory()` validiert `newStock >= 0` + - Artikel ohne `trackStock` werden abgelehnt + - Inventory-Log-Fehler werden absichtlich geschluckt, der Artikelbestand wird trotzdem aktualisiert +- Root-Endpoint `/` gibt aktuell `"V1"` zurueck, nicht `{ message: 'Hello API' }` + +### 4a. Fachlicher Status-Workflow fuer Lieferscheine und Rechnungen + +- Lieferschein: + - `offen`: neuer oder noch nicht finalisierter Lieferschein + - PDF drucken / Lieferschein erzeugen: Status wechselt auf `geschlossen` + - Artikel oder Positionen nachtraeglich bearbeiten: Status wechselt auf `bearbeitet` +- Rechnung: + - nur geschlossene Lieferscheine werden zur Rechnung hinzugefuegt + - Rechnung erstellen und drucken: Rechnung ist `geschlossen` + - Rechnungsdatum bearbeiten oder einen bereits verrechneten Lieferschein bearbeiten: + - der betroffene Lieferschein geht auf `bearbeitet` + - die zugehoerige Rechnung geht fachlich ebenfalls auf `bearbeitet` +- Fachliche Kette damit zusammengefasst: + - Lieferschein `offen` -> PDF drucken -> `geschlossen` -> Artikel bearbeiten -> `bearbeitet` + - geschlossene Lieferscheine auswaehlen -> Rechnung erzeugen und drucken -> Rechnung `geschlossen` + - Rechnungsdatum aendern oder verrechneten Lieferschein bearbeiten -> Lieferschein und Rechnung `bearbeitet` + +### 5. Frontend-Ist-Zustand + +- Angular 21 mit Standalone Components +- `provideRouter(...)` und `provideHttpClient(withInterceptors([apiInterceptor]))` +- Kein Proxy im aktuellen Setup erkennbar; das Frontend arbeitet ueber `environment.apiUrl` +- Dev-API-URL: `http://localhost:3000/api/v1` +- Prod-API-URL: leerer String, also faktisch Same-Origin-Annahme +- Layout ist bereits modernisiert: + - Sidebar + - Topbar + - Bottom Navigation fuer mobile Nutzung + +### 6. Aktuelle Frontend-Routen + +- Vorhanden: + - `/dashboard` + - `/articles` + - `/articles/:id` + - `/customers` + - `/customers/new` + - `/customers/:id` + - `/customers/:id/edit` + - `/slipsheets` + - `/slipsheets/:id` + - `/bills` + - `/bills/:id` + - `/inventory` + - `/order/new` +- Nicht vorhanden: + - `/login` +- Es gibt zwar eine `login.component.ts`, aber keine Route dorthin + +### 7. Konkrete API-Contract-Brueche zwischen Frontend und Backend + +- `AuthenticationService` erwartet: + - `POST users/login` + - `GET users/me/refresh` +- Diese Endpunkte existieren im aktuellen Backend nicht +- `authGuard` leitet auf `/login` um, aber die Route existiert nicht +- `CustomerService` im Frontend verwendet: + - `DELETE customers/:id` + - `DELETE customers/:id/discounts/:discountId` +- Beide Endpunkte existieren im aktuellen `CustomerController` nicht +- Die Doku spricht von `PUT /slipsheets/:id`, im aktuellen Backend existiert diese Route nicht +- README/CLAUDE nennen Auth/JWT als Thema; real ist die API derzeit funktional offen, obwohl Swagger-Dekoratoren `@ApiBearerAuth()` gesetzt sind + +### 8. Tests und Wartungszustand + +- Es gibt Backend-E2E-Spezifikationen fuer Basisanforderungen wie Prefix, Error-Filter, Validation und Response-Wrapper +- Mindestens ein Test wirkt veraltet: + - `apps/server-e2e/src/server/server.spec.ts` erwartet am Root `{ message: 'Hello API' }` + - der aktuelle Code liefert `"V1"` +- `apps/sim-system-e2e` ist noch nicht sinnvoll ausgebaut +- README ist nur bedingt als Wahrheit nutzbar; mehrere Aussagen sind klar aelter als der aktuelle Code + +## Abgleich mit CLAUDE.md + +### Uebereinstimmungen + +- Monorepo mit NestJS-Backend und Angular-Frontend +- zentrale Fachmodule `articles`, `customers`, `slipsheets`, `bills`, `dashboard` +- globaler API-Prefix `/api/v1` +- Swagger vorhanden +- Antwort-Wrapper und DTO/Validation-Ansatz passen grundsaetzlich zum Code +- Auth ist nicht sauber fertiggestellt + +### Abweichungen oder veraltete Angaben in CLAUDE.md + +- Frontend-Pfad falsch: + - `CLAUDE.md` spricht von `apps/sims` + - real ist es `apps/sim-system` +- Angular-Version falsch: + - `CLAUDE.md`: Angular 20 + - real: Angular 21 +- Frontend-Routing in `CLAUDE.md` ist weitgehend historisch: + - die heute vorhandenen Routen liegen direkt unter `/dashboard`, `/articles`, `/customers`, `/slipsheets`, `/bills`, `/inventory`, `/order/new` + - die beschriebenen alten Admin-/Mobile-Routen existieren so nicht +- Proxy-Angabe ist veraltet: + - ein `apps/sims/proxy.conf.json` existiert in diesem Repo nicht + - stattdessen arbeitet das Frontend mit `environment.apiUrl` +- Mehrere in `CLAUDE.md` als offen gelistete Bugs sind bereits behoben: + - `GET /slipsheets/:id` Bug + - `GET /bills/:id/pdf` Side-Effect Bug + - `inventoryDate` Bug + - CORS "alles offen" Bug +- Die Aussage "DELETE /articles/:id fehlt im Backend" ist im aktuellen Code falsch: + - die Route existiert +- Die Aussage "state-Filter von Frontend wird im Backend ignoriert" ist im aktuellen Code fuer `bills` und `slipsheets` falsch: + - beide Controller unterstuetzen State-Filter +- Die in `CLAUDE.md` genannten Root-Skripte fuer Migrationen sind im aktuellen `package.json` nicht hinterlegt +- Mehrere Dateipfade in `CLAUDE.md` zeigen noch auf alte Strukturen wie `apps/server/src/app/...` oder `apps/sims/...`; das aktuelle Repo nutzt diese Struktur so nicht + +### Punkte, die CLAUDE.md korrekt ahnt, aber unpraezise beschreibt + +- Auth ist tatsaechlich unfertig, aber das Problem ist tiefer als "Login-Stub": + - im Frontend fehlen Route und lauffaehiger Flow + - im Backend fehlen die erwarteten Login-/Refresh-Endpunkte komplett +- Race-Condition bei Nummernvergabe ist weiterhin ein valider Hinweis + - allerdings mit Retry-Absicherung, nicht mehr ganz im alten Zustand +- Frontend/Backend-Contract-Matrix ist weiterhin ein sinnvolles Thema + - besonders bei Auth sowie Customer-Delete / Discount-Delete + +## Arbeitskontext fuer weitere Aenderungen + +- Primaere Wahrheit ist aktuell der Code, nicht README oder `CLAUDE.md` +- Bei Folgearbeiten zuerst Contract-Brueche zwischen Angular-Services und NestJS-Controllern pruefen +- Besonders riskant: + - Auth-Flows + - Customer-Loeschung / Discount-Loeschung + - veraltete Tests und Doku +- Wenn neue Arbeit an Bills/Slipsheets ansteht: + - aktuelle Buglisten aus `CLAUDE.md` nicht ungeprueft uebernehmen + - mehrere dort genannte Punkte sind bereits erledigt diff --git a/libs/domain/README.md b/libs/domain/README.md new file mode 100644 index 0000000..5840f6a --- /dev/null +++ b/libs/domain/README.md @@ -0,0 +1,11 @@ +# domain + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build domain` to build the library. + +## Running unit tests + +Run `nx test domain` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/domain/eslint.config.mjs b/libs/domain/eslint.config.mjs new file mode 100644 index 0000000..c334bc0 --- /dev/null +++ b/libs/domain/eslint.config.mjs @@ -0,0 +1,19 @@ +import baseConfig from '../../eslint.config.mjs'; + +export default [ + ...baseConfig, + { + files: ['**/*.json'], + rules: { + '@nx/dependency-checks': [ + 'error', + { + ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'], + }, + ], + }, + languageOptions: { + parser: await import('jsonc-eslint-parser'), + }, + }, +]; diff --git a/libs/domain/jest.config.cts b/libs/domain/jest.config.cts new file mode 100644 index 0000000..3d2d014 --- /dev/null +++ b/libs/domain/jest.config.cts @@ -0,0 +1,10 @@ +module.exports = { + displayName: 'domain', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/libs/domain', +}; diff --git a/libs/domain/package.json b/libs/domain/package.json new file mode 100644 index 0000000..9bbe6fd --- /dev/null +++ b/libs/domain/package.json @@ -0,0 +1,11 @@ +{ + "name": "@sim-system/domain", + "version": "0.0.1", + "private": true, + "type": "commonjs", + "main": "./src/index.js", + "types": "./src/index.d.ts", + "dependencies": { + "tslib": "^2.3.0" + } +} diff --git a/libs/domain/project.json b/libs/domain/project.json new file mode 100644 index 0000000..a48443b --- /dev/null +++ b/libs/domain/project.json @@ -0,0 +1,26 @@ +{ + "name": "domain", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/domain/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/domain", + "main": "libs/domain/src/index.ts", + "tsConfig": "libs/domain/tsconfig.lib.json", + "assets": ["libs/domain/*.md"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/domain/jest.config.cts" + } + } + } +} diff --git a/libs/domain/src/index.ts b/libs/domain/src/index.ts new file mode 100644 index 0000000..d6e22b8 --- /dev/null +++ b/libs/domain/src/index.ts @@ -0,0 +1 @@ +export * from './lib/domain'; diff --git a/libs/domain/src/lib/domain.spec.ts b/libs/domain/src/lib/domain.spec.ts new file mode 100644 index 0000000..fcb14d3 --- /dev/null +++ b/libs/domain/src/lib/domain.spec.ts @@ -0,0 +1,7 @@ +import { domain } from './domain'; + +describe('domain', () => { + it('should work', () => { + expect(domain()).toEqual('domain'); + }); +}); diff --git a/libs/domain/src/lib/domain.ts b/libs/domain/src/lib/domain.ts new file mode 100644 index 0000000..46ac4ac --- /dev/null +++ b/libs/domain/src/lib/domain.ts @@ -0,0 +1,3 @@ +export function domain(): string { + return 'domain'; +} diff --git a/libs/domain/tsconfig.json b/libs/domain/tsconfig.json new file mode 100644 index 0000000..ea98558 --- /dev/null +++ b/libs/domain/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "importHelpers": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/domain/tsconfig.lib.json b/libs/domain/tsconfig.lib.json new file mode 100644 index 0000000..6653d08 --- /dev/null +++ b/libs/domain/tsconfig.lib.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": [ + "jest.config.ts", + "jest.config.cts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ] +} diff --git a/libs/domain/tsconfig.spec.json b/libs/domain/tsconfig.spec.json new file mode 100644 index 0000000..01c59b6 --- /dev/null +++ b/libs/domain/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "moduleResolution": "node10", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "jest.config.cts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/nx.json b/nx.json new file mode 100644 index 0000000..6f94fb8 --- /dev/null +++ b/nx.json @@ -0,0 +1,80 @@ +{ + "$schema": "./node_modules/nx/schemas/nx-schema.json", + "defaultBase": "master", + "tui": {"enabled": false}, + "namedInputs": { + "default": ["{projectRoot}/**/*", "sharedGlobals"], + "production": [ + "default", + "!{projectRoot}/.eslintrc.json", + "!{projectRoot}/eslint.config.mjs", + "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", + "!{projectRoot}/tsconfig.spec.json", + "!{projectRoot}/jest.config.[jt]s", + "!{projectRoot}/src/test-setup.[jt]s", + "!{projectRoot}/test-setup.[jt]s" + ], + "sharedGlobals": [] + }, + "targetDefaults": { + "@angular/build:application": { + "cache": true, + "dependsOn": ["^build"], + "inputs": ["production", "^production"] + }, + "@nx/eslint:lint": { + "cache": true, + "inputs": [ + "default", + "{workspaceRoot}/.eslintrc.json", + "{workspaceRoot}/.eslintignore", + "{workspaceRoot}/eslint.config.mjs" + ] + }, + "@nx/jest:jest": { + "cache": true, + "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"], + "options": { + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "@nx/js:tsc": { + "cache": true, + "dependsOn": ["^build"], + "inputs": ["production", "^production"] + }, + "@nx/esbuild:esbuild": { + "cache": true, + "dependsOn": ["^build"], + "inputs": ["production", "^production"] + } + }, + "plugins": [ + { + "plugin": "@nx/playwright/plugin", + "options": { + "targetName": "e2e" + } + }, + { + "plugin": "@nx/eslint/plugin", + "options": { + "targetName": "lint" + } + } + ], + "generators": { + "@nx/angular:application": { + "e2eTestRunner": "playwright", + "linter": "eslint", + "style": "scss", + "unitTestRunner": "jest" + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b72a50e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,30954 @@ +{ + "name": "@sim-system/source", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sim-system/source", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@angular/common": "~21.1.0", + "@angular/compiler": "~21.1.0", + "@angular/core": "~21.1.0", + "@angular/forms": "~21.1.0", + "@angular/platform-browser": "~21.1.0", + "@angular/platform-browser-dynamic": "~21.1.0", + "@angular/router": "~21.1.0", + "@nestjs/common": "^11.1.17", + "@nestjs/config": "^4.0.3", + "@nestjs/core": "^11.1.17", + "@nestjs/mapped-types": "^2.1.0", + "@nestjs/platform-express": "^11.1.17", + "@nestjs/swagger": "^11.2.6", + "@nestjs/typeorm": "^11.0.0", + "axios": "^1.6.0", + "better-sqlite3": "^12.8.0", + "bootstrap": "^5.3.8", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.4", + "express": "^4.21.2", + "moment": "^2.30.1", + "multer": "^2.1.1", + "nest-csv-parser": "^2.0.4", + "ngx-toastr": "^20.0.5", + "pdf-lib": "^1.17.1", + "pdf-to-printer": "^5.7.0", + "pdfmake": "^0.2.23", + "reflect-metadata": "^0.2.2", + "rxjs": "~7.8.0", + "swagger-ui-express": "^5.0.1", + "typeorm": "^0.3.28" + }, + "devDependencies": { + "@angular-devkit/core": "~21.1.0", + "@angular-devkit/schematics": "~21.1.0", + "@angular/build": "~21.1.0", + "@angular/cli": "~21.1.0", + "@angular/compiler-cli": "~21.1.0", + "@angular/language-service": "~21.1.0", + "@eslint/js": "^9.8.0", + "@nx/angular": "22.5.4", + "@nx/devkit": "22.5.4", + "@nx/esbuild": "22.5.4", + "@nx/eslint": "22.5.4", + "@nx/eslint-plugin": "22.5.4", + "@nx/jest": "22.5.4", + "@nx/js": "22.5.4", + "@nx/node": "^22.5.4", + "@nx/playwright": "22.5.4", + "@nx/web": "22.5.4", + "@nx/workspace": "22.5.4", + "@playwright/test": "^1.36.0", + "@schematics/angular": "~21.1.0", + "@swc-node/register": "~1.11.1", + "@swc/core": "~1.15.5", + "@swc/helpers": "~0.5.18", + "@types/better-sqlite3": "^7.6.13", + "@types/express": "^4.17.21", + "@types/jest": "^30.0.0", + "@types/moment": "^2.11.29", + "@types/multer": "^2.1.0", + "@types/node": "20.19.9", + "@types/pdfmake": "^0.1.21", + "@typescript-eslint/utils": "^8.40.0", + "angular-eslint": "^21.0.1", + "esbuild": "^0.19.2", + "eslint": "^9.8.0", + "eslint-config-prettier": "^10.0.0", + "eslint-plugin-playwright": "^1.6.2", + "jest": "^30.0.2", + "jest-environment-jsdom": "^30.0.2", + "jest-environment-node": "^30.0.2", + "jest-preset-angular": "~16.0.0", + "jest-util": "^30.0.2", + "jsonc-eslint-parser": "^2.1.0", + "nx": "22.5.4", + "prettier": "~3.6.2", + "ts-jest": "^29.4.0", + "ts-node": "10.9.1", + "tslib": "^2.3.0", + "typescript": "~5.9.2", + "typescript-eslint": "^8.40.0" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.12.2.tgz", + "integrity": "sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.46.2.tgz", + "integrity": "sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.46.2.tgz", + "integrity": "sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.46.2.tgz", + "integrity": "sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.46.2.tgz", + "integrity": "sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.46.2.tgz", + "integrity": "sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.46.2.tgz", + "integrity": "sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.46.2.tgz", + "integrity": "sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.46.2", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.46.2.tgz", + "integrity": "sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.46.2", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.46.2.tgz", + "integrity": "sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.46.2.tgz", + "integrity": "sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.46.2.tgz", + "integrity": "sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.46.2.tgz", + "integrity": "sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.46.2.tgz", + "integrity": "sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "21.1.5", + "rxjs": "7.8.2" + }, + "bin": { + "architect": "bin/cli.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.3", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.5.tgz", + "integrity": "sha512-CGmoorQL5+mVCJEHwHWOrhSd1hFxB3h66i9wUDizJAEQUM3mSml5SiglHArpWY/G4GmFwi6XVe+Jm3U8J/mcFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "21.1.5", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.21", + "ora": "9.0.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-eslint/builder": { + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-21.3.0.tgz", + "integrity": "sha512-26QUUouei52biUFAlJSrWNAU9tuF2miKwd8uHdxWwCF31xz+OxC5+NfudWvt1AFaYow7gWueX1QX3rNNtSPDrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": ">= 0.2100.0 < 0.2200.0", + "@angular-devkit/core": ">= 21.0.0 < 22.0.0" + }, + "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-21.3.0.tgz", + "integrity": "sha512-l521I24J9gJxyMbRkrM24Tc7W8J8BP+TDAmVs2nT8+lXbS3kg8QpWBRtd+hNUgq6o+vt+lKBkytnEfu8OiqeRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-21.3.0.tgz", + "integrity": "sha512-Whf/AUUBekOlfSJRS78m76YGrBQAZ3waXE7oOdlW5xEQvn8jBDN9EGuNnjg/syZzvzjK4ZpYC4g1XYXrc+fQIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.3.0", + "@angular-eslint/utils": "21.3.0", + "ts-api-utils": "^2.1.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-21.3.0.tgz", + "integrity": "sha512-lVixd/KypPWgA/5/pUOhJV9MTcaHjYZEqyOi+IiLk+h+maGxn6/s6Ot+20n+XGS85zAgOY+qUw6EEQ11hoojIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.3.0", + "@angular-eslint/utils": "21.3.0", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@angular-eslint/template-parser": "21.3.0", + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-21.3.0.tgz", + "integrity": "sha512-8deU/zVY9f8k8kAQQ9PL130ox2VlrZw3fMxgsPNAY5tjQ0xk0J2YVSszYHhcqdMGG1J01IsxIjvQaJ4pFfEmMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": ">= 21.0.0 < 22.0.0", + "@angular-devkit/schematics": ">= 21.0.0 < 22.0.0", + "@angular-eslint/eslint-plugin": "21.3.0", + "@angular-eslint/eslint-plugin-template": "21.3.0", + "ignore": "7.0.5", + "semver": "7.7.4", + "strip-json-comments": "3.1.1" + }, + "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0" + } + }, + "node_modules/@angular-eslint/schematics/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/@angular-eslint/template-parser": { + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-21.3.0.tgz", + "integrity": "sha512-ysyou1zAY6M6rSZNdIcYKGd4nk6TCapamyFNB3ivmTlVZ0O35TS9o/rJ0aUttuHgDp+Ysgs3ql+LA746PXgCyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.3.0", + "eslint-scope": "^9.1.1" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-21.3.0.tgz", + "integrity": "sha512-oNigH6w3l+owTMboj/uFG0tHOy43uH8BpQRtBOQL1/s2+5in/BJ2Fjobv3SyizxTgeJ1FhRefbkT8GmVjK7jAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.3.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": "*" + } + }, + "node_modules/@angular/build": { + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.5.tgz", + "integrity": "sha512-v2eDinWKlSKuk5pyMMY8j5TMFW8HA9B1l13TrDDpxsRGAAzekg7TFNyuh1x9Y6Rq4Vn+8/8pCjMUPZigzWbMhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2101.5", + "@babel/core": "7.28.5", + "@babel/helper-annotate-as-pure": "7.27.3", + "@babel/helper-split-export-declaration": "7.24.7", + "@inquirer/confirm": "5.1.21", + "@vitejs/plugin-basic-ssl": "2.1.0", + "beasties": "0.3.5", + "browserslist": "^4.26.0", + "esbuild": "0.27.2", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "listr2": "9.0.5", + "magic-string": "0.30.21", + "mrmime": "2.0.1", + "parse5-html-rewriting-stream": "8.0.0", + "picomatch": "4.0.3", + "piscina": "5.1.4", + "rolldown": "1.0.0-beta.58", + "sass": "1.97.1", + "semver": "7.7.3", + "source-map-support": "0.5.21", + "tinyglobby": "0.2.15", + "undici": "7.20.0", + "vite": "7.3.0", + "watchpack": "2.5.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "lmdb": "3.4.4" + }, + "peerDependencies": { + "@angular/compiler": "^21.0.0", + "@angular/compiler-cli": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/localize": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/platform-server": "^21.0.0", + "@angular/service-worker": "^21.0.0", + "@angular/ssr": "^21.1.5", + "karma": "^6.4.0", + "less": "^4.2.0", + "ng-packagr": "^21.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.9 <6.0", + "vitest": "^4.0.8" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@angular/build/node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@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/@angular/build/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", + "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/@angular/build/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/build/node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/@angular/build/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/build/node_modules/sass": { + "version": "1.97.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz", + "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/@angular/build/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/build/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/build/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@angular/build/node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "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/@angular/build/node_modules/watchpack": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz", + "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@angular/cli": { + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.5.tgz", + "integrity": "sha512-ljqvAzSk8FKMaYW/aZhR+SXjudbQViYYkMlJvJUClGpokjDM9KfJWPX+QZfr2J+piW5yaaHmFaIMddO9QxkUDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2101.5", + "@angular-devkit/core": "21.1.5", + "@angular-devkit/schematics": "21.1.5", + "@inquirer/prompts": "7.10.1", + "@listr2/prompt-adapter-inquirer": "3.0.5", + "@modelcontextprotocol/sdk": "1.26.0", + "@schematics/angular": "21.1.5", + "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.46.2", + "ini": "6.0.0", + "jsonc-parser": "3.3.1", + "listr2": "9.0.5", + "npm-package-arg": "13.0.2", + "pacote": "21.0.4", + "parse5-html-rewriting-stream": "8.0.0", + "resolve": "1.22.11", + "semver": "7.7.3", + "yargs": "18.0.0", + "zod": "4.3.5" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@angular/cli/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@angular/cli/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@angular/cli/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular/cli/node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@angular/cli/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "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/@angular/cli/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular/cli/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@angular/cli/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@angular/cli/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@angular/cli/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@angular/common": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.6.tgz", + "integrity": "sha512-qEfwyJhebl2tHwFhKHE/ZzsCLMWnQ0u/UDS23KUA4tTWaOKH8Usu7DS1B3KnUravII8R6ZpYn86L+B903v9WxA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "21.1.6", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.6.tgz", + "integrity": "sha512-8RFdfbWTyx+OiRkiK2LsYPuhv8b65S3x/4+98kZRCrzP6YNjq/qsuqpfMUIKwnjok78FfhGyEx4I5LOV3Vkabw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@angular/compiler-cli": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.6.tgz", + "integrity": "sha512-0JU2cBDMSB4hU4KwDS2ThrkGh+Njf8Yfm11CKR0NWbHGwW1xHa7whlcpUzX/USqL+FNGXQ75R0fOcZrT86YvrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.5", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^5.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "21.1.6", + "typescript": ">=5.9 <6.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@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/@angular/compiler-cli/node_modules/@babel/core/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/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/compiler-cli/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@angular/compiler-cli/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@angular/compiler-cli/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@angular/compiler-cli/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular/compiler-cli/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular/compiler-cli/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular/compiler-cli/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@angular/compiler-cli/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@angular/compiler-cli/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@angular/compiler-cli/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@angular/core": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.6.tgz", + "integrity": "sha512-c+n9Ynq1Ar+4SOaL10C/arqBje0dUFFUaDyErXp3jPXU/L29fsFTlmKM2EWunM1RhJckYonJ/xtH0gwwrH6W9Q==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "21.1.6", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0 || ~0.16.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } + } + }, + "node_modules/@angular/forms": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.6.tgz", + "integrity": "sha512-Bw3nVDWihGUGyys7oq2zdJ2MjvJvU1x1WaExYmp3rKU3S7rQXGq6IxY8bopTtHirTANrY2KUEnJ2IlK+xVg9OA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "21.1.6", + "@angular/core": "21.1.6", + "@angular/platform-browser": "21.1.6", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/language-service": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.6.tgz", + "integrity": "sha512-I2gYI9cKP/B/rUz8WVaWNpuULSdq4W+ZUm6YQyUXdGEh3dDEssK1xLucPBfUT7RAr6h7+w5/RS+zKWC1q9g24w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.6.tgz", + "integrity": "sha512-im6aNcgYdIYIVW2262ATkC39WUmhc+KVNVKwKtO5jlOsq9TWmxT1/esncEAlokMe5os6eeb/Ga4D6Ghj0gj4Ig==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/animations": "21.1.6", + "@angular/common": "21.1.6", + "@angular/core": "21.1.6" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.6.tgz", + "integrity": "sha512-lVtHkhK/jnrGdX+4S8ItfMO+5buHAU9NMHeDq+QqalnXznMaC7Qd4BPLcRWW4QAI177zG0NE1Bet5cjO75N9+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "21.1.6", + "@angular/compiler": "21.1.6", + "@angular/core": "21.1.6", + "@angular/platform-browser": "21.1.6" + } + }, + "node_modules/@angular/router": { + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.6.tgz", + "integrity": "sha512-JJn0gfeRks2czbeLmtxxjIlmKtOmjYi3yAaoAwiwpbfVHPLJeJ32axLJREAU0dBkThF8YD+r18uEJ9UrvkqrtA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "21.1.6", + "@angular/core": "21.1.6", + "@angular/platform-browser": "21.1.6", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.3.tgz", + "integrity": "sha512-Q6mU0Z6bfj6YvnX2k9n0JxiIwrCFN59x/nWmYQnAqP000ruX/yV+5bp/GRcF5T8ncvfwJQ7fgfP74DlpKExILA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0", + "peer": true + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.7.tgz", + "integrity": "sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "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/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz", + "integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-decorators": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", + "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.1.tgz", + "integrity": "sha512-ENp89vM9Pw4kv/koBb5N2f9bDZsR0hpf3BdPMOg/pkS3pwO4dzNnQZVXtBbeyAadgm865DmQG2jMMLqmZXvuCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.7", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@bramus/specificity/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/@bramus/specificity/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0", + "peer": true + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", + "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "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.1.1", + "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/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "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/@foliojs-fork/fontkit": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/fontkit/-/fontkit-1.9.2.tgz", + "integrity": "sha512-IfB5EiIb+GZk+77TRB86AHroVaqfq8JRFlUbz0WEwsInyCG0epX2tCPOy+UfaWPju30DeVoUAXfzWXmhn753KA==", + "license": "MIT", + "dependencies": { + "@foliojs-fork/restructure": "^2.0.2", + "brotli": "^1.2.0", + "clone": "^1.0.4", + "deep-equal": "^1.0.0", + "dfa": "^1.2.0", + "tiny-inflate": "^1.0.2", + "unicode-properties": "^1.2.2", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/@foliojs-fork/linebreak": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/linebreak/-/linebreak-1.1.2.tgz", + "integrity": "sha512-ZPohpxxbuKNE0l/5iBJnOAfUaMACwvUIKCvqtWGKIMv1lPYoNjYXRfhi9FeeV9McBkBLxsMFWTVVhHJA8cyzvg==", + "license": "MIT", + "dependencies": { + "base64-js": "1.3.1", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/@foliojs-fork/linebreak/node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "license": "MIT" + }, + "node_modules/@foliojs-fork/pdfkit": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/@foliojs-fork/pdfkit/-/pdfkit-0.15.3.tgz", + "integrity": "sha512-Obc0Wmy3bm7BINFVvPhcl2rnSSK61DQrlHU8aXnAqDk9LCjWdUOPwhgD8Ywz5VtuFjRxmVOM/kQ/XLIBjDvltw==", + "license": "MIT", + "dependencies": { + "@foliojs-fork/fontkit": "^1.9.2", + "@foliojs-fork/linebreak": "^1.1.1", + "crypto-js": "^4.2.0", + "jpeg-exif": "^1.1.4", + "png-js": "^1.0.0" + } + }, + "node_modules/@foliojs-fork/restructure": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/restructure/-/restructure-2.0.2.tgz", + "integrity": "sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==", + "license": "MIT" + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.2.tgz", + "integrity": "sha512-Lm/ZLhDZcBECta3TmCQSngiQykFdfw+QtI1/GYMsZd4l3nG+P8WLB16XuS7WaBGLQ+9E+cOcWQsth9cayuGt8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "retry": "^0.13.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.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/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/core/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/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "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/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/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/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/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/@jest/core/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.3.0.tgz", + "integrity": "sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/@jest/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.3.0", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", + "@types/node": "*", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/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/@jest/reporters/node_modules/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/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/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform/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/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types/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/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.11.tgz", + "integrity": "sha512-wThHjzUp01ImIjfCwhs+UnFkeGPFAymwLEkOtenHewaKe2pTP12p6r1UuwikA9NEvNf9Vlck92r8fb8n/MWM5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.11.tgz", + "integrity": "sha512-ZYlF3XbMayyp97xEN8ZvYutU99PCHjM64mMZvnCseXkCJXJDVLAwlF8Q/7q/xiWQRsv3pQBj1WXHd9eEyYcaCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.11.tgz", + "integrity": "sha512-D65YrnP6wRuZyEWoSFnBJSr5zARVpVBGctnhie4rCsMuGXNzX7IHKaOt85/Aj7SSoG1N2+/xlNjWmkLvZ2H3Tg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "@jsonjoy.com/fs-print": "4.56.11", + "@jsonjoy.com/fs-snapshot": "4.56.11", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.11.tgz", + "integrity": "sha512-CNmt3a0zMCIhniFLXtzPWuUxXFU+U+2VyQiIrgt/rRVeEJNrMQUABaRbVxR0Ouw1LyR9RjaEkPM6nYpED+y43A==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.11.tgz", + "integrity": "sha512-5OzGdvJDgZVo+xXWEYo72u81zpOWlxlbG4d4nL+hSiW+LKlua/dldNgPrpWxtvhgyntmdFQad2UTxFyGjJAGhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.11.tgz", + "integrity": "sha512-JADOZFDA3wRfsuxkT0+MYc4F9hJO2PYDaY66kRTG6NqGX3+bqmKu66YFYAbII/tEmQWPZeHoClUB23rtQM9UPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.56.11" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.11.tgz", + "integrity": "sha512-rnaKRgCRIn8JGTjxhS0JPE38YM3Pj/H7SW4/tglhIPbfKEkky7dpPayNKV2qy25SZSL15oFVgH/62dMZ/z7cyA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.56.11", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.11.tgz", + "integrity": "sha512-IIldPX+cIRQuUol9fQzSS3hqyECxVpYMJQMqdU3dCKZFRzEl1rkIkw4P6y7Oh493sI7YdxZlKr/yWdzEWZ1wGQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz", + "integrity": "sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 8", + "listr2": "9.0.5" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.4.tgz", + "integrity": "sha512-XaKL705gDWd6XVls3ATDj13ZdML/LqSIxwgnYpG8xTzH2ifArx8fMMDdvqGE/Emd+W6R90W2fveZcJ0AyS8Y0w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.4.tgz", + "integrity": "sha512-GPHGEVcwJlkD01GmIr7B4kvbIcUDS2+kBadVEd7lU4can1RZaZQLDDBJRrrNfS2Kavvl0VLI/cMv7UASAXGrww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.4.tgz", + "integrity": "sha512-cmev5/dZr5ACKri9f6GU6lZCXTjMhV72xujlbOhFCgFXrt4W0TxGsmY8kA1BITvH60JBKE50cSxsiulybAbrrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.4.tgz", + "integrity": "sha512-mALqr7DE42HsiwVTKpQWxacjHoJk+e9p00RWIJqTACh/hpucxp/0lK/XMh5XzWnU/TDCZLukq1+vNqnNumTP/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.4.tgz", + "integrity": "sha512-QjLs8OcmCNcraAcLoZyFlo0atzBJniQLLwhtR+ymQqS5kLYpV5RqwriL87BW+ZiR9ZiGgZx3evrz5vnWPtJ1fQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.4.tgz", + "integrity": "sha512-tr/pwHDlZ33forLGAr0tI04cRmP4SgF93yHbb+2zvZiDEyln5yMHhbKDySxY66aUOkhvBvTuHq9q/3YmTj6ZHQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.4.tgz", + "integrity": "sha512-KRzfocJzB/mgoTCqnMawuLSKheHRVTqWfSmouIgYpFs6Hx4zvZSvsZKSCEb5gHmICy7qsx9l06jk3MFTtiFVAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/bridge-react-webpack-plugin": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.21.6.tgz", + "integrity": "sha512-lJMmdhD4VKVkeg8RHb+Jwe6Ou9zKVgjtb1inEURDG/sSS2ksdZA8pVKLYbRPRbdmjr193Y8gJfqFbI2dqoyc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/sdk": "0.21.6", + "@types/semver": "7.5.8", + "semver": "7.6.3" + } + }, + "node_modules/@module-federation/bridge-react-webpack-plugin/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/cli": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/cli/-/cli-0.21.6.tgz", + "integrity": "sha512-qNojnlc8pTyKtK7ww3i/ujLrgWwgXqnD5DcDPsjADVIpu7STaoaVQ0G5GJ7WWS/ajXw6EyIAAGW/AMFh4XUxsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/dts-plugin": "0.21.6", + "@module-federation/sdk": "0.21.6", + "chalk": "3.0.0", + "commander": "11.1.0", + "jiti": "2.4.2" + }, + "bin": { + "mf": "bin/mf.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@module-federation/data-prefetch": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-0.21.6.tgz", + "integrity": "sha512-8HD7ZhtWZ9vl6i3wA7M8cEeCRdtvxt09SbMTfqIPm+5eb/V4ijb8zGTYSRhNDb5RCB+BAixaPiZOWKXJ63/rVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.21.6", + "@module-federation/sdk": "0.21.6", + "fs-extra": "9.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@module-federation/dts-plugin": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.21.6.tgz", + "integrity": "sha512-YIsDk8/7QZIWn0I1TAYULniMsbyi2LgKTi9OInzVmZkwMC6644x/ratTWBOUDbdY1Co+feNkoYeot1qIWv2L7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.21.6", + "@module-federation/managers": "0.21.6", + "@module-federation/sdk": "0.21.6", + "@module-federation/third-party-dts-extractor": "0.21.6", + "adm-zip": "^0.5.10", + "ansi-colors": "^4.1.3", + "axios": "^1.12.0", + "chalk": "3.0.0", + "fs-extra": "9.1.0", + "isomorphic-ws": "5.0.0", + "koa": "3.0.3", + "lodash.clonedeepwith": "4.5.0", + "log4js": "6.9.1", + "node-schedule": "2.1.1", + "rambda": "^9.1.0", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/enhanced": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-0.21.6.tgz", + "integrity": "sha512-8PFQxtmXc6ukBC4CqGIoc96M2Ly9WVwCPu4Ffvt+K/SB6rGbeFeZoYAwREV1zGNMJ5v5ly6+AHIEOBxNuSnzSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.21.6", + "@module-federation/cli": "0.21.6", + "@module-federation/data-prefetch": "0.21.6", + "@module-federation/dts-plugin": "0.21.6", + "@module-federation/error-codes": "0.21.6", + "@module-federation/inject-external-runtime-core-plugin": "0.21.6", + "@module-federation/managers": "0.21.6", + "@module-federation/manifest": "0.21.6", + "@module-federation/rspack": "0.21.6", + "@module-federation/runtime-tools": "0.21.6", + "@module-federation/sdk": "0.21.6", + "btoa": "^1.2.1", + "schema-utils": "^4.3.0", + "upath": "2.0.1" + }, + "bin": { + "mf": "bin/mf.js" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@module-federation/error-codes": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.21.6.tgz", + "integrity": "sha512-MLJUCQ05KnoVl8xd6xs9a5g2/8U+eWmVxg7xiBMeR0+7OjdWUbHwcwgVFatRIwSZvFgKHfWEiI7wsU1q1XbTRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@module-federation/inject-external-runtime-core-plugin": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-0.21.6.tgz", + "integrity": "sha512-DJQne7NQ988AVi3QB8byn12FkNb+C2lBeU1NRf8/WbL0gmHsr6kW8hiEJCm8LYaURwtsQqtsEV7i+8+51qjSmQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@module-federation/runtime-tools": "0.21.6" + } + }, + "node_modules/@module-federation/managers": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.21.6.tgz", + "integrity": "sha512-BeV6m2/7kF5MDVz9JJI5T8h8lMosnXkH2bOxxFewcra7ZjvDOgQu7WIio0mgk5l1zjNPvnEVKhnhrenEdcCiWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/sdk": "0.21.6", + "find-pkg": "2.0.0", + "fs-extra": "9.1.0" + } + }, + "node_modules/@module-federation/manifest": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-0.21.6.tgz", + "integrity": "sha512-yg93+I1qjRs5B5hOSvjbjmIoI2z3th8/yst9sfwvx4UDOG1acsE3HHMyPN0GdoIGwplC/KAnU5NmUz4tREUTGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/dts-plugin": "0.21.6", + "@module-federation/managers": "0.21.6", + "@module-federation/sdk": "0.21.6", + "chalk": "3.0.0", + "find-pkg": "2.0.0" + } + }, + "node_modules/@module-federation/node": { + "version": "2.7.35", + "resolved": "https://registry.npmjs.org/@module-federation/node/-/node-2.7.35.tgz", + "integrity": "sha512-z4l4LGsUhHntERzb6X9ggtTpq+L7xtXdeGRnr+Alv5GNubbml78HjE1b1sJZyqwpIVIt9buGerWUUlJosaEH+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/enhanced": "2.2.1", + "@module-federation/runtime": "2.2.1", + "@module-federation/sdk": "2.2.1", + "btoa": "1.2.1", + "encoding": "^0.1.13", + "node-fetch": "2.7.0", + "tapable": "2.3.0" + }, + "peerDependencies": { + "webpack": "^5.40.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/bridge-react-webpack-plugin": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-2.2.1.tgz", + "integrity": "sha512-0WTYkW5MYyyzBRH/wwVRuvP3ZE5pRnkIITU11ahPZhB1LtjBx03CLXmAlyhGocJ+wVkXM4WO8U3U6pN4+Y9jQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/sdk": "2.2.1", + "@types/semver": "7.5.8", + "semver": "7.6.3" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/cli": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/cli/-/cli-2.2.1.tgz", + "integrity": "sha512-JUuVY0X6FTis7+mvd7GBK4aDWvKo4dUNGYL7KjOZ5PWjbdZ/u/Igbbzx3LC/UK4PHIf5M/Xg7NQXC5RUcpYLKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/dts-plugin": "2.2.1", + "@module-federation/sdk": "2.2.1", + "chalk": "3.0.0", + "commander": "11.1.0", + "jiti": "2.4.2" + }, + "bin": { + "mf": "bin/mf.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/data-prefetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-2.2.1.tgz", + "integrity": "sha512-guT54LpM88VfDG4g12EB4UVrPpPYQ3cKRcRsz8Fa9lrX2vqOOmVCrNxGkKBOhtoMv3dcSJ/jtiBpiZWo6RcGfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "2.2.1", + "@module-federation/sdk": "2.2.1", + "fs-extra": "9.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/dts-plugin": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-2.2.1.tgz", + "integrity": "sha512-XmGwPyb0F88uR1wKCjJV1oR9zY1h1easCZymavOiklbC1KYADUUd90P4ti+2NesUdLsSdAE0EUzEhD4k+8B/8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "2.2.1", + "@module-federation/managers": "2.2.1", + "@module-federation/sdk": "2.2.1", + "@module-federation/third-party-dts-extractor": "2.2.1", + "adm-zip": "^0.5.10", + "ansi-colors": "^4.1.3", + "axios": "^1.13.5", + "chalk": "3.0.0", + "fs-extra": "9.1.0", + "isomorphic-ws": "5.0.0", + "lodash.clonedeepwith": "4.5.0", + "log4js": "6.9.1", + "node-schedule": "2.1.1", + "rambda": "^9.1.0", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-2.2.1.tgz", + "integrity": "sha512-KXVSXHi5Msx7r8IoubAuJCdHYxzzTynP4KDP3XdgZYqzYz3EjVqS5gWGVAtB4FMeYYVUW17+J3lQdgSSq1oCDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "2.2.1", + "@module-federation/cli": "2.2.1", + "@module-federation/data-prefetch": "2.2.1", + "@module-federation/dts-plugin": "2.2.1", + "@module-federation/error-codes": "2.2.1", + "@module-federation/inject-external-runtime-core-plugin": "2.2.1", + "@module-federation/managers": "2.2.1", + "@module-federation/manifest": "2.2.1", + "@module-federation/rspack": "2.2.1", + "@module-federation/runtime-tools": "2.2.1", + "@module-federation/sdk": "2.2.1", + "@module-federation/webpack-bundler-runtime": "2.2.1", + "btoa": "^1.2.1", + "schema-utils": "^4.3.0", + "tapable": "2.3.0", + "upath": "2.0.1" + }, + "bin": { + "mf": "bin/mf.js" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/error-codes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-2.2.1.tgz", + "integrity": "sha512-uucGTGpLk43HiOxLSsm04o/dk4nCde4NxfUrIsdJoFjQcnvC9rvDiuSqyoLm+8wxF7TOKSeZ0OG3I6h6/RKjsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@module-federation/node/node_modules/@module-federation/inject-external-runtime-core-plugin": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-2.2.1.tgz", + "integrity": "sha512-SslQbzYMaDMNuvdRiRk/m+7I5CcwHQZVPrGZsFeKN9x93F0e07CxnEbiEJbLCTM/yBivxKB+0Wg+iNw+TRs+Bg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@module-federation/runtime-tools": "2.2.1" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/managers": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-2.2.1.tgz", + "integrity": "sha512-lndwQ2Pu8TKgmhc5+jykAcHbDeSUGuxdFNYp+7C2oFps4vc5m1aGRl8WB9Lm9LX1xmF4qhz0/CVZ/oCt4rSv4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/sdk": "2.2.1", + "find-pkg": "2.0.0", + "fs-extra": "9.1.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/manifest": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-2.2.1.tgz", + "integrity": "sha512-Y5IoOQB9R/WmZePY7nyio7NMqAPL9xUqH42qeCZkUJHs2wrtR11fwOUNR9eEwGUPEsGUutXpiHWAXm7Eqcnb+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/dts-plugin": "2.2.1", + "@module-federation/managers": "2.2.1", + "@module-federation/sdk": "2.2.1", + "chalk": "3.0.0", + "find-pkg": "2.0.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/rspack": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-2.2.1.tgz", + "integrity": "sha512-tT84wYRg3T4cH+AUDHS+UCYiMN2KHpxtDGmMrS/yV78KhMoIj4X5uo59mdLtt9nnq/HQ6k557hrTwHW1HS6A+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "2.2.1", + "@module-federation/dts-plugin": "2.2.1", + "@module-federation/inject-external-runtime-core-plugin": "2.2.1", + "@module-federation/managers": "2.2.1", + "@module-federation/manifest": "2.2.1", + "@module-federation/runtime-tools": "2.2.1", + "@module-federation/sdk": "2.2.1", + "btoa": "1.2.1" + }, + "peerDependencies": { + "@rspack/core": "^0.7.0 || ^1.0.0 || ^2.0.0-0", + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/runtime": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-2.2.1.tgz", + "integrity": "sha512-FNE0jqhDvAEm84uyNVbGizbfrESk2Ftl/NdPpfADYiLa/zTapTGgbvkSHkrpTI7nqvwe/s4nPe05uNyo3fJCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "2.2.1", + "@module-federation/runtime-core": "2.2.1", + "@module-federation/sdk": "2.2.1" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/runtime-core": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-2.2.1.tgz", + "integrity": "sha512-u+cv5h599CriHsjjT7Xg7retuV6SnKPqA+/PjdSOZNkS3DR9PM+lmRZYb5p3qEricykbc0DdL4ZeHVPwGwlqWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "2.2.1", + "@module-federation/sdk": "2.2.1" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/runtime-tools": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-2.2.1.tgz", + "integrity": "sha512-MfDNbQFvbfeQNdc2hvb85qBQ29aehs9NGVWXE0goDPLb4SJNPVMYXjrgiNx6Z0glud7rEnXdstRGsmLcRAOujA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "2.2.1", + "@module-federation/webpack-bundler-runtime": "2.2.1" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/sdk": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-2.2.1.tgz", + "integrity": "sha512-J99VObuxkzwO57VvUwq1mDhbqvnqXmtBEossxiMlxayp2u2cGUNg94KD4tBrZQ/cZKce1nmufo5TxkUGnPKyjA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "node-fetch": "^3.3.2" + }, + "peerDependenciesMeta": { + "node-fetch": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/third-party-dts-extractor": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-2.2.1.tgz", + "integrity": "sha512-gIx2qQT9fMklnUf+SBnZHD0tbBrPhWP9krccxXR+BzwDVtjnP6hlYDzbiElx0c4oFaL48cxMwiHgVixybw/xEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-pkg": "2.0.0", + "fs-extra": "9.1.0", + "resolve": "1.22.8" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/webpack-bundler-runtime": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-2.2.1.tgz", + "integrity": "sha512-LSZ7RiJuiOuz+918eDNcFXZfFsj/mpwO/NC37N7Q8Jf/iQgPuGKZKYX6hbuX4uXhuS1KWJn/k2NctwkkVQ6LrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "2.2.1", + "@module-federation/sdk": "2.2.1" + } + }, + "node_modules/@module-federation/node/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/rspack": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-0.21.6.tgz", + "integrity": "sha512-SB+z1P+Bqe3R6geZje9dp0xpspX6uash+zO77nodmUy8PTTBlkL7800Cq2FMLKUdoTZHJTBVXf0K6CqQWSlItg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.21.6", + "@module-federation/dts-plugin": "0.21.6", + "@module-federation/inject-external-runtime-core-plugin": "0.21.6", + "@module-federation/managers": "0.21.6", + "@module-federation/manifest": "0.21.6", + "@module-federation/runtime-tools": "0.21.6", + "@module-federation/sdk": "0.21.6", + "btoa": "1.2.1" + }, + "peerDependencies": { + "@rspack/core": ">=0.7", + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/runtime": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.21.6.tgz", + "integrity": "sha512-+caXwaQqwTNh+CQqyb4mZmXq7iEemRDrTZQGD+zyeH454JAYnJ3s/3oDFizdH6245pk+NiqDyOOkHzzFQorKhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.21.6", + "@module-federation/runtime-core": "0.21.6", + "@module-federation/sdk": "0.21.6" + } + }, + "node_modules/@module-federation/runtime-core": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.21.6.tgz", + "integrity": "sha512-5Hd1Y5qp5lU/aTiK66lidMlM/4ji2gr3EXAtJdreJzkY+bKcI5+21GRcliZ4RAkICmvdxQU5PHPL71XmNc7Lsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.21.6", + "@module-federation/sdk": "0.21.6" + } + }, + "node_modules/@module-federation/runtime-tools": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.21.6.tgz", + "integrity": "sha512-fnP+ZOZTFeBGiTAnxve+axGmiYn2D60h86nUISXjXClK3LUY1krUfPgf6MaD4YDJ4i51OGXZWPekeMe16pkd8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.21.6", + "@module-federation/webpack-bundler-runtime": "0.21.6" + } + }, + "node_modules/@module-federation/sdk": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.21.6.tgz", + "integrity": "sha512-x6hARETb8iqHVhEsQBysuWpznNZViUh84qV2yE7AD+g7uIzHKiYdoWqj10posbo5XKf/147qgWDzKZoKoEP2dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@module-federation/third-party-dts-extractor": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.21.6.tgz", + "integrity": "sha512-Il6x4hLsvCgZNk1DFwuMBNeoxD1BsZ5AW2BI/nUgu0k5FiAvfcz1OFawRFEHtaM/kVrCsymMOW7pCao90DaX3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-pkg": "2.0.0", + "fs-extra": "9.1.0", + "resolve": "1.22.8" + } + }, + "node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.21.6.tgz", + "integrity": "sha512-7zIp3LrcWbhGuFDTUMLJ2FJvcwjlddqhWGxi/MW3ur1a+HaO8v5tF2nl+vElKmbG1DFLU/52l3PElVcWf/YcsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.21.6", + "@module-federation/sdk": "0.21.6" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@nestjs/common": { + "version": "11.1.17", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.17.tgz", + "integrity": "sha512-hLODw5Abp8OQgA+mUO4tHou4krKgDtUcM9j5Ihxncst9XeyxYBTt2bwZm4e4EQr5E352S4Fyy6V3iFx9ggxKAg==", + "license": "MIT", + "dependencies": { + "file-type": "21.3.2", + "iterare": "1.2.1", + "load-esm": "1.0.3", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": ">=0.4.1", + "class-validator": ">=0.13.2", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/config": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.3.tgz", + "integrity": "sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==", + "license": "MIT", + "dependencies": { + "dotenv": "17.2.3", + "dotenv-expand": "12.0.3", + "lodash": "4.17.23" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nestjs/core": { + "version": "11.1.17", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.17.tgz", + "integrity": "sha512-lD5mAYekTTurF3vDaa8C2OKPnjiz4tsfxIc5XlcSUzOhkwWf6Ay3HKvt6FmvuWQam6uIIHX52Clg+e6tAvf/cg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@nuxt/opencollective": "0.4.1", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "8.3.0", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/core/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/platform-express": { + "version": "11.1.17", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.17.tgz", + "integrity": "sha512-mAf4eOsSBsTOn/VbrUO1gsjW6dVh91qqXPMXun4dN8SnNjf7PTQagM9o8d6ab8ZBpNe6UdZftdrZoDetU+n4Qg==", + "license": "MIT", + "dependencies": { + "cors": "2.8.6", + "express": "5.2.1", + "multer": "2.1.1", + "path-to-regexp": "8.3.0", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/@nestjs/platform-express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/platform-express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@nestjs/platform-express/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/swagger": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.6.tgz", + "integrity": "sha512-oiXOxMQqDFyv1AKAqFzSo6JPvMEs4uA36Eyz/s2aloZLxUjcLfUMELSLSNQunr61xCPTpwEOShfmO7NIufKXdA==", + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.1", + "lodash": "4.17.23", + "path-to-regexp": "8.3.0", + "swagger-ui-dist": "5.31.0" + }, + "peerDependencies": { + "@fastify/static": "^8.0.0 || ^9.0.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/swagger/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@nestjs/swagger/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/typeorm": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", + "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/package-json/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@nuxt/opencollective": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz", + "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0", + "npm": ">=5.10.0" + } + }, + "node_modules/@nuxtjs/opencollective": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.2.2.tgz", + "integrity": "sha512-69gFVDs7mJfNjv9Zs5DFVD+pvBW+k1TaHSOqUWqAyTTfLcKI/EMYQgvEvziRd+zAFtUOoye6MfWh0qvinGISPw==", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "consola": "^2.3.0", + "node-fetch": "^2.3.0" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/@nuxtjs/opencollective/node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "license": "MIT" + }, + "node_modules/@nuxtjs/opencollective/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/@nuxtjs/opencollective/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nx/angular": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-22.5.4.tgz", + "integrity": "sha512-JHDB6syIJLei0AqbJb/W31yLABBwGN99wrVuoFSk5CpgPy5AVzmlNYCIWnUycuCJyWB4NeMcJ9hiS2ycrbizNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@nx/eslint": "22.5.4", + "@nx/js": "22.5.4", + "@nx/module-federation": "22.5.4", + "@nx/rspack": "22.5.4", + "@nx/web": "22.5.4", + "@nx/webpack": "22.5.4", + "@nx/workspace": "22.5.4", + "@phenomnomnominal/tsquery": "~6.1.4", + "@typescript-eslint/type-utils": "^8.0.0", + "enquirer": "~2.3.6", + "magic-string": "~0.30.2", + "picocolors": "^1.1.0", + "picomatch": "4.0.2", + "semver": "^7.6.3", + "tslib": "^2.3.0", + "webpack-merge": "^5.8.0" + }, + "peerDependencies": { + "@angular-devkit/build-angular": ">= 19.0.0 < 22.0.0", + "@angular-devkit/core": ">= 19.0.0 < 22.0.0", + "@angular-devkit/schematics": ">= 19.0.0 < 22.0.0", + "@angular/build": ">= 19.0.0 < 22.0.0", + "@schematics/angular": ">= 19.0.0 < 22.0.0", + "ng-packagr": ">= 19.0.0 < 22.0.0", + "rxjs": "^6.5.3 || ^7.5.0" + }, + "peerDependenciesMeta": { + "@angular-devkit/build-angular": { + "optional": true + }, + "@angular/build": { + "optional": true + }, + "ng-packagr": { + "optional": true + } + } + }, + "node_modules/@nx/angular/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@nx/devkit": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-22.5.4.tgz", + "integrity": "sha512-+QCmpQZQmEGvi8IurC6bOgUTk+Q0dQo7wkp6V04lskXBztSyasBS0BGy5ic90kY05UlQUd++zRA1VY0jc+Yz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zkochan/js-yaml": "0.0.7", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "minimatch": "10.2.4", + "semver": "^7.6.3", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 21 <= 23 || ^22.0.0-0" + } + }, + "node_modules/@nx/docker": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/docker/-/docker-22.5.4.tgz", + "integrity": "sha512-nO4CKMWoq680dDkQSx8q1bZRI0io0yct1nx1w8NWSnnhKPB5Hbi6tXcFXsrSvoR3C02OyS9TRDw0Vc5eXjW3uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "enquirer": "~2.3.6", + "tslib": "^2.3.0" + } + }, + "node_modules/@nx/esbuild": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/esbuild/-/esbuild-22.5.4.tgz", + "integrity": "sha512-0U34hUn7zvniWBdeO3x8vKLnZV9c/W0Tzl2askc9270g2XX8Z+LwfST6yqHyWnf1VuTpSEtyke7e9XH7CdfSGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@nx/js": "22.5.4", + "picocolors": "^1.1.0", + "tinyglobby": "^0.2.12", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "esbuild": ">=0.19.2 <1.0.0" + }, + "peerDependenciesMeta": { + "esbuild": { + "optional": true + } + } + }, + "node_modules/@nx/eslint": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-22.5.4.tgz", + "integrity": "sha512-LMFpyep6N5Se7v5Ck2icZPBa3krWcuGYpubzjEuG35dQm2f/Fr+vLNfQWvfHiF+gP3eSYuJJPI/E38ifTZ/J5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@nx/js": "22.5.4", + "semver": "^7.6.3", + "tslib": "^2.3.0", + "typescript": "~5.9.2" + }, + "peerDependencies": { + "@zkochan/js-yaml": "0.0.7", + "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "@zkochan/js-yaml": { + "optional": true + } + } + }, + "node_modules/@nx/eslint-plugin": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-22.5.4.tgz", + "integrity": "sha512-nbbSnqxR9JQbqsJJUsJcpGtbqLulYOJG1CQdQ0xP3wntK6qu6XDzosopIMHO8MXNQlDp14hAPavE5hKMQwuawA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@nx/js": "22.5.4", + "@phenomnomnominal/tsquery": "~6.1.4", + "@typescript-eslint/type-utils": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0", + "chalk": "^4.1.0", + "confusing-browser-globals": "^1.0.9", + "globals": "^15.9.0", + "jsonc-eslint-parser": "^2.1.0", + "semver": "^7.6.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.13.2 || ^7.0.0 || ^8.0.0", + "eslint-config-prettier": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/@nx/eslint-plugin/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/@nx/jest": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-22.5.4.tgz", + "integrity": "sha512-jUPj6e++F49x/P8O+vrsLs34AnUjNMK1H8wQ5vKl3XhsCNV1j0ADoCfsIdLHPXnJ7PUd3QOVwn2I9KNT7mAzBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/reporters": "^30.0.2", + "@jest/test-result": "^30.0.2", + "@nx/devkit": "22.5.4", + "@nx/js": "22.5.4", + "@phenomnomnominal/tsquery": "~6.1.4", + "identity-obj-proxy": "3.0.0", + "jest-config": "^30.0.2", + "jest-resolve": "^30.0.2", + "jest-util": "^30.0.2", + "minimatch": "10.2.4", + "picocolors": "^1.1.0", + "resolve.exports": "2.0.3", + "semver": "^7.6.3", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + } + }, + "node_modules/@nx/js": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/js/-/js-22.5.4.tgz", + "integrity": "sha512-RPGDQjPm68ml5vKOk2RhRgNUM51qyMfIkRsKSxTWy0EpMOa7ud0I2bPQyNDMkqP04w8I5GZPD8O8opesDrdmtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.2", + "@babel/plugin-proposal-decorators": "^7.22.7", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-runtime": "^7.23.2", + "@babel/preset-env": "^7.23.2", + "@babel/preset-typescript": "^7.22.5", + "@babel/runtime": "^7.22.6", + "@nx/devkit": "22.5.4", + "@nx/workspace": "22.5.4", + "@zkochan/js-yaml": "0.0.7", + "babel-plugin-const-enum": "^1.0.1", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-typescript-metadata": "^0.3.1", + "chalk": "^4.1.0", + "columnify": "^1.6.0", + "detect-port": "^1.5.1", + "ignore": "^5.0.4", + "js-tokens": "^4.0.0", + "jsonc-parser": "3.2.0", + "npm-run-path": "^4.0.1", + "picocolors": "^1.1.0", + "picomatch": "4.0.2", + "semver": "^7.6.3", + "source-map-support": "0.5.19", + "tinyglobby": "^0.2.12", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "verdaccio": "^6.0.5" + }, + "peerDependenciesMeta": { + "verdaccio": { + "optional": true + } + } + }, + "node_modules/@nx/js/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/@nx/js/node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nx/js/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@nx/module-federation": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/module-federation/-/module-federation-22.5.4.tgz", + "integrity": "sha512-rjYSxbKMrrRHQOh+25GlNI91cads+PyHe1LVNn3/S1NTZH20PObtt+KRuppj6L2Mjg3/gK0WfQoMrvFNRiV48A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/enhanced": "^0.21.2", + "@module-federation/node": "^2.7.21", + "@module-federation/sdk": "^0.21.2", + "@nx/devkit": "22.5.4", + "@nx/js": "22.5.4", + "@nx/web": "22.5.4", + "@rspack/core": "1.6.8", + "express": "^4.21.2", + "http-proxy-middleware": "^3.0.5", + "picocolors": "^1.1.0", + "tslib": "^2.3.0", + "webpack": "^5.101.3" + } + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.6.8.tgz", + "integrity": "sha512-lUeL4mbwGo+nqRKqFDCm9vH2jv9FNMVt1X8jqayWRcOCPlj/2UVMEFgqjR7Pp2vlvnTKq//31KbDBJmDZq31RQ==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.6.8", + "@rspack/binding-darwin-x64": "1.6.8", + "@rspack/binding-linux-arm64-gnu": "1.6.8", + "@rspack/binding-linux-arm64-musl": "1.6.8", + "@rspack/binding-linux-x64-gnu": "1.6.8", + "@rspack/binding-linux-x64-musl": "1.6.8", + "@rspack/binding-wasm32-wasi": "1.6.8", + "@rspack/binding-win32-arm64-msvc": "1.6.8", + "@rspack/binding-win32-ia32-msvc": "1.6.8", + "@rspack/binding-win32-x64-msvc": "1.6.8" + } + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-darwin-arm64": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.6.8.tgz", + "integrity": "sha512-e8CTQtzaeGnf+BIzR7wRMUwKfIg0jd/sxMRc1Vd0bCMHBhSN9EsGoMuJJaKeRrSmy2nwMCNWHIG+TvT1CEKg+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-darwin-x64": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.6.8.tgz", + "integrity": "sha512-ku1XpTEPt6Za11zhpFWhfwrTQogcgi9RJrOUVC4FESiPO9aKyd4hJ+JiPgLY0MZOqsptK6vEAgOip+uDVXrCpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.6.8.tgz", + "integrity": "sha512-fvZX6xZPvBT8qipSpvkKMX5M7yd2BSpZNCZXcefw6gA3uC7LI3gu+er0LrDXY1PtPzVuHTyDx+abwWpagV3PiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.6.8.tgz", + "integrity": "sha512-++XMKcMNrt59HcFBLnRaJcn70k3X0GwkAegZBVpel8xYIAgvoXT5+L8P1ExId/yTFxqedaz8DbcxQnNmMozviw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.6.8.tgz", + "integrity": "sha512-tv3BWkTE1TndfX+DsE1rSTg8fBevCxujNZ3MlfZ22Wfy9x1FMXTJlWG8VIOXmaaJ1wUHzv8S7cE2YUUJ2LuiCg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.6.8.tgz", + "integrity": "sha512-DCGgZ5/in1O3FjHWqXnDsncRy+48cMhfuUAAUyl0yDj1NpsZu9pP+xfGLvGcQTiYrVl7IH9Aojf1eShP/77WGA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.6.8.tgz", + "integrity": "sha512-VUwdhl/lI4m6o1OGCZ9JwtMjTV/yLY5VZTQdEPKb40JMTlmZ5MBlr5xk7ByaXXYHr6I+qnqEm73iMKQvg6iknw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "1.0.7" + } + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.6.8.tgz", + "integrity": "sha512-23YX7zlOZlub+nPGDBUzktb4D5D6ETUAluKjXEeHIZ9m7fSlEYBnGL66YE+3t1DHXGd0OqsdwlvrNGcyo6EXDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.6.8.tgz", + "integrity": "sha512-cFgRE3APxrY4AEdooVk2LtipwNNT/9mrnjdC5lVbsIsz+SxvGbZR231bxDJEqP15+RJOaD07FO1sIjINFqXMEg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.6.8.tgz", + "integrity": "sha512-cIuhVsZYd3o3Neo1JSAhJYw6BDvlxaBoqvgwRkG1rs0ExFmEmgYyG7ip9pFKnKNWph/tmW3rDYypmEfjs1is7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/module-federation/node_modules/@rspack/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.6.8.tgz", + "integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime-tools": "0.21.6", + "@rspack/binding": "1.6.8", + "@rspack/lite-tapable": "1.1.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@nx/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/node/-/node-22.5.4.tgz", + "integrity": "sha512-E9u7mkCaJAcibaxIrpImgrNWlSnZF1qeMyOL593y4mchOmGJC5d8MWFI2wJQQgIWi21OXW9FGUH7KMnMb6KZPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@nx/docker": "22.5.4", + "@nx/eslint": "22.5.4", + "@nx/jest": "22.5.4", + "@nx/js": "22.5.4", + "kill-port": "^1.6.1", + "tcp-port-used": "^1.0.2", + "tslib": "^2.3.0" + } + }, + "node_modules/@nx/nx-darwin-arm64": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-22.5.4.tgz", + "integrity": "sha512-Ib9znwSLQZSZ/9hhg5ODplpNhE/RhGVXzdfRj6YonTuWSj/kH3dLMio+4JEkjRdTQVm06cDW0KdwSgnwovqMGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/nx-darwin-x64": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-22.5.4.tgz", + "integrity": "sha512-DjyXuQMc93MPU2XdRsJYjzbv1tgCzMi+zm7O0gc4x3h+ECFjKkjzQBg67pqGdhE3TV27MAlVRKrgHStyK9iigg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/nx-freebsd-x64": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-22.5.4.tgz", + "integrity": "sha512-DhxdP8AhIfN0yCtFhZQcbp32MVN3L7UiTotYqqnOgwW922NRGSd5e+KEAWiJVrIO6TdgnI7prxpg1hfQQK0WDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-22.5.4.tgz", + "integrity": "sha512-pv1x1afTaLAOxPxVhQneLeXgjclp11f9ORxR7jA4E86bSgc9OL92dLSCkXtLQzqPNOej6SZ2fO+PPHVMZwtaPQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-arm64-gnu": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-22.5.4.tgz", + "integrity": "sha512-mPji9PzleWPvXpmFDKaXpTymRgZkk/hW8JHGhvEZpKHHXMYgTGWC+BqOEM2A4dYC4bu4fi9RrteL7aouRRWJoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-arm64-musl": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-22.5.4.tgz", + "integrity": "sha512-hF/HvEhbCjcFpTgY7RbP1tUTbp0M1adZq4ckyW8mwhDWQ/MDsc8FnOHwCO3Bzy9ZeJM0zQUES6/m0Onz8geaEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-x64-gnu": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-22.5.4.tgz", + "integrity": "sha512-1+vicSYEOtc7CNMoRCjo59no4gFe8w2nGIT127wk1yeW3EJzRVNlOA7Deu10NUUbzLeOvHc8EFOaU7clT+F7XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-x64-musl": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-22.5.4.tgz", + "integrity": "sha512-/KjndxVB14yU0SJOhqADHOWoTy4Y45h5RjW3cxcXlPSJZz7ar1FnlLne1rWMMMUttepc8ku+3T//SGKi2eu+Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-win32-arm64-msvc": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-22.5.4.tgz", + "integrity": "sha512-CrYt9FwhjOI6ZNy/G6YHLJmZuXCFJ24BCxugPXiZ7knDx7eGrr7owGgfht4SSiK3KCX40CvWCBJfqR4ZSgaSUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/nx-win32-x64-msvc": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-22.5.4.tgz", + "integrity": "sha512-g5YByv4XsYwsYZvFe24A9bvfhZA+mwtIQt6qZtEVduZTT1hfhIsq0LXGHhkGoFLYwRMXSracWOqkalY0KT4IQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/playwright": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/playwright/-/playwright-22.5.4.tgz", + "integrity": "sha512-gkHO/YBcqB2vpjxJt2P5Gzl6JqF7RD3Hz47fEb/WPfRbyaephCl68PJTPcBI/KIt+edgpiPz/G0c+0jOmg5/iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@nx/eslint": "22.5.4", + "@nx/js": "22.5.4", + "minimatch": "10.2.4", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@playwright/test": "^1.36.0" + }, + "peerDependenciesMeta": { + "@playwright/test": { + "optional": true + } + } + }, + "node_modules/@nx/rspack": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/rspack/-/rspack-22.5.4.tgz", + "integrity": "sha512-qk2d10JOFEBsK1lY4goQ+RhsOMan8Z6PV07VuLKq1wVLzHMlgH1XSmWRBpk2P0P8I7Vy1e8M+tRRsbna5Xf+AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@nx/js": "22.5.4", + "@nx/module-federation": "22.5.4", + "@nx/web": "22.5.4", + "@phenomnomnominal/tsquery": "~6.1.4", + "@rspack/core": "1.6.8", + "@rspack/dev-server": "^1.1.4", + "@rspack/plugin-react-refresh": "^1.0.0", + "autoprefixer": "^10.4.9", + "browserslist": "^4.26.0", + "css-loader": "^6.4.0", + "enquirer": "~2.3.6", + "express": "^4.21.2", + "http-proxy-middleware": "^3.0.5", + "less-loader": "^11.1.0", + "license-webpack-plugin": "^4.0.2", + "loader-utils": "^2.0.3", + "parse5": "4.0.0", + "picocolors": "^1.1.0", + "postcss": "^8.4.38", + "postcss-import": "~14.1.0", + "postcss-loader": "^8.1.1", + "sass": "^1.85.0", + "sass-embedded": "^1.83.4", + "sass-loader": "^16.0.4", + "source-map-loader": "^5.0.0", + "style-loader": "^3.3.0", + "ts-checker-rspack-plugin": "^1.1.1", + "tslib": "^2.3.0", + "webpack": "^5.101.3", + "webpack-node-externals": "^3.0.0" + }, + "peerDependencies": { + "@module-federation/enhanced": "^0.21.2", + "@module-federation/node": "^2.7.21" + } + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.6.8.tgz", + "integrity": "sha512-lUeL4mbwGo+nqRKqFDCm9vH2jv9FNMVt1X8jqayWRcOCPlj/2UVMEFgqjR7Pp2vlvnTKq//31KbDBJmDZq31RQ==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.6.8", + "@rspack/binding-darwin-x64": "1.6.8", + "@rspack/binding-linux-arm64-gnu": "1.6.8", + "@rspack/binding-linux-arm64-musl": "1.6.8", + "@rspack/binding-linux-x64-gnu": "1.6.8", + "@rspack/binding-linux-x64-musl": "1.6.8", + "@rspack/binding-wasm32-wasi": "1.6.8", + "@rspack/binding-win32-arm64-msvc": "1.6.8", + "@rspack/binding-win32-ia32-msvc": "1.6.8", + "@rspack/binding-win32-x64-msvc": "1.6.8" + } + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-darwin-arm64": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.6.8.tgz", + "integrity": "sha512-e8CTQtzaeGnf+BIzR7wRMUwKfIg0jd/sxMRc1Vd0bCMHBhSN9EsGoMuJJaKeRrSmy2nwMCNWHIG+TvT1CEKg+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-darwin-x64": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.6.8.tgz", + "integrity": "sha512-ku1XpTEPt6Za11zhpFWhfwrTQogcgi9RJrOUVC4FESiPO9aKyd4hJ+JiPgLY0MZOqsptK6vEAgOip+uDVXrCpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.6.8.tgz", + "integrity": "sha512-fvZX6xZPvBT8qipSpvkKMX5M7yd2BSpZNCZXcefw6gA3uC7LI3gu+er0LrDXY1PtPzVuHTyDx+abwWpagV3PiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.6.8.tgz", + "integrity": "sha512-++XMKcMNrt59HcFBLnRaJcn70k3X0GwkAegZBVpel8xYIAgvoXT5+L8P1ExId/yTFxqedaz8DbcxQnNmMozviw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.6.8.tgz", + "integrity": "sha512-tv3BWkTE1TndfX+DsE1rSTg8fBevCxujNZ3MlfZ22Wfy9x1FMXTJlWG8VIOXmaaJ1wUHzv8S7cE2YUUJ2LuiCg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.6.8.tgz", + "integrity": "sha512-DCGgZ5/in1O3FjHWqXnDsncRy+48cMhfuUAAUyl0yDj1NpsZu9pP+xfGLvGcQTiYrVl7IH9Aojf1eShP/77WGA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.6.8.tgz", + "integrity": "sha512-VUwdhl/lI4m6o1OGCZ9JwtMjTV/yLY5VZTQdEPKb40JMTlmZ5MBlr5xk7ByaXXYHr6I+qnqEm73iMKQvg6iknw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "1.0.7" + } + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.6.8.tgz", + "integrity": "sha512-23YX7zlOZlub+nPGDBUzktb4D5D6ETUAluKjXEeHIZ9m7fSlEYBnGL66YE+3t1DHXGd0OqsdwlvrNGcyo6EXDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.6.8.tgz", + "integrity": "sha512-cFgRE3APxrY4AEdooVk2LtipwNNT/9mrnjdC5lVbsIsz+SxvGbZR231bxDJEqP15+RJOaD07FO1sIjINFqXMEg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.6.8.tgz", + "integrity": "sha512-cIuhVsZYd3o3Neo1JSAhJYw6BDvlxaBoqvgwRkG1rs0ExFmEmgYyG7ip9pFKnKNWph/tmW3rDYypmEfjs1is7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/rspack/node_modules/@rspack/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.6.8.tgz", + "integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime-tools": "0.21.6", + "@rspack/binding": "1.6.8", + "@rspack/lite-tapable": "1.1.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@nx/web": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/web/-/web-22.5.4.tgz", + "integrity": "sha512-GH4+TLdFiw4RSUgPwn0KWcu6yHfMu23umidrgVgq9Dmj0fn3i/yfxvfdhMQ6aDiZr831b4tIbTQ7JLNd92ilIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@nx/js": "22.5.4", + "detect-port": "^1.5.1", + "http-server": "^14.1.0", + "picocolors": "^1.1.0", + "tslib": "^2.3.0" + } + }, + "node_modules/@nx/webpack": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-22.5.4.tgz", + "integrity": "sha512-2yfnad1Rwh+VT6L5iCMfTMwtPETxedcjPLMoNpyDmZg7dJj5+Xx51ZibygBefLwCBpZV0xnOZPBxR7xGpQqpSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.2", + "@nx/devkit": "22.5.4", + "@nx/js": "22.5.4", + "@phenomnomnominal/tsquery": "~6.1.4", + "ajv": "^8.12.0", + "autoprefixer": "^10.4.9", + "babel-loader": "^9.1.2", + "browserslist": "^4.26.0", + "copy-webpack-plugin": "^10.2.4", + "css-loader": "^6.4.0", + "css-minimizer-webpack-plugin": "^5.0.0", + "fork-ts-checker-webpack-plugin": "7.2.13", + "less": "^4.1.3", + "less-loader": "^11.1.0", + "license-webpack-plugin": "^4.0.2", + "loader-utils": "^2.0.3", + "mini-css-extract-plugin": "~2.4.7", + "parse5": "4.0.0", + "picocolors": "^1.1.0", + "postcss": "^8.4.38", + "postcss-import": "~14.1.0", + "postcss-loader": "^6.1.1", + "rxjs": "^7.8.0", + "sass": "^1.85.0", + "sass-embedded": "^1.83.4", + "sass-loader": "^16.0.4", + "source-map-loader": "^5.0.0", + "style-loader": "^3.3.0", + "terser-webpack-plugin": "^5.3.3", + "ts-loader": "^9.3.1", + "tsconfig-paths-webpack-plugin": "4.2.0", + "tslib": "^2.3.0", + "webpack": "^5.101.3", + "webpack-dev-server": "^5.2.1", + "webpack-node-externals": "^3.0.0", + "webpack-subresource-integrity": "^5.1.0" + } + }, + "node_modules/@nx/webpack/node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/@nx/workspace": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-22.5.4.tgz", + "integrity": "sha512-TZeuCDy+VN/5zqMYxHw15HKe2Ppcb9WBOebz4bmXE206c8Aop3S9QeLfys00Uobt9ZaYh9QUeN0iFsZm7TNv0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.5.4", + "@zkochan/js-yaml": "0.0.7", + "chalk": "^4.1.0", + "enquirer": "~2.3.6", + "nx": "22.5.4", + "picomatch": "4.0.2", + "semver": "^7.6.3", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + } + }, + "node_modules/@nx/workspace/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/@nx/workspace/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.106.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.106.0.tgz", + "integrity": "sha512-QdsH3rZq480VnOHSHgPYOhjL8O8LBdcnSjM408BpPCCUc0JYYZPG9Gafl9i3OcGk/7137o+gweb4cCv3WAUykg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.19.1.tgz", + "integrity": "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.19.1.tgz", + "integrity": "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.19.1.tgz", + "integrity": "sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.19.1.tgz", + "integrity": "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.19.1.tgz", + "integrity": "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.19.1.tgz", + "integrity": "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.19.1.tgz", + "integrity": "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.19.1.tgz", + "integrity": "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.19.1.tgz", + "integrity": "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.19.1.tgz", + "integrity": "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.19.1.tgz", + "integrity": "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.19.1.tgz", + "integrity": "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.19.1.tgz", + "integrity": "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.19.1.tgz", + "integrity": "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.19.1.tgz", + "integrity": "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.19.1.tgz", + "integrity": "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.19.1.tgz", + "integrity": "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.19.1.tgz", + "integrity": "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.19.1.tgz", + "integrity": "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.19.1.tgz", + "integrity": "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/standard-fonts/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.10" + } + }, + "node_modules/@pdf-lib/upng/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@phenomnomnominal/tsquery": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-6.1.4.tgz", + "integrity": "sha512-3tHlGy/fxjJCHqIV8nelAzbRTNkCUY+k7lqBGKNuQz99H2OKGRt6oU+U2SZs6LYrbOe8mxMFl6kq6gzHapFRkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/esquery": "^1.5.0", + "esquery": "^1.5.0" + }, + "peerDependencies": { + "typescript": "^3 || ^4 || ^5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "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", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.58.tgz", + "integrity": "sha512-mWj5eE4Qc8TbPdGGaaLvBb9XfDPvE1EmZkJQgiGKwchkWH4oAJcRAKMTw7ZHnb1L+t7Ah41sBkAecaIsuUgsug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.58.tgz", + "integrity": "sha512-wFxUymI/5R8bH8qZFYDfAxAN9CyISEIYke+95oZPiv6EWo88aa5rskjVcCpKA532R+klFmdqjbbaD56GNmTF4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.58.tgz", + "integrity": "sha512-ybp3MkPj23VDV9PhtRwdU5qrGhlViWRV5BjKwO6epaSlUD5lW0WyY+roN3ZAzbma/9RrMTgZ/a/gtQq8YXOcqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.58.tgz", + "integrity": "sha512-Evxj3yh7FWvyklUYZa0qTVT9N2zX9TPDqGF056hl8hlCZ9/ndQ2xMv6uw9PD1VlLpukbsqL+/C6M0qwipL0QMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.58.tgz", + "integrity": "sha512-tYeXprDOrEgVHUbPXH6MPso4cM/c6RTkmJNICMQlYdki4hGMh92aj3yU6CKs+4X5gfG0yj5kVUw/L4M685SYag==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.58.tgz", + "integrity": "sha512-N78vmZzP6zG967Ohr+MasCjmKtis0geZ1SOVmxrA0/bklTQSzH5kHEjW5Qn+i1taFno6GEre1E40v0wuWsNOQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.58.tgz", + "integrity": "sha512-l+p4QVtG72C7wI2SIkNQw/KQtSjuYwS3rV6AKcWrRBF62ClsFUcif5vLaZIEbPrCXu5OFRXigXFJnxYsVVZqdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.58.tgz", + "integrity": "sha512-urzJX0HrXxIh0FfxwWRjfPCMeInU9qsImLQxHBgLp5ivji1EEUnOfux8KxPPnRQthJyneBrN2LeqUix9DYrNaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.58.tgz", + "integrity": "sha512-7ijfVK3GISnXIwq/1FZo+KyAUJjL3kWPJ7rViAL6MWeEBhEgRzJ0yEd9I8N9aut8Y8ab+EKFJyRNMWZuUBwQ0A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.58.tgz", + "integrity": "sha512-/m7sKZCS+cUULbzyJTIlv8JbjNohxbpAOA6cM+lgWgqVzPee3U6jpwydrib328JFN/gF9A99IZEnuGYqEDJdww==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.58.tgz", + "integrity": "sha512-6SZk7zMgv+y3wFFQ9qE5P9NnRHcRsptL1ypmudD26PDY+PvFCvfHRkJNfclWnvacVGxjowr7JOL3a9fd1wWhUw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.58.tgz", + "integrity": "sha512-sFqfYPnBZ6xBhMkadB7UD0yjEDRvs7ipR3nCggblN+N4ODCXY6qhg/bKL39+W+dgQybL7ErD4EGERVbW9DAWvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.58.tgz", + "integrity": "sha512-AnFWJdAqB8+IDPcGrATYs67Kik/6tnndNJV2jGRmwlbeNiQQ8GhRJU8ETRlINfII0pqi9k4WWLnb00p1QCxw/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.58.tgz", + "integrity": "sha512-qWhDs6yFGR5xDfdrwiSa3CWGIHxD597uGE/A9xGqytBjANvh4rLCTTkq7szhMV4+Ygh+PMS90KVJ8xWG/TkX4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.7.8.tgz", + "integrity": "sha512-P4fbrQx5hRhAiC8TBTEMCTnNawrIzJLjWwAgrTwRxjgenpjNvimEkQBtSGrXOY+c+MV5Q74P+9wPvVWLKzRkQQ==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.7.8", + "@rspack/binding-darwin-x64": "1.7.8", + "@rspack/binding-linux-arm64-gnu": "1.7.8", + "@rspack/binding-linux-arm64-musl": "1.7.8", + "@rspack/binding-linux-x64-gnu": "1.7.8", + "@rspack/binding-linux-x64-musl": "1.7.8", + "@rspack/binding-wasm32-wasi": "1.7.8", + "@rspack/binding-win32-arm64-msvc": "1.7.8", + "@rspack/binding-win32-ia32-msvc": "1.7.8", + "@rspack/binding-win32-x64-msvc": "1.7.8" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.8.tgz", + "integrity": "sha512-KS6SRc+4VYRdX1cKr1j1HEuMNyEzt7onBS0rkenaiCRRYF0z4WNZNyZqRiuxgM3qZ3TISF7gdmgJQyd4ZB43ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.8.tgz", + "integrity": "sha512-uyXSDKLg2CtqIJrsJDlCqQH80YIPsCUiTToJ59cXAG3v4eke0Qbiv6d/+pV0h/mc0u4inAaSkr5dD18zkMIghw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.8.tgz", + "integrity": "sha512-dD6gSHA18Uj0eqc1FCwwQ5IO5mIckrpYN4H4kPk9Pjau+1mxWvC4y5Lryz1Z8P/Rh1lnQ/wwGE0XL9nd80+LqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.8.tgz", + "integrity": "sha512-m+uBi9mEVGkZ02PPOAYN2BSmmvc00XGa6v9CjV8qLpolpUXQIMzDNG+i1fD5SHp8LO+XWsZJOHypMsT0MzGTGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.8.tgz", + "integrity": "sha512-IAPp2L3yS33MAEkcGn/I1gO+a+WExJHXz2ZlRlL2oFCUGpYi2ZQHyAcJ3o2tJqkXmdqsTiN+OjEVMd/RcLa24g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.8.tgz", + "integrity": "sha512-do/QNzb4GWdXCsipblDcroqRDR3BFcbyzpZpAw/3j9ajvEqsOKpdHZpILT2NZX/VahhjqfqB3k0kJVt3uK7UYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.8.tgz", + "integrity": "sha512-mHtgYTpdhx01i0XNKFYBZyCjtv9YUe/sDfpD1QK4FytPFB+1VpYnmZiaJIMM77VpNsjxGAqWhmUYxi2P6jWifw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@napi-rs/wasm-runtime": "1.0.7" + } + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.8.tgz", + "integrity": "sha512-Mkxg86F7kIT4pM9XvE/1LAGjK5NOQi/GJxKyyiKbUAeKM8XBUizVeNuvKR0avf2V5IDAIRXiH1SX8SpujMJteA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.8.tgz", + "integrity": "sha512-VmTOZ/X7M85lKFNwb2qJpCRzr4SgO42vucq/X7Uz1oSoTPAf8UUMNdi7BPnu+D4lgy6l8PwV804ZyHO3gGsvPA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.8.tgz", + "integrity": "sha512-BK0I4HAwp/yQLnmdJpUtGHcht3x11e9fZwyaiMzznznFc+Oypbf+FS5h+aBgpb53QnNkPpdG7MfAPoKItOcU8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/core": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.7.8.tgz", + "integrity": "sha512-kT6yYo8xjKoDfM7iB8N9AmN9DJIlrs7UmQDbpTu1N4zaZocN1/t2fIAWOKjr5+3eJlZQR2twKZhDVHNLbLPjOw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/runtime-tools": "0.22.0", + "@rspack/binding": "1.7.8", + "@rspack/lite-tapable": "1.1.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/error-codes": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.22.0.tgz", + "integrity": "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.22.0.tgz", + "integrity": "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/error-codes": "0.22.0", + "@module-federation/runtime-core": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime-core": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.22.0.tgz", + "integrity": "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/error-codes": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime-tools": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.22.0.tgz", + "integrity": "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/runtime": "0.22.0", + "@module-federation/webpack-bundler-runtime": "0.22.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/sdk": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.22.0.tgz", + "integrity": "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@rspack/core/node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.22.0.tgz", + "integrity": "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/runtime": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@rspack/dev-server": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rspack/dev-server/-/dev-server-1.2.1.tgz", + "integrity": "sha512-e/ARvskYn2Qdd02qLvc0i6H9BnOmzP0xGHS2XCr7GZ3t2k5uC5ZlLkeN1iEebU0FkAW+6ot89NahFo3nupKuww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@rspack/core": "*" + } + }, + "node_modules/@rspack/dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@rspack/dev-server/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@rspack/dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/@rspack/dev-server/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rspack/dev-server/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz", + "integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rspack/plugin-react-refresh": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.1.tgz", + "integrity": "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.1.4", + "html-entities": "^2.6.0" + }, + "peerDependencies": { + "react-refresh": ">=0.10.0 <1.0.0", + "webpack-hot-middleware": "2.x" + }, + "peerDependenciesMeta": { + "webpack-hot-middleware": { + "optional": true + } + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@schematics/angular": { + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.5.tgz", + "integrity": "sha512-AndJ17ePYUoqJqiIF9VaXbGAFfOqDcHuAxhwozsQlWDzwgQSOUC/WWeG9hKVCgMD6tE02Sxr2ova9DiBKsLQNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "21.1.5", + "@angular-devkit/schematics": "21.1.5", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/core": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.1.0.tgz", + "integrity": "sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.0.tgz", + "integrity": "sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.1.tgz", + "integrity": "sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", + "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@swc-node/core": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@swc-node/core/-/core-1.14.1.tgz", + "integrity": "sha512-jrt5GUaZUU6cmMS+WTJEvGvaB6j1YNKPHPzC2PUi2BjaFbtxURHj6641Az6xN7b665hNniAIdvjxWcRml5yCnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@swc/core": ">= 1.13.3", + "@swc/types": ">= 0.1" + } + }, + "node_modules/@swc-node/register": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@swc-node/register/-/register-1.11.1.tgz", + "integrity": "sha512-VQ0hJ5jX31TVv/fhZx4xJRzd8pwn6VvzYd2tGOHHr2TfXGCBixZoqdPDXTiEoJLCTS2MmvBf6zyQZZ0M8aGQCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc-node/core": "^1.14.1", + "@swc-node/sourcemap-support": "^0.6.1", + "colorette": "^2.0.20", + "debug": "^4.4.1", + "oxc-resolver": "^11.6.1", + "pirates": "^4.0.7", + "tslib": "^2.8.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@swc/core": ">= 1.4.13", + "typescript": ">= 4.3" + } + }, + "node_modules/@swc-node/sourcemap-support": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@swc-node/sourcemap-support/-/sourcemap-support-0.6.1.tgz", + "integrity": "sha512-ovltDVH5QpdHXZkW138vG4+dgcNsxfwxHVoV6BtmTbz2KKl1A8ZSlbdtxzzfNjCjbpayda8Us9eMtcHobm38dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map-support": "^0.5.21", + "tslib": "^2.8.1" + } + }, + "node_modules/@swc-node/sourcemap-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@swc-node/sourcemap-support/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@swc/core": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.18.tgz", + "integrity": "sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.25" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.18", + "@swc/core-darwin-x64": "1.15.18", + "@swc/core-linux-arm-gnueabihf": "1.15.18", + "@swc/core-linux-arm64-gnu": "1.15.18", + "@swc/core-linux-arm64-musl": "1.15.18", + "@swc/core-linux-x64-gnu": "1.15.18", + "@swc/core-linux-x64-musl": "1.15.18", + "@swc/core-win32-arm64-msvc": "1.15.18", + "@swc/core-win32-ia32-msvc": "1.15.18", + "@swc/core-win32-x64-msvc": "1.15.18" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.18.tgz", + "integrity": "sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.18.tgz", + "integrity": "sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.18.tgz", + "integrity": "sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.18.tgz", + "integrity": "sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.18.tgz", + "integrity": "sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.18.tgz", + "integrity": "sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.18.tgz", + "integrity": "sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.18.tgz", + "integrity": "sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.18.tgz", + "integrity": "sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.18.tgz", + "integrity": "sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", + "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@swc/types": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/esquery": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/esquery/-/esquery-1.5.4.tgz", + "integrity": "sha512-yYO4Q8H+KJHKW1rEeSzHxcZi90durqYgWVfnh5K6ZADVBjBv2e1NEveYX5yT2bffgN7RqzH3k9930m+i2yBoMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/jsdom/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/@types/jsdom/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/@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/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/moment": { + "version": "2.11.29", + "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.11.29.tgz", + "integrity": "sha512-D5WIgbLYQzvgfsDnBhZFSTnt/BjGPOE+Jsh3k1BYYijJAkrn7ceeLvU4jtjKKXXuXN42O3ARlU4D/P9ezbQYFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz", + "integrity": "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/pdfkit": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.5.tgz", + "integrity": "sha512-T3ZHnvF91HsEco5ClhBCOuBwobZfPcI2jaiSHybkkKYq4KhVIIurod94JVKvDIG0JXT6o3KiERC0X0//m8dyrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pdfmake": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@types/pdfmake/-/pdfmake-0.1.21.tgz", + "integrity": "sha512-rDmJr/jzUZSg/AzWYAMVBS4z4weZKTOtrD6Jlt+hzZu87bkIe7WVA02+m+uGGopyTUazFoWYT6HXxwT68Nqfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/pdfkit": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz", + "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/type-utils": "8.57.1", + "@typescript-eslint/utils": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.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.57.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/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/@typescript-eslint/parser": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.1.tgz", + "integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", + "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.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz", + "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.1", + "@typescript-eslint/types": "^8.57.1", + "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.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz", + "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1" + }, + "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.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz", + "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==", + "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.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz", + "integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1", + "@typescript-eslint/utils": "8.57.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.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.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz", + "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==", + "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.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz", + "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.1", + "@typescript-eslint/tsconfig-utils": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.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.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz", + "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1" + }, + "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.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz", + "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", + "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@zkochan/js-yaml": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", + "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "devOptional": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/algoliasearch": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.46.2.tgz", + "integrity": "sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.12.2", + "@algolia/client-abtesting": "5.46.2", + "@algolia/client-analytics": "5.46.2", + "@algolia/client-common": "5.46.2", + "@algolia/client-insights": "5.46.2", + "@algolia/client-personalization": "5.46.2", + "@algolia/client-query-suggestions": "5.46.2", + "@algolia/client-search": "5.46.2", + "@algolia/ingestion": "1.46.2", + "@algolia/monitoring": "1.46.2", + "@algolia/recommend": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/angular-eslint": { + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-21.3.0.tgz", + "integrity": "sha512-K4+I41fSxzOavbv87EOnG5rdXiscs31j5pIAQG0aX21OHdSSjFCKOAQNmj8zv5OxUuYj5uYRToR3PuKQSN6i/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": ">= 21.0.0 < 22.0.0", + "@angular-devkit/schematics": ">= 21.0.0 < 22.0.0", + "@angular-eslint/builder": "21.3.0", + "@angular-eslint/eslint-plugin": "21.3.0", + "@angular-eslint/eslint-plugin-template": "21.3.0", + "@angular-eslint/schematics": "21.3.0", + "@angular-eslint/template-parser": "21.3.0", + "@typescript-eslint/types": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0" + }, + "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": "*", + "typescript-eslint": "^8.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true, + "license": "MIT" + }, + "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==", + "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-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "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/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "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==", + "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.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.3.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-jest/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/babel-jest/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-const-enum": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-const-enum/-/babel-plugin-const-enum-1.2.0.tgz", + "integrity": "sha512-o1m/6iyyFnp9MRsK1dHF3bneqyf3AlM2q3A/YbgQr2pCat6B6XJVDv2TXqzfY2RYUi4mak6WAksSBPlyYGx9dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.3.3", + "@babel/traverse": "^7.16.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.16.tgz", + "integrity": "sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.7", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.7.tgz", + "integrity": "sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.7" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-typescript-metadata": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-typescript-metadata/-/babel-plugin-transform-typescript-metadata-0.3.2.tgz", + "integrity": "sha512-mWEvCQTgXQf48yDqgN7CH50waTyYBeP2Lpqx4nNWab9sxEpdXVeKgfj1qYI2/TgUPQtNFZ85i3PemRtnXVYYJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", + "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/beasties": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", + "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "css-select": "^6.0.0", + "css-what": "^7.0.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/beasties/node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/beasties/node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/better-sqlite3": { + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz", + "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.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==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacache": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/cacache/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001779", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz", + "integrity": "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz", + "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, + "node_modules/cli-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz", + "integrity": "sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==", + "license": "ISC", + "dependencies": { + "ansi-regex": "^2.1.1", + "d": "^1.0.1", + "es5-ext": "^0.10.51", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.7" + } + }, + "node_modules/cli-color/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "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==", + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/columnify": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", + "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", + "integrity": "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^12.0.2", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 12.20.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/css-declaration-sorter": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz", + "integrity": "sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/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/cssstyle/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/cssstyle/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/cssstyle/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/cssstyle/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/cssstyle/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/cssstyle/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/csv-parser": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-2.3.3.tgz", + "integrity": "sha512-czcyxc4/3Tt63w0oiK1zsnRgRD4PkqWaRSJ6eef63xC0f+5LVLuGdSYEcJwGp2euPgRHx+jmlH2Lb49anb1CGQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0", + "through2": "^3.0.1" + }, + "bin": { + "csv-parser": "bin/csv-parser" + }, + "engines": { + "node": ">= 8.16.0" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "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/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "license": "MIT" + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-port": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", + "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "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-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "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/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.27.4.tgz", + "integrity": "sha512-3xhVMcJ8Odvb1QjlWnjBGSYVYESsi3/oJYwLyVvbHOb2CiV4mFtD6x8Lk6JFnRxwEE3fUeVuJLbIxyVQWa867g==", + "dev": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "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.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "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.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-playwright": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-1.8.3.tgz", + "integrity": "sha512-h87JPFHkz8a6oPhn8GRGGhSQoAJjx0AkOv1jME6NoMk2FpEsfvfJJNaQDxLSqSALkCr0IJXPGTnp6SIRVu5Nqg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "examples" + ], + "dependencies": { + "globals": "^13.23.0" + }, + "engines": { + "node": ">=16.6.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0", + "eslint-plugin-jest": ">=25" + }, + "peerDependenciesMeta": { + "eslint-plugin-jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-playwright/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/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/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/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/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "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-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-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/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/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/file-type": { + "version": "21.3.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.2.tgz", + "integrity": "sha512-DLkUvGwep3poOV2wpzbHCOnSKGk1LzyXTv+aHFgN2VFl96wnp8YA9YjO2qPzg5PuL8q/SW9Pdi6WTkYOIh995w==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/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/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-file-up": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-2.0.1.tgz", + "integrity": "sha512-qVdaUhYO39zmh28/JLQM5CoYN9byEOKEH4qfa8K1eNV17W0UUMJ9WgbR/hHFH+t5rcl+6RTb5UC7ck/I+uRkpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-2.0.0.tgz", + "integrity": "sha512-WgZ+nKbELDa6N3i/9nrHeNznm+lY3z4YfhDDWgW+5P0pdmMj26bxaxU11ookgY3NyP9GC7HvZ9etp0jRFqGEeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-file-up": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "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==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz", + "integrity": "sha512-fR3WRkOb4bQdWB/y7ssDUlVdrclvwtyCUIHCfivAoYxq9dF7XfrDKbMdZIfwJ7hxIAqkYSGeU7lLJE6xrxIBdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "vue-template-compiler": "*", + "webpack": "^5.11.0" + }, + "peerDependenciesMeta": { + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/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/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/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/fork-ts-checker-webpack-plugin/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/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/fork-ts-checker-webpack-plugin/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/fork-ts-checker-webpack-plugin/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-them-args": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/get-them-args/-/get-them-args-1.3.2.tgz", + "integrity": "sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", + "dev": true, + "license": "(Apache-2.0 OR MPL-1.1)" + }, + "node_modules/has-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==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "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.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hono": { + "version": "4.12.8", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.8.tgz", + "integrity": "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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-middleware": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/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/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dev": true, + "license": "MIT", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "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/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-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-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-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==", + "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": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is2": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + }, + "engines": { + "node": ">=v0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "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==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.3.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "p-limit": "^3.1.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/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/jest-circus/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/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/jest-config": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "parse-json": "^5.2.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/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/jest-config/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-diff/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/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/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/jest-environment-jsdom": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.3.0.tgz", + "integrity": "sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/environment-jsdom-abstract": "30.3.0", + "jsdom": "^26.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-node": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-haste-map/node_modules/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils/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/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/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/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-preset-angular": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-16.0.0.tgz", + "integrity": "sha512-FVo98EZiJ9cwHeteJozCCIkgJecytt1tu0t8DrAMTyyQ4x/seeZmctkWXP0J9uGyARS0Kcwd+f2YeKqKQOB2yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment-jsdom-abstract": "^30.0.0", + "bs-logger": "^0.2.6", + "esbuild-wasm": ">=0.23.0", + "jest-util": "^30.0.0", + "pretty-format": "^30.0.0", + "ts-jest": "^29.4.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "optionalDependencies": { + "esbuild": ">=0.23.0" + }, + "peerDependencies": { + "@angular/compiler-cli": ">=19.0.0 <22.0.0", + "@angular/core": ">=19.0.0 <22.0.0", + "@angular/platform-browser": ">=19.0.0 <22.0.0", + "@angular/platform-browser-dynamic": ">=19.0.0 <22.0.0", + "jest": "^30.0.0", + "jsdom": ">=26.0.0", + "typescript": ">=5.5" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/jest-preset-angular/node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve/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/jest-resolve/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/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/jest-runner/node_modules/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/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/jest-runtime/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/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/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/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/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/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/jest-watcher": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.3.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/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/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-worker/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/jest-worker/node_modules/chalk/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/jest-worker/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", + "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jpeg-exif": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz", + "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/jsdom": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.0.tgz", + "integrity": "sha512-9FshNB6OepopZ08unmmGpsF7/qCjxGPbo3NbgfJAnPeHXnsODE9WWffXZtRFRFe0ntzaAOcSKNJFz8wiyvF1jQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@asamuzakjp/dom-selector": "^7.0.2", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", + "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peer": true, + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/jsdom/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", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0", + "peer": true + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/undici": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz", + "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "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/jsonc-eslint-parser": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz", + "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/jsonc-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/kill-port": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/kill-port/-/kill-port-1.6.1.tgz", + "integrity": "sha512-un0Y55cOM7JKGaLnGja28T38tDDop0AQ8N0KlAdyh+B1nmMoX8AnNmqPNZbS3mUMgiST51DCVqmbFT1gNJpVNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-them-args": "1.3.2", + "shell-exec": "1.0.2" + }, + "bin": { + "kill-port": "cli.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/koa": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.0.3.tgz", + "integrity": "sha512-MeuwbCoN1daWS32/Ni5qkzmrOtQO2qrnfdxDHjrm6s4b59yG4nexAJ0pTEFyzjLp0pBVO80CZp0vW8Ze30Ebow==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^1.3.8", + "content-disposition": "~0.5.4", + "content-type": "^1.0.5", + "cookies": "~0.9.1", + "delegates": "^1.0.0", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.5.0", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/koa/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/koa/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/launch-editor": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.1.tgz", + "integrity": "sha512-lPSddlAAluRKJ7/cjRFoXUFzaX7q/YKI7yPHuEvSJVqoXvFnJov1/Ud87Aa4zULIbA9Nja4mSPK8l0z/7eV2wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/less": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/less/-/less-4.6.4.tgz", + "integrity": "sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^3.0.5", + "parse-node-version": "^1.0.1" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.4.tgz", + "integrity": "sha512-6/GrYaB6QcW6Vj+/9ZPgKKs6G10YZai/l/eJ4SLwbzqNTBsAqt5hSLVF47TgsiBxV1P6eAU0GYRH3YRuQU9V3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.12.40", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.40.tgz", + "integrity": "sha512-HKGs7GowShNls3Zh+7DTr6wYpPk5jC78l508yQQY3e8ZgJChM3A9JZghmMJZuK+5bogSfuTafpjksGSR3aMIEg==", + "license": "MIT" + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "license": "ISC", + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lmdb": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.4.tgz", + "integrity": "sha512-+Y2DqovevLkb6DrSQ6SXTYLEd6kvlRbhsxzgJrk7BUfOVA/mt21ak6pFDZDKxiAczHMWxrb02kXBTSTIA0O94A==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.4.4", + "@lmdb/lmdb-darwin-x64": "3.4.4", + "@lmdb/lmdb-linux-arm": "3.4.4", + "@lmdb/lmdb-linux-arm64": "3.4.4", + "@lmdb/lmdb-linux-x64": "3.4.4", + "@lmdb/lmdb-win32-arm64": "3.4.4", + "@lmdb/lmdb-win32-x64": "3.4.4" + } + }, + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/load-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "engines": { + "node": ">=13.2.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "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.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash.clonedeepwith": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeepwith/-/lodash.clonedeepwith-4.5.0.tgz", + "integrity": "sha512-QRBRSxhbtsX1nc0baxSkkK5WlVTTm/s48DSukcGcWZwIyI8Zz+lB+kFiELJXtzfH4Aj6kMWQ1VWW4U5uUDgZMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "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/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", + "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/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/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/memoizee/node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "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/mime-types/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/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/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.7.tgz", + "integrity": "sha512-euWmddf0sk9Nv1O0gfeeUAvAkoSlWncNLF77C0TP2+WoPvy8mAHKOzMajcCz2dzvyt3CNgxb1obIEVFIRxaipg==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-fetch/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/msgpackr": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.9.tgz", + "integrity": "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==", + "dev": true, + "license": "MIT", + "optional": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz", + "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/needle": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nest-csv-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nest-csv-parser/-/nest-csv-parser-2.0.4.tgz", + "integrity": "sha512-WUn8kmJuiyJzybT3OLhX2GRlBQrKSFjZp/L6upTt2Z+84IYcPG1SZuLfWoQaN23x2v75o51NWuKO3iuT9zXJaw==", + "license": "MIT", + "dependencies": { + "@nestjs/common": "7.3.2", + "@nestjs/core": "7.3.2", + "class-transformer": "0.2.3", + "csv-parser": "2.3.3", + "reflect-metadata": "0.1.13", + "rxjs": "6.6.0" + } + }, + "node_modules/nest-csv-parser/node_modules/@nestjs/common": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-7.3.2.tgz", + "integrity": "sha512-qK8szqv0PuHDVDPbQjVV+Z91QasDIe7cbzULqtNDARIRkgU4LGk/Q+32E3kBWfp8gh5axRkgVuKtpCiq2s55Cg==", + "license": "MIT", + "dependencies": { + "axios": "0.19.2", + "cli-color": "2.0.0", + "iterare": "1.2.1", + "tslib": "2.0.0", + "uuid": "8.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "reflect-metadata": "^0.1.12", + "rxjs": "^6.0.0" + } + }, + "node_modules/nest-csv-parser/node_modules/@nestjs/core": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-7.3.2.tgz", + "integrity": "sha512-oL1iDfkJyM/0L+LKS+/Yo7GUJkiEcYsZY/ndONdiv1XqiBC5t76DDXry9CuJmcRH36FJ+QbI5jwpKXjRa9D5dQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@nuxtjs/opencollective": "0.2.2", + "fast-safe-stringify": "2.0.7", + "iterare": "1.2.1", + "object-hash": "2.0.3", + "path-to-regexp": "3.2.0", + "tslib": "2.0.0", + "uuid": "8.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^6.0.0" + } + }, + "node_modules/nest-csv-parser/node_modules/axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "license": "MIT", + "dependencies": { + "follow-redirects": "1.5.10" + } + }, + "node_modules/nest-csv-parser/node_modules/class-transformer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.2.3.tgz", + "integrity": "sha512-qsP+0xoavpOlJHuYsQJsN58HXSl8Jvveo+T37rEvCEeRfMWoytAyR0Ua/YsFgpM6AZYZ/og2PJwArwzJl1aXtQ==", + "license": "MIT" + }, + "node_modules/nest-csv-parser/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/nest-csv-parser/node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "license": "MIT" + }, + "node_modules/nest-csv-parser/node_modules/follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "license": "MIT", + "dependencies": { + "debug": "=3.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/nest-csv-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/nest-csv-parser/node_modules/path-to-regexp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", + "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==", + "license": "MIT" + }, + "node_modules/nest-csv-parser/node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "license": "Apache-2.0" + }, + "node_modules/nest-csv-parser/node_modules/rxjs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/nest-csv-parser/node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/nest-csv-parser/node_modules/tslib": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", + "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "license": "0BSD" + }, + "node_modules/nest-csv-parser/node_modules/uuid": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", + "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, + "node_modules/ngx-toastr": { + "version": "20.0.5", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-20.0.5.tgz", + "integrity": "sha512-JcGu1Cbl+0SovPhxma72ygGeZHtpHWWKwBCyiabb+MSWYtXu/SOwEZ2HTWtZ4wcEYOOiy9tDQZgiEKWXpibpRw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + }, + "peerDependencies": { + "@angular/common": "^21.0.0", + "@angular/core": "^21.0.0", + "rxjs": "^7.8.2" + } + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-package-arg": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", + "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nx": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/nx/-/nx-22.5.4.tgz", + "integrity": "sha512-L8wL7uCjnmpyvq4r2mN9s+oriUE4lY+mX9VgOpjj0ucRd5nzaEaBQppVs0zQGkbKC0BnHS8PGtnAglspd5Gh1Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@napi-rs/wasm-runtime": "0.2.4", + "@yarnpkg/lockfile": "^1.1.0", + "@yarnpkg/parsers": "3.0.2", + "@zkochan/js-yaml": "0.0.7", + "axios": "^1.12.0", + "cli-cursor": "3.1.0", + "cli-spinners": "2.6.1", + "cliui": "^8.0.1", + "dotenv": "~16.4.5", + "dotenv-expand": "~11.0.6", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "figures": "3.2.0", + "flat": "^5.0.2", + "front-matter": "^4.0.2", + "ignore": "^7.0.5", + "jest-diff": "^30.0.2", + "jsonc-parser": "3.2.0", + "lines-and-columns": "2.0.3", + "minimatch": "10.2.4", + "node-machine-id": "1.1.12", + "npm-run-path": "^4.0.1", + "open": "^8.4.0", + "ora": "5.3.0", + "picocolors": "^1.1.0", + "resolve.exports": "2.0.3", + "semver": "^7.6.3", + "string-width": "^4.2.3", + "tar-stream": "~2.2.0", + "tmp": "~0.2.1", + "tree-kill": "^1.2.2", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0", + "yaml": "^2.6.0", + "yargs": "^17.6.2", + "yargs-parser": "21.1.1" + }, + "bin": { + "nx": "bin/nx.js", + "nx-cloud": "bin/nx-cloud.js" + }, + "optionalDependencies": { + "@nx/nx-darwin-arm64": "22.5.4", + "@nx/nx-darwin-x64": "22.5.4", + "@nx/nx-freebsd-x64": "22.5.4", + "@nx/nx-linux-arm-gnueabihf": "22.5.4", + "@nx/nx-linux-arm64-gnu": "22.5.4", + "@nx/nx-linux-arm64-musl": "22.5.4", + "@nx/nx-linux-x64-gnu": "22.5.4", + "@nx/nx-linux-x64-musl": "22.5.4", + "@nx/nx-win32-arm64-msvc": "22.5.4", + "@nx/nx-win32-x64-msvc": "22.5.4" + }, + "peerDependencies": { + "@swc-node/register": "^1.11.1", + "@swc/core": "^1.15.8" + }, + "peerDependenciesMeta": { + "@swc-node/register": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/nx/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", + "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/nx/node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/nx/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/nx/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/nx/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/nx/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/nx/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/nx/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/nx/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/nx/node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/nx/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/nx/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/nx/node_modules/ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nx/node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "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-hash": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", + "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "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": "9.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.0.0.tgz", + "integrity": "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.2.2", + "string-width": "^8.1.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ordered-binary": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/oxc-resolver": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.19.1.tgz", + "integrity": "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.19.1", + "@oxc-resolver/binding-android-arm64": "11.19.1", + "@oxc-resolver/binding-darwin-arm64": "11.19.1", + "@oxc-resolver/binding-darwin-x64": "11.19.1", + "@oxc-resolver/binding-freebsd-x64": "11.19.1", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", + "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", + "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", + "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", + "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", + "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", + "@oxc-resolver/binding-linux-x64-musl": "11.19.1", + "@oxc-resolver/binding-openharmony-arm64": "11.19.1", + "@oxc-resolver/binding-wasm32-wasi": "11.19.1", + "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", + "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", + "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" + } + }, + "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/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.0.4", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.4.tgz", + "integrity": "sha512-RplP/pDW0NNNDh3pnaoIWYPvNenS7UqMbXyvMqJczosiFWTeGGwJC2NQBLqKf4rGLFfwCOnntw1aEp9Jiqm1MA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", + "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0", + "parse5": "^8.0.0", + "parse5-sax-parser": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream/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/parse5-html-rewriting-stream/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", + "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser/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/parse5-sax-parser/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "license": "MIT", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "node_modules/pdf-lib/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/pdf-lib/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/pdf-to-printer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/pdf-to-printer/-/pdf-to-printer-5.7.0.tgz", + "integrity": "sha512-CBZKTdKXjZ+ENiF6GcDUQVFq0pJOiM4LhP88XXN1rJ2lo5rEo6uPPYm8l010QYSapNEy33WYcJ+5lDy6d8mTEw==", + "license": "MIT" + }, + "node_modules/pdfmake": { + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.23.tgz", + "integrity": "sha512-A/IksoKb/ikOZH1edSDJ/2zBbqJKDghD4+fXn3rT7quvCJDlsZMs3NmIB3eajLMMFU9Bd3bZPVvlUMXhvFI+bQ==", + "license": "MIT", + "dependencies": { + "@foliojs-fork/linebreak": "^1.1.2", + "@foliojs-fork/pdfkit": "^0.15.3", + "iconv-lite": "^0.7.1", + "xmldoc": "^2.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pdfmake/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/piscina": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.4.tgz", + "integrity": "sha512-7uU4ZnKeQq22t9AsmHGD2w4OYQGonwFnTypDypaWi7Qr2EvQIFVtG8J5D/3bE7W123Wdc9+v4CZDu5hJXVCtBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.4" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkijs": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", + "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + }, + "node_modules/portfinder": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", + "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-loader": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.1.tgz", + "integrity": "sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^2.5.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-loader/node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss-loader/node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/postcss-loader/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/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/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rambda": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-9.4.2.tgz", + "integrity": "sha512-++euMfxnl7OgaEKwXh9QqThOjMeta2HH001N1v4mYQzBjJBnmXBh2BCK6dZAbICFVXOFUVD3xFG0R3ZPU0mxXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.58.tgz", + "integrity": "sha512-v1FCjMZCan7f+xGAHBi+mqiE4MlH7I+SXEHSQSJoMOGNNB2UYtvMiejsq9YuUOiZjNeUeV/a21nSFbrUR+4ZCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.106.0", + "@rolldown/pluginutils": "1.0.0-beta.58" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.58", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.58", + "@rolldown/binding-darwin-x64": "1.0.0-beta.58", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.58", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.58", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.58", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.58", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.58", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.58", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.58", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.58", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.58", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.58" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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==", + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", + "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.98.0.tgz", + "integrity": "sha512-Do7u6iRb6K+lrllcTkB1BXcHwOxcKe3rEfOF/GcCLE2w3WpddakRAosJOHFUR37DpsvimQXEt5abs3NzUjEIqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.1.5", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.98.0", + "sass-embedded-android-arm": "1.98.0", + "sass-embedded-android-arm64": "1.98.0", + "sass-embedded-android-riscv64": "1.98.0", + "sass-embedded-android-x64": "1.98.0", + "sass-embedded-darwin-arm64": "1.98.0", + "sass-embedded-darwin-x64": "1.98.0", + "sass-embedded-linux-arm": "1.98.0", + "sass-embedded-linux-arm64": "1.98.0", + "sass-embedded-linux-musl-arm": "1.98.0", + "sass-embedded-linux-musl-arm64": "1.98.0", + "sass-embedded-linux-musl-riscv64": "1.98.0", + "sass-embedded-linux-musl-x64": "1.98.0", + "sass-embedded-linux-riscv64": "1.98.0", + "sass-embedded-linux-x64": "1.98.0", + "sass-embedded-unknown-all": "1.98.0", + "sass-embedded-win32-arm64": "1.98.0", + "sass-embedded-win32-x64": "1.98.0" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.98.0.tgz", + "integrity": "sha512-6n4RyK7/1mhdfYvpP3CClS3fGoYqDvRmLClCESS6I7+SAzqjxvGG6u5Fo+cb1nrPNbbilgbM4QKdgcgWHO9NCA==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "sass": "1.98.0" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.98.0.tgz", + "integrity": "sha512-LjGiMhHgu7VL1n7EJxTCre1x14bUsWd9d3dnkS2rku003IWOI/fxc7OXgaKagoVzok1kv09rzO3vFXJR5ZeONQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.98.0.tgz", + "integrity": "sha512-M9Ra98A6vYJHpwhoC/5EuH1eOshQ9ZyNwC8XifUDSbRl/cGeQceT1NReR9wFj3L7s1pIbmes1vMmaY2np0uAKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.98.0.tgz", + "integrity": "sha512-WPe+0NbaJIZE1fq/RfCZANMeIgmy83x4f+SvFOG7LhUthHpZWcOcrPTsCKKmN3xMT3iw+4DXvqTYOCYGRL3hcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.98.0.tgz", + "integrity": "sha512-zrD25dT7OHPEgLWuPEByybnIfx4rnCtfge4clBgjZdZ3lF6E7qNLRBtSBmoFflh6Vg0RlEjJo5VlpnTMBM5MQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.98.0.tgz", + "integrity": "sha512-cgr1z9rBnCdMf8K+JabIaYd9Rag2OJi5mjq08XJfbJGMZV/TA6hFJCLGkr5/+ZOn4/geTM5/3aSfQ8z5EIJAOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.98.0.tgz", + "integrity": "sha512-OLBOCs/NPeiMqTdOrMFbVHBQFj19GS3bSVSxIhcCq16ZyhouUkYJEZjxQgzv9SWA2q6Ki8GCqp4k6jMeUY9dcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.98.0.tgz", + "integrity": "sha512-03baQZCxVyEp8v1NWBRlzGYrmVT/LK7ZrHlF1piscGiGxwfdxoLXVuxsylx3qn/dD/4i/rh7Bzk7reK1br9jvQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": "glibc", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.98.0.tgz", + "integrity": "sha512-axOE3t2MTBwCtkUCbrdM++Gj0gC0fdHJPrgzQ+q1WUmY9NoNMGqflBtk5mBZaWUeha2qYO3FawxCB8lctFwCtw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": "glibc", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.98.0.tgz", + "integrity": "sha512-OBkjTDPYR4hSaueOGIM6FDpl9nt/VZwbSRpbNu9/eEJcxE8G/vynRugW8KRZmCFjPy8j/jkGBvvS+k9iOqKV3g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": "musl", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.98.0.tgz", + "integrity": "sha512-LeqNxQA8y4opjhe68CcFvMzCSrBuJqYVFbwElEj9bagHXQHTp9xVPJRn6VcrC+0VLEDq13HVXMv7RslIuU0zmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": "musl", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.98.0.tgz", + "integrity": "sha512-7w6hSuOHKt8FZsmjRb3iGSxEzM87fO9+M8nt5JIQYMhHTj5C+JY/vcske0v715HCVj5e1xyTnbGXf8FcASeAIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": "musl", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.98.0.tgz", + "integrity": "sha512-QikNyDEJOVqPmxyCFkci8ZdCwEssdItfjQFJB+D+Uy5HFqcS5Lv3d3GxWNX/h1dSb23RPyQdQc267ok5SbEyJw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": "musl", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.98.0.tgz", + "integrity": "sha512-E7fNytc/v4xFBQKzgzBddV/jretA4ULAPO6XmtBiQu4zZBdBozuSxsQLe2+XXeb0X4S2GIl72V7IPABdqke/vA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": "glibc", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.98.0.tgz", + "integrity": "sha512-VsvP0t/uw00mMNPv3vwyYKUrFbqzxQHnRMO+bHdAMjvLw4NFf6mscpym9Bzf+NXwi1ZNKnB6DtXjmcpcvqFqYg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": "glibc", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.98.0.tgz", + "integrity": "sha512-C4MMzcAo3oEDQnW7L8SBgB9F2Fq5qHPnaYTZRMOH3Mp/7kM4OooBInXpCiiFjLnjY95hzP4KyctVx0uYR6MYlQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "dependencies": { + "sass": "1.98.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.98.0.tgz", + "integrity": "sha512-nP/10xbAiPbhQkMr3zQfXE4TuOxPzWRQe1Hgbi90jv2R4TbzbqQTuZVOaJf7KOAN4L2Bo6XCTRjK5XkVnwZuwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.98.0.tgz", + "integrity": "sha512-/lbrVsfbcbdZQ5SJCWcV0NVPd6YRs+FtAnfedp4WbCkO/ZO7Zt/58MvI4X2BVpRY/Nt5ZBo1/7v2gYcQ+J4svQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/sass-loader": { + "version": "16.0.7", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.7.tgz", + "integrity": "sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sax": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true, + "license": "MIT" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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==", + "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/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-exec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shell-exec/-/shell-exec-1.0.2.tgz", + "integrity": "sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==", + "dev": true, + "license": "MIT" + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "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.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "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/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/sigstore": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "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": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/sql-highlight": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", + "integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==", + "funding": [ + "https://github.com/scriptcoded/sql-highlight?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/scriptcoded" + } + ], + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "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/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-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/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", + "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "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/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.2.0.tgz", + "integrity": "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4.3.1", + "is2": "^2.0.6" + } + }, + "node_modules/tcp-port-used/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/tcp-port-used/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/thingies": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.26.tgz", + "integrity": "sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tldts-core": "^7.0.26" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.26.tgz", + "integrity": "sha512-5WJ2SqFsv4G2Dwi7ZFVRnz6b2H1od39QME1lc2y5Ew3eWiZMAeqOAfWpRP9jHvhUl881406QtZTODvjttJs+ew==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-checker-rspack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-checker-rspack-plugin/-/ts-checker-rspack-plugin-1.3.0.tgz", + "integrity": "sha512-89oK/BtApjdid1j9CGjPGiYry+EZBhsnTAM481/8ipgr/y2IOgCbW1HPnan+fs5FnzlpUgf9dWGNZ4Ayw3Bd8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rspack/lite-tapable": "^1.1.0", + "chokidar": "^3.6.0", + "memfs": "^4.56.10", + "picocolors": "^1.1.1" + }, + "peerDependencies": { + "@rspack/core": "^1.0.0 || ^2.0.0-0", + "typescript": ">=3.8.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + } + } + }, + "node_modules/ts-checker-rspack-plugin/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ts-checker-rspack-plugin/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ts-checker-rspack-plugin/node_modules/memfs": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.11.tgz", + "integrity": "sha512-/GodtwVeKVIHZKLUSr2ZdOxKBC5hHki4JNCU22DoCGPEHr5o2PD5U721zvESKyWwCfTfavFl9WZYgA13OAYK0g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.11", + "@jsonjoy.com/fs-fsa": "4.56.11", + "@jsonjoy.com/fs-node": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-to-fsa": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "@jsonjoy.com/fs-print": "4.56.11", + "@jsonjoy.com/fs-snapshot": "4.56.11", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/ts-checker-rspack-plugin/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/ts-checker-rspack-plugin/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-loader": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/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/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/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/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/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "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-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typeorm": { + "version": "0.3.28", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", + "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", + "license": "MIT", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "ansis": "^4.2.0", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "dayjs": "^1.11.19", + "debug": "^4.4.3", + "dedent": "^1.7.0", + "dotenv": "^16.6.1", + "glob": "^10.5.0", + "reflect-metadata": "^0.2.2", + "sha.js": "^2.4.12", + "sql-highlight": "^6.1.0", + "tslib": "^2.8.1", + "uuid": "^11.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@sap/hana-client": "^2.14.22", + "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0 || ^6.0.0", + "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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/typeorm/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/typeorm/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.1.tgz", + "integrity": "sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.57.1", + "@typescript-eslint/parser": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1", + "@typescript-eslint/utils": "8.57.1" + }, + "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.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", + "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/unique-filename": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/unique-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "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/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^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/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/webpack": { + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/memfs": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.11.tgz", + "integrity": "sha512-/GodtwVeKVIHZKLUSr2ZdOxKBC5hHki4JNCU22DoCGPEHr5o2PD5U721zvESKyWwCfTfavFl9WZYgA13OAYK0g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.11", + "@jsonjoy.com/fs-fsa": "4.56.11", + "@jsonjoy.com/fs-node": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-to-fsa": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "@jsonjoy.com/fs-print": "4.56.11", + "@jsonjoy.com/fs-snapshot": "4.56.11", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", + "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/webpack-dev-server/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/selfsigned": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "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": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "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/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "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/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/xmldoc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-2.0.3.tgz", + "integrity": "sha512-6gRk4NY/Jvg67xn7OzJuxLRsGgiXBaPUQplVJ/9l99uIugxh4FTOewYz5ic8WScj7Xx/2WvhENiQKwkK9RpE4w==", + "license": "MIT", + "dependencies": { + "sax": "^1.4.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2d9c54c --- /dev/null +++ b/package.json @@ -0,0 +1,91 @@ +{ + "name": "@sim-system/source", + "version": "0.0.0", + "license": "MIT", + "scripts": {}, + "private": true, + "dependencies": { + "@angular/common": "~21.1.0", + "@angular/compiler": "~21.1.0", + "@angular/core": "~21.1.0", + "@angular/forms": "~21.1.0", + "@angular/platform-browser": "~21.1.0", + "@angular/platform-browser-dynamic": "~21.1.0", + "@angular/router": "~21.1.0", + "@nestjs/common": "^11.1.17", + "@nestjs/config": "^4.0.3", + "@nestjs/core": "^11.1.17", + "@nestjs/mapped-types": "^2.1.0", + "@nestjs/platform-express": "^11.1.17", + "@nestjs/swagger": "^11.2.6", + "@nestjs/typeorm": "^11.0.0", + "axios": "^1.6.0", + "better-sqlite3": "^12.8.0", + "bootstrap": "^5.3.8", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.4", + "express": "^4.21.2", + "moment": "^2.30.1", + "multer": "^2.1.1", + "nest-csv-parser": "^2.0.4", + "ngx-toastr": "^20.0.5", + "pdf-lib": "^1.17.1", + "pdf-to-printer": "^5.7.0", + "pdfmake": "^0.2.23", + "reflect-metadata": "^0.2.2", + "rxjs": "~7.8.0", + "swagger-ui-express": "^5.0.1", + "typeorm": "^0.3.28" + }, + "devDependencies": { + "@angular-devkit/core": "~21.1.0", + "@angular-devkit/schematics": "~21.1.0", + "@angular/build": "~21.1.0", + "@angular/cli": "~21.1.0", + "@angular/compiler-cli": "~21.1.0", + "@angular/language-service": "~21.1.0", + "@eslint/js": "^9.8.0", + "@nx/angular": "22.5.4", + "@nx/devkit": "22.5.4", + "@nx/esbuild": "22.5.4", + "@nx/eslint": "22.5.4", + "@nx/eslint-plugin": "22.5.4", + "@nx/jest": "22.5.4", + "@nx/js": "22.5.4", + "@nx/node": "^22.5.4", + "@nx/playwright": "22.5.4", + "@nx/web": "22.5.4", + "@nx/workspace": "22.5.4", + "@playwright/test": "^1.36.0", + "@schematics/angular": "~21.1.0", + "@swc-node/register": "~1.11.1", + "@swc/core": "~1.15.5", + "@swc/helpers": "~0.5.18", + "@types/better-sqlite3": "^7.6.13", + "@types/express": "^4.17.21", + "@types/jest": "^30.0.0", + "@types/moment": "^2.11.29", + "@types/multer": "^2.1.0", + "@types/node": "20.19.9", + "@types/pdfmake": "^0.1.21", + "@typescript-eslint/utils": "^8.40.0", + "angular-eslint": "^21.0.1", + "esbuild": "^0.19.2", + "eslint": "^9.8.0", + "eslint-config-prettier": "^10.0.0", + "eslint-plugin-playwright": "^1.6.2", + "jest": "^30.0.2", + "jest-environment-jsdom": "^30.0.2", + "jest-environment-node": "^30.0.2", + "jest-preset-angular": "~16.0.0", + "jest-util": "^30.0.2", + "jsonc-eslint-parser": "^2.1.0", + "nx": "22.5.4", + "prettier": "~3.6.2", + "ts-jest": "^29.4.0", + "ts-node": "10.9.1", + "tslib": "^2.3.0", + "typescript": "~5.9.2", + "typescript-eslint": "^8.40.0" + } +} diff --git a/test-req/REQ-001-global-api-prefix.md b/test-req/REQ-001-global-api-prefix.md new file mode 100644 index 0000000..f94bede --- /dev/null +++ b/test-req/REQ-001-global-api-prefix.md @@ -0,0 +1,188 @@ +# REQ-001 — Globaler API-Prefix + +**Datum:** 2026-03-17 · **Branch:** first-init +**Eltern-Dokument:** `test-req/backend-requirements.md` +**Framework:** Framework-agnostisch (UNBELEGT) — Given/When/Then + +--- + +## Requirement + +| Feld | Wert | +|------|------| +| **ID** | REQ-001 | +| **Titel** | Globaler API-Prefix | +| **Beschreibung** | Alle Endpunkte sind unter dem Prefix `api/v1` erreichbar. Anfragen ohne diesen Prefix werden nicht vom Backend verarbeitet. | +| **Quelle** | `apps/server/src/main.ts` Zeile 20 | +| **Evidenz** | `app.setGlobalPrefix('api/v1')` — NestJS setzt den Prefix ohne führenden Slash; Routing wird intern zu `/api/v1/...` | +| **Priorität** | High | +| **Status** | CONFIRMED | + +--- + +## E) Testdimensionen + +| Dimension | Werte | Belegt durch | +|-----------|-------|--------------| +| **Prefix-Varianten** | Mit Prefix (`/api/v1/...`) / Ohne Prefix (`/...`) / Falscher Prefix (`/api/...`, `/v1/...`, `/api/v2/...`) | `main.ts` Zeile 20 | +| **HTTP-Methoden** | GET, POST, PUT, PATCH, DELETE, OPTIONS | `main.ts` Zeile 16 — `enableCors` methods-Liste | +| **Endpunkt-Auswahl** | Beliebiger existierender Endpunkt (z.B. `articles`, `customers`) | Alle Controller | + +**Keine belegbaren Dimensionen für:** Rollen, Locale, Performance-Schwellen + +--- + +## F) Testmatrix + +| REQ-ID | Feature/Komponente | Szenario | Testtyp | Prio | +/- | Datenvarianten | Erwartetes Ergebnis | Auto | Notes | +|--------|-------------------|----------|---------|------|-----|---------------|---------------------|------|-------| +| REQ-001 | Global Routing | Request mit korrektem Prefix `/api/v1/articles` wird verarbeitet | API | High | + | `GET /api/v1/articles` | HTTP 200, Body `{ success: true, data: [...] }` | Yes | Standard-Happy-Path | +| REQ-001 | Global Routing | Request ohne Prefix `/articles` wird NICHT verarbeitet | API | High | - | `GET /articles` | HTTP 404 | Yes | NestJS gibt 404 bei unbekannter Route | +| REQ-001 | Global Routing | Request mit falschem Prefix `/api/articles` (ohne Version) | API | High | - | `GET /api/articles` | HTTP 404 | Yes | Prefix muss exakt `api/v1` sein | +| REQ-001 | Global Routing | Request mit falschem Prefix `/v1/articles` (ohne `api/`) | API | High | - | `GET /v1/articles` | HTTP 404 | Yes | | +| REQ-001 | Global Routing | Request mit falschem Prefix `/api/v2/articles` (falsche Version) | API | High | - | `GET /api/v2/articles` | HTTP 404 | Yes | Keine zweite API-Version vorhanden | +| REQ-001 | Global Routing | POST-Endpunkt ebenfalls unter `/api/v1/` erreichbar | API | High | + | `POST /api/v1/customers` | HTTP 201 oder 400 (kein 404) | Yes | Alle HTTP-Methoden nutzen denselben Prefix | +| REQ-001 | Global Routing | Root ohne Prefix `GET /` gibt 404 zurück (kein ungeschützter Root) | API | Med | - | `GET /` | HTTP 404 | Yes | INFERIERT: kein Root-Handler ohne Prefix dokumentiert | + +--- + +## G) Testfälle + +--- + +### TC-001-001 — Korrekter Prefix: Endpunkt erreichbar +**REQ-ID:** REQ-001 +**Titel:** GET /api/v1/articles wird mit HTTP 200 beantwortet +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft; DB enthält mindestens 0 Artikel + +**Given** das Backend ist gestartet +**When** `GET /api/v1/articles` wird aufgerufen +**Then** +- HTTP Status ist `200` +- Body enthält `success: true` +- Body enthält Feld `data` (Array) + +**Testdaten:** Kein spezifisches Datum nötig (leere DB erlaubt) +**Orakel:** REQ-001 — `app.setGlobalPrefix('api/v1')`, `main.ts:20` +**Automatisierung:** Yes + +--- + +### TC-001-002 — Fehlender Prefix: Route nicht gefunden +**REQ-ID:** REQ-001 +**Titel:** GET /articles (ohne Prefix) gibt HTTP 404 zurück +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `GET /articles` (ohne `/api/v1`) wird aufgerufen +**Then** +- HTTP Status ist `404` + +**Testdaten:** Keine +**Orakel:** REQ-001; NestJS-Verhalten bei unregistrierter Route +**Automatisierung:** Yes + +--- + +### TC-001-003 — Falscher Prefix: /api/articles +**REQ-ID:** REQ-001 +**Titel:** GET /api/articles (ohne Version) gibt HTTP 404 zurück +**Typ:** API +**Status:** READY + +**Given** das Backend ist gestartet +**When** `GET /api/articles` wird aufgerufen +**Then** +- HTTP Status ist `404` + +**Orakel:** REQ-001 +**Automatisierung:** Yes + +--- + +### TC-001-004 — Falscher Prefix: /v1/articles +**REQ-ID:** REQ-001 +**Titel:** GET /v1/articles (ohne api/) gibt HTTP 404 zurück +**Typ:** API +**Status:** READY + +**Given** das Backend ist gestartet +**When** `GET /v1/articles` wird aufgerufen +**Then** +- HTTP Status ist `404` + +**Orakel:** REQ-001 +**Automatisierung:** Yes + +--- + +### TC-001-005 — Falscher Prefix: falsche Versionsnummer +**REQ-ID:** REQ-001 +**Titel:** GET /api/v2/articles gibt HTTP 404 zurück +**Typ:** API +**Status:** READY + +**Given** das Backend ist gestartet +**When** `GET /api/v2/articles` wird aufgerufen +**Then** +- HTTP Status ist `404` + +**Orakel:** REQ-001; kein zweiter Versionspfad registriert +**Automatisierung:** Yes + +--- + +### TC-001-006 — POST-Endpunkt nutzt denselben Prefix +**REQ-ID:** REQ-001 +**Titel:** POST /api/v1/customers ist routing-seitig erreichbar (kein 404) +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `POST /api/v1/customers` mit leerem Body `{}` aufgerufen wird +**Then** +- HTTP Status ist **NICHT** `404` +- (Erwartet: 400 wegen fehlenden Pflichtfeldern — das ist ok) +- HTTP 404 wäre ein Routing-Fehler → Testfailure + +**Testdaten:** `{}` (leerer Body) +**Orakel:** REQ-001; REQ-003 (ValidationPipe gibt 400 für fehlende Felder) +**Automatisierung:** Yes + +--- + +## H) Coverage Summary + +| Metrik | Wert | +|--------|------| +| Requirements in dieser Datei | 1 (REQ-001) | +| Testfälle gesamt | 6 (TC-001-001 bis TC-001-006) | +| Abgedeckte Szenarien | Positiv-Pfad, 4 × Negativ-Prefix-Varianten, HTTP-Methoden | +| READY | 6 | +| BLOCKED | 0 | + +--- + +## I) Gaps & Blockers + +| ID | Beschreibung | Betroffene REQ | Fehlende Info | +|----|-------------|----------------|---------------| +| GAP-001-1 | Swagger-Docs unter `/api/v1/doc` — ist der Swagger-Endpoint selbst Teil des Prefix-Tests? | REQ-001 | Kein eigener TC, da Swagger als Dev-Tool gilt | +| GAP-001-2 | Kein TC für `OPTIONS /api/v1/articles` (CORS-Preflight) — überschneidet sich mit REQ-005 | REQ-001, REQ-005 | Scope-Entscheidung: wird in REQ-005 abgedeckt | + +--- + +## J) Decision Needed + +| ID | Entscheidung | Betroffene REQ | +|----|-------------|----------------| +| DEC-001-1 | Soll `GET /api/v1/` (Root mit Prefix) einen definierten Response liefern? Derzeit gibt `AppController.GET /` — ohne Prefix — "V1" zurück. Mit Prefix wäre das `/api/v1/`. Verhalten unklar. | REQ-001 | +| DEC-001-2 | Soll bei zukünftigen API-Versionen `/api/v2/` eingeführt werden? Dann müssen Routing-Tests erweitert werden. | REQ-001 | diff --git a/test-req/REQ-002-response-wrapper.md b/test-req/REQ-002-response-wrapper.md new file mode 100644 index 0000000..ededefe --- /dev/null +++ b/test-req/REQ-002-response-wrapper.md @@ -0,0 +1,260 @@ +# REQ-002 — Response-Wrapper (ReS / ReE) + +**Datum:** 2026-03-17 · **Branch:** first-init +**Eltern-Dokument:** `test-req/backend-requirements.md` +**Framework:** Framework-agnostisch (UNBELEGT) — Given/When/Then + +--- + +## Requirement + +| Feld | Wert | +|------|------| +| **ID** | REQ-002 | +| **Titel** | Response-Wrapper ReS / ReE | +| **Beschreibung** | Erfolgreiche Antworten haben exakt die Struktur `{ success: true, data: T }`. Fehlerantworten haben exakt die Struktur `{ success: false, statusCode: number, error: string, message: string[] }`. | +| **Quelle** | `apps/server/src/common/res.model.ts` | +| **Evidenz** | Klassen `ReS` (Zeilen 10–20) und `ReE` (Zeilen 22–40); `ReturnHelper` als Basis mit `success = true` (Zeile 7) | +| **Priorität** | High | +| **Status** | CONFIRMED | + +### Exakte Struktur aus Code + +**ReS (Success):** +``` +{ + success: true, // boolean, immer true (ReturnHelper, Zeile 7) + data: T // generischer Payload (ReS, Zeile 19) +} +``` + +**ReE (Error):** +``` +{ + success: false, // boolean, immer false (FromData setzt ret.success = false, Zeile 25) + statusCode: number, // HTTP-Statuscode (Zeile 33) + message: string[], // Array von Fehlermeldungen (Zeile 37) + error: string // Fehlerbezeichnung (Zeile 39) +} +``` + +--- + +## E) Testdimensionen + +| Dimension | Werte | Belegt durch | +|-----------|-------|--------------| +| **Response-Typ** | Success (ReS) / Error (ReE) | `res.model.ts` — zwei separate Klassen | +| **data-Feldtypen** | Array (z.B. `Article[]`) / Einzelobjekt (z.B. `Article`) / `null` (z.B. nach DELETE) | Controller-Rückgaben | +| **message-Feld** | Array mit einem Element / Array mit mehreren Elementen | `errors.filter.ts` Zeilen 25–27 | +| **statusCode-Werte** | 200, 201, 400, 404, 500 (HTTP-Standard) | `errors.filter.ts` Zeile 16–19 | +| **Fehlerquelle** | HttpException (NestJS-kontrolliert) / Unerwarteter Fehler (non-HttpException) | `errors.filter.ts` `@Catch()` — fängt alles | + +**Nicht belegbare Dimensionen:** Locale, Content-Type-Varianten (kein explizites Content-Type-Handling dokumentiert) + +--- + +## F) Testmatrix + +| REQ-ID | Feature/Komponente | Szenario | Testtyp | Prio | +/- | Datenvarianten | Erwartetes Ergebnis | Auto | Notes | +|--------|-------------------|----------|---------|------|-----|---------------|---------------------|------|-------| +| REQ-002 | ReS Wrapper | Erfolgreicher GET gibt `success: true` zurück | API | High | + | `GET /api/v1/articles` | `{ success: true, data: [...] }` | Yes | | +| REQ-002 | ReS Wrapper | `data` enthält den Nutzdaten-Payload (kein Null bei Erfolg mit Daten) | API | High | + | `GET /api/v1/articles` mit 2 Artikeln in DB | `data.length === 2` | Yes | | +| REQ-002 | ReS Wrapper | `data` ist `null` nach erfolgreichem DELETE | API | High | + | `DELETE /api/v1/articles/:id` | `{ success: true, data: null }` | Yes | Evidenz: Controller gibt `ReS.FromData(null)` | +| REQ-002 | ReS Wrapper | Response enthält KEIN Feld `statusCode` bei Erfolg | API | Med | + | Beliebiger 200-Request | Kein `statusCode` im Body | Yes | ReS-Klasse hat kein statusCode-Feld | +| REQ-002 | ReS Wrapper | Response enthält KEIN Feld `error` bei Erfolg | API | Med | + | Beliebiger 200-Request | Kein `error` im Body | Yes | ReS-Klasse hat kein error-Feld | +| REQ-002 | ReE Wrapper | Fehlerhafte Response hat `success: false` | API | High | - | `GET /api/v1/articles/99999` (404) | `{ success: false, ... }` | Yes | | +| REQ-002 | ReE Wrapper | ReE enthält `statusCode` als Zahl | API | High | - | `GET /api/v1/articles/99999` | `statusCode: 404` | Yes | | +| REQ-002 | ReE Wrapper | ReE enthält `message` als Array (nicht String) | API | High | - | `GET /api/v1/articles/99999` | `message: [string]` | Yes | `errors.filter.ts:25-27` — normalisiert zu Array | +| REQ-002 | ReE Wrapper | ReE enthält `error` als String | API | High | - | `GET /api/v1/articles/99999` | `error: "Not Found"` o.ä. | Yes | | +| REQ-002 | ReE Wrapper | Unerwarteter Fehler (non-HttpException) gibt HTTP 500 und ReE zurück | API | High | - | Endpoint der intern crasht | `{ success: false, statusCode: 500, ... }` | Maybe | Benötigt Mock/Test-Endpoint der Exception wirft | +| REQ-002 | ReE Wrapper | `message` bei ValidationPipe-Fehler ist Array mit Beschreibungen | API | High | - | POST ohne Pflichtfelder | `message: ["name should not be empty", ...]` | Yes | NestJS ValidationPipe produziert Array | + +--- + +## G) Testfälle + +--- + +### TC-002-001 — Success-Response: Grundstruktur +**REQ-ID:** REQ-002 +**Titel:** Erfolgreicher GET enthält `success: true` und `data` +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft; DB mit mindestens 1 Artikel + +**Given** das Backend ist gestartet +**And** mindestens 1 Artikel ist in der DB vorhanden +**When** `GET /api/v1/articles` +**Then** +- HTTP Status ist `200` +- Body ist gültiges JSON +- Body enthält Feld `success` mit Wert `true` (boolean) +- Body enthält Feld `data` (Array) +- Body enthält KEIN Feld `statusCode` +- Body enthält KEIN Feld `error` + +**Testdaten:** Standard-Artikel-Datensatz +**Orakel:** `res.model.ts` — `ReS.FromData()` setzt `success = true`, `data = arg0` +**Automatisierung:** Yes + +--- + +### TC-002-002 — Success-Response: data ist null nach DELETE +**REQ-ID:** REQ-002 +**Titel:** DELETE gibt `{ success: true, data: null }` zurück +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft; Artikel mit id=1 existiert in DB + +**Given** Artikel mit id=1 existiert +**When** `DELETE /api/v1/articles/1` +**Then** +- HTTP Status ist `200` +- Body: `{ "success": true, "data": null }` + +**Testdaten:** id=1 (existierender Artikel) +**Orakel:** Controller-Code: `return ReS.FromData(null)` nach erfolgreichem Delete +**Automatisierung:** Yes + +--- + +### TC-002-003 — Error-Response: 404 Grundstruktur +**REQ-ID:** REQ-002 +**Titel:** Nicht-existente Ressource gibt korrekte ReE-Struktur zurück +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft; kein Artikel mit id=99999 + +**Given** kein Artikel mit id=99999 existiert +**When** `GET /api/v1/articles/99999` +**Then** +- HTTP Status ist `404` +- Body enthält Feld `success` mit Wert `false` (boolean) +- Body enthält Feld `statusCode` mit Wert `404` (number) +- Body enthält Feld `message` als Array (z.B. `["Not Found"]`) +- Body enthält Feld `error` als String +- Body enthält KEIN Feld `data` + +**Testdaten:** id=99999 (nicht-existent) +**Orakel:** `errors.filter.ts` — `ReE.FromData(statusCode, name, message)` +**Automatisierung:** Yes + +--- + +### TC-002-004 — Error-Response: message ist immer Array +**REQ-ID:** REQ-002 +**Titel:** `message`-Feld in ReE ist immer ein Array, nie ein String +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** ein Request erzeugt einen Fehler (z.B. 404) +**When** `GET /api/v1/articles/99999` +**Then** +- `body.message` ist ein Array (`Array.isArray(body.message) === true`) +- Kein einzelner String (nicht `body.message === "Not Found"`) + +**Testdaten:** id=99999 +**Orakel:** `errors.filter.ts` Zeilen 25–27 — `message = Array.isArray(errorMessage) ? errorMessage : [errorMessage]` +**Automatisierung:** Yes + +--- + +### TC-002-005 — Error-Response: ValidationPipe erzeugt message-Array +**REQ-ID:** REQ-002 +**Titel:** POST ohne Pflichtfelder gibt ReE mit Array-Fehlermeldungen zurück +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `POST /api/v1/articles` mit leerem Body `{}` +**Then** +- HTTP Status ist `400` +- `body.success === false` +- `body.message` ist ein Array mit mindestens 1 Element +- Jedes Element in `body.message` ist ein String + +**Testdaten:** `{}` (leerer Body) +**Orakel:** REQ-002 + REQ-003; NestJS ValidationPipe produziert Array von Constraint-Messages +**Automatisierung:** Yes + +--- + +### TC-002-006 — Error-Response: unerwarteter Fehler gibt HTTP 500 +**REQ-ID:** REQ-002 +**Titel:** Non-HttpException wird zu HTTP 500 ReE +**Typ:** Unit / API +**Status:** READY + +**Setup:** Mock oder Test-Endpoint der einen `new Error("Unexpected failure")` wirft + +**Given** ein Endpunkt wirft eine unerwartete (non-HttpException) Exception +**When** der Endpunkt aufgerufen wird +**Then** +- HTTP Status ist `500` +- `body.success === false` +- `body.statusCode === 500` +- `body.message` ist ein Array mit `["Unexpected failure"]` +- `body.error` entspricht dem Error-Namen (z.B. `"Error"`) + +**Testdaten:** Custom Error: `new Error("Unexpected failure")` +**Orakel:** `errors.filter.ts` Zeilen 16–19 — non-HttpException → `HttpStatus.INTERNAL_SERVER_ERROR` +**Automatisierung:** Maybe (benötigt kontrollierten Crash-Endpunkt oder Unit-Test des Filters) + +--- + +### TC-002-007 — ReS: kein Cross-Kontamination mit ReE-Feldern +**REQ-ID:** REQ-002 +**Titel:** Success-Response enthält keine Error-spezifischen Felder +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft, Artikel in DB + +**Given** ein Request ist erfolgreich +**When** `GET /api/v1/articles` +**Then** +- `body.statusCode` ist `undefined` +- `body.error` ist `undefined` +- `body.message` ist `undefined` + +**Orakel:** `res.model.ts` — `ReS` hat nur `success` und `data`; kein `statusCode`, `error`, `message` +**Automatisierung:** Yes + +--- + +## H) Coverage Summary + +| Metrik | Wert | +|--------|------| +| Requirements in dieser Datei | 1 (REQ-002) | +| Testfälle gesamt | 7 (TC-002-001 bis TC-002-007) | +| Abgedeckte Szenarien | ReS-Struktur, data=null, ReE-Struktur, message-Array-Typ, ValidationPipe, 500-Fehler, keine Kreuz-Kontamination | +| READY | 6 | +| BLOCKED | 0 | +| Maybe (Infrastruktur) | 1 (TC-002-006) | + +--- + +## I) Gaps & Blockers + +| ID | Beschreibung | Betroffene REQ | Fehlende Info | +|----|-------------|----------------|---------------| +| GAP-002-1 | TC-002-006 (500-Fehler) benötigt einen kontrollierten Crash-Endpunkt oder Unit-Test des ErrorFilter-Catch direkt | REQ-002 | Test-Infrastruktur: Entweder Unit-Test von ErrorFilter oder spezieller Test-Endpunkt | +| GAP-002-2 | Content-Type des Response nicht explizit geprüft — NestJS sendet standardmäßig `application/json`, aber kein expliziter Test | REQ-002 | Kein explizites Requirement für Content-Type dokumentiert | + +--- + +## J) Decision Needed + +| ID | Entscheidung | Betroffene REQ | +|----|-------------|----------------| +| DEC-002-1 | Soll `ReS` auch einen `statusCode` im Body enthalten (z.B. 201 bei POST)? Aktuell kein `statusCode` in `ReS`. Frontend muss auf HTTP-Status-Header angewiesen sein. | REQ-002 | +| DEC-002-2 | Bei unerwarteten Fehlern (500): Soll die interne Fehlermeldung (`error.message`) an den Client gesendet werden? Aktuell: ja (`message = [error.message]`). Das kann interne Details leaken. | REQ-002 | diff --git a/test-req/REQ-003-validation-pipe.md b/test-req/REQ-003-validation-pipe.md new file mode 100644 index 0000000..bc10b44 --- /dev/null +++ b/test-req/REQ-003-validation-pipe.md @@ -0,0 +1,280 @@ +# REQ-003 — Global ValidationPipe (whitelist + transform) + +**Datum:** 2026-03-17 · **Branch:** first-init +**Eltern-Dokument:** `test-req/backend-requirements.md` +**Framework:** Framework-agnostisch (UNBELEGT) — Given/When/Then + +--- + +## Requirement + +| Feld | Wert | +|------|------| +| **ID** | REQ-003 | +| **Titel** | Global ValidationPipe (whitelist, transform) | +| **Beschreibung** | (1) **Whitelist:** Felder, die nicht im DTO deklariert sind, werden ohne Fehler aus dem Request-Body entfernt und nicht verarbeitet. (2) **Transform:** String-Werte in Query-Parametern/Path-Params werden automatisch in den deklarierten Typ umgewandelt (z.B. `"5"` → `5`). (3) **Validierung:** Fehlende Pflichtfelder und Typ-Verletzungen führen zu HTTP 400. | +| **Quelle** | `apps/server/src/main.ts` Zeilen 22–27 | +| **Evidenz** | `new ValidationPipe({ whitelist: true, transform: true })` | +| **Priorität** | High | +| **Status** | CONFIRMED | + +### Exakte Konfiguration aus Code + +```typescript +// main.ts, Zeile 22–27 +app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, // Entfernt nicht-deklarierte Felder (kein Fehler) + transform: true, // Transformiert Eingaben in deklarierte Typen + }), +); +``` + +**Hinweis `whitelist: true`:** Extra-Felder werden **still entfernt** (kein 400). Das Ziel ist Schutz vor unerwarteten Payloads, nicht Ablehnung. +**Hinweis `transform: true`:** NestJS transformiert z.B. `@Param('id')` mit `id: number` → String aus URL wird zu Number gecastet. + +--- + +## E) Testdimensionen + +| Dimension | Werte | Belegt durch | +|-----------|-------|--------------| +| **Whitelist-Verhalten** | Bekanntes Feld (erlaubt) / Unbekanntes Feld (still entfernt) / Nur unbekannte Felder (alle entfernt, Body effektiv leer) | `ValidationPipe({ whitelist: true })` | +| **Pflichtfeld-Validierung** | Alle Pflichtfelder vorhanden / Einzelnes Pflichtfeld fehlt / Alle Pflichtfelder fehlen | DTOs mit `@IsString()`, `@IsNumber()` ohne `@IsOptional()` | +| **Typvalidierung** | Korrekter Typ / Falscher Typ (String statt Number) | DTO-Dekoratoren | +| **Transform** | Path-Param als String → Number / Query-Param als String → Number | `transform: true`; Controller mit `:id` Parametern | +| **Nested DTO** | Korrekte Nested-Objekte / Fehlende verschachtelte Pflichtfelder | `IdDto` in DTOs (z.B. `articleGroup: IdDto`) | + +**Nicht belegbar:** Enum-Validierung in DTOs (kein `@IsEnum` in REQ-001–005 Scope), Array-Validierungen + +--- + +## F) Testmatrix + +| REQ-ID | Feature/Komponente | Szenario | Testtyp | Prio | +/- | Datenvarianten | Erwartetes Ergebnis | Auto | Notes | +|--------|-------------------|----------|---------|------|-----|---------------|---------------------|------|-------| +| REQ-003 | ValidationPipe whitelist | Unbekanntes Feld im Body wird still entfernt | API | High | - | POST mit `{ "customerNumber": "K001", "hackerField": "x" }` | HTTP 201; gespeichertes Objekt hat kein `hackerField` | Yes | Kein 400 — silent strip | +| REQ-003 | ValidationPipe whitelist | Ausschließlich unbekannte Felder → 400 wegen fehlenden Pflichtfeldern | API | High | - | POST mit `{ "unknownOnly": "x" }` | HTTP 400 (Pflichtfeld `customerNumber` fehlt) | Yes | Whitelist entfernt alle → Pflichtfeld-Validierung schlägt an | +| REQ-003 | ValidationPipe whitelist | Bekannte Felder werden korrekt übergeben | API | High | + | POST mit validen DTO-Feldern | HTTP 201, alle Felder in Response | Yes | | +| REQ-003 | Pflichtfeld fehlt | Einzelnes Pflichtfeld fehlt → 400 | API | High | - | POST /articles ohne `name` | HTTP 400, `body.message` enthält Constraint-Beschreibung | Yes | Evidenz: `@IsString()` ohne `@IsOptional()` | +| REQ-003 | Pflichtfeld fehlt | Alle Pflichtfelder fehlen → 400 | API | High | - | POST /articles mit `{}` | HTTP 400, mehrere Messages im Array | Yes | | +| REQ-003 | Pflichtfeld fehlt | `message`-Array enthält eine Meldung pro verletztem Feld | API | Med | - | POST /articles ohne `name` und `code` | `body.message.length >= 2` | Yes | NestJS ValidationPipe-Standardverhalten | +| REQ-003 | Transform — Path-Param | `id` in `:id`-Route wird aus String zu Number transformiert | Unit/API | High | + | `GET /api/v1/articles/5` (id als String in URL) | Interne Verarbeitung mit `id = 5` (number) | Yes | INFERIERT: `transform: true` transformiert `@Param('id') id: number` | +| REQ-003 | Transform — Query-Param | `@Query` mit `@Type(() => Number)` wird transformiert | Unit | Med | + | `GET /dashboard/summary?days=30` | `days` intern als `30` (number), nicht `"30"` (string) | Yes | Belegt durch `DashboardSummaryQueryDto @Type(() => Number)` | +| REQ-003 | Nested DTO | Fehlende `id` in Nested-DTO `articleGroup` → 400 | API | High | - | POST /articles mit `{ ..., "articleGroup": {} }` (id fehlt) | HTTP 400 | Yes | `IdDto` hat `id: number` als Pflichtfeld | +| REQ-003 | Typfehler | String statt Number in Pflichtfeld → 400 | API | Med | - | POST mit `{ "price": "nicht-eine-zahl" }` | HTTP 400 | Yes | `@IsNumber()` in DTO | + +--- + +## G) Testfälle + +--- + +### TC-003-001 — Whitelist: Unbekanntes Feld wird still entfernt +**REQ-ID:** REQ-003 +**Titel:** Extra-Feld im Request wird verworfen, keine 400-Antwort +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft; DB akzeptiert neuen Customer + +**Given** das Backend ist gestartet +**When** `POST /api/v1/customers` mit Body: +```json +{ + "customerNumber": "K-WHITELIST-TEST", + "hackerField": "sollte-ignoriert-werden", + "anotherUnknown": 12345 +} +``` +**Then** +- HTTP Status ist `201` (kein 400) +- `body.success === true` +- `body.data.customerNumber === "K-WHITELIST-TEST"` +- `body.data` enthält kein Feld `hackerField` +- `body.data` enthält kein Feld `anotherUnknown` + +**Testdaten:** Wie oben; Aufräumen: gespeicherten Kunden nach Test löschen +**Orakel:** REQ-003 — `whitelist: true` entfernt unbekannte Felder still +**Automatisierung:** Yes + +--- + +### TC-003-002 — Whitelist: Ausschließlich Extra-Felder → fehlende Pflichtfelder +**REQ-ID:** REQ-003 +**Titel:** Body nur mit unbekannten Feldern → 400 wegen fehlendem Pflichtfeld +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `POST /api/v1/customers` mit Body `{ "unknownField": "x" }` +**Then** +- HTTP Status ist `400` +- `body.success === false` +- `body.message` enthält Hinweis auf `customerNumber` (Pflichtfeld fehlt) + +**Testdaten:** `{ "unknownField": "x" }` +**Orakel:** REQ-003 (whitelist entfernt `unknownField`; `customerNumber` bleibt fehlend → ValidationPipe 400) +**Automatisierung:** Yes + +--- + +### TC-003-003 — Pflichtfeld fehlt: Einzelnes Feld +**REQ-ID:** REQ-003 +**Titel:** POST ohne einzelnes Pflichtfeld `name` → HTTP 400 +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `POST /api/v1/articles` mit Body ohne das Feld `name`: +```json +{ + "code": "T001", + "price": 9.99, + "type": "Typ", + "unit": "Stk", + "artNumber": "001", + "articleGroup": { "id": 1 } +} +``` +**Then** +- HTTP Status ist `400` +- `body.success === false` +- `body.message` ist ein Array +- Mindestens ein Element in `body.message` beschreibt den `name`-Fehler + +**Testdaten:** Wie oben +**Orakel:** REQ-003; `CreateArticleDto.name` ist `@IsString()` ohne `@IsOptional()` +**Automatisierung:** Yes + +--- + +### TC-003-004 — Pflichtfeld fehlt: Leerer Body +**REQ-ID:** REQ-003 +**Titel:** POST mit leerem Body → HTTP 400 mit mehreren Fehlermeldungen +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `POST /api/v1/articles` mit Body `{}` +**Then** +- HTTP Status ist `400` +- `body.success === false` +- `body.message` ist Array mit **mehr als einem** Element (für jedes fehlende Pflichtfeld mindestens 1 Message) + +**Testdaten:** `{}` +**Orakel:** REQ-003; `CreateArticleDto` hat mehrere Pflichtfelder ohne `@IsOptional()` +**Automatisierung:** Yes + +--- + +### TC-003-005 — Transform: Path-Param String → Number +**REQ-ID:** REQ-003 +**Titel:** `:id` URL-Parameter wird von String zu Number transformiert +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft; Artikel mit id=5 in DB + +**Given** Artikel mit id=5 existiert +**When** `GET /api/v1/articles/5` +**Then** +- HTTP Status ist `200` +- `body.data.id === 5` (number, nicht `"5"`) +- Kein Fehler aufgrund des String-URL-Parameters + +**Testdaten:** id=5 (als String in URL, als Number im System) +**Orakel:** REQ-003 — `transform: true`; `@Param('id') id: number` in Controller +**Automatisierung:** Yes +**Hinweis:** Test verifiziert indirekt, dass Transform funktioniert. Ein fehlgeschlagener Transform würde keine DB-Abfrage erzeugen (String-Vergleich statt Number-Vergleich in SQL). + +--- + +### TC-003-006 — Transform: Query-Param String → Number +**REQ-ID:** REQ-003 +**Titel:** `?days=30` wird intern als Number verarbeitet +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `GET /api/v1/dashboard/summary?days=30` +**Then** +- HTTP Status ist `200` (kein 400 wegen falschen Typs) +- Dashboard-Response enthält `filters.days === 30` (number) + +**Testdaten:** `?days=30` +**Orakel:** REQ-003; `DashboardSummaryQueryDto` mit `@Type(() => Number)` und `@IsInt` +**Automatisierung:** Yes + +--- + +### TC-003-007 — Nested DTO: Fehlende id in articleGroup +**REQ-ID:** REQ-003 +**Titel:** POST /articles mit leerem `articleGroup`-Objekt → HTTP 400 +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `POST /api/v1/articles` mit Body: +```json +{ + "name": "Test", + "code": "X001", + "price": 9.99, + "type": "T", + "unit": "Stk", + "artNumber": "001", + "articleGroup": {} +} +``` +(Feld `id` in `articleGroup` fehlt) +**Then** +- HTTP Status ist `400` +- `body.message` enthält Hinweis auf `articleGroup.id` + +**Testdaten:** Wie oben +**Orakel:** REQ-003; `IdDto.id` ist `@IsNumber()` ohne `@IsOptional()` +**Automatisierung:** Yes + +--- + +## H) Coverage Summary + +| Metrik | Wert | +|--------|------| +| Requirements in dieser Datei | 1 (REQ-003) | +| Testfälle gesamt | 7 (TC-003-001 bis TC-003-007) | +| Abgedeckte Szenarien | Whitelist-silent-strip, whitelist+pflichtfeld, pflichtfeld-einzel, pflichtfeld-leer, transform-path, transform-query, nested-dto | +| READY | 7 | +| BLOCKED | 0 | + +**Nicht abgedeckt (begründet):** +- Typfehler (String statt Number in Feld): In Matrix enthalten, aber kein eigener TC — Pflichtfeld-Tests decken ähnliches Verhalten ab. Kann bei Bedarf ergänzt werden. + +--- + +## I) Gaps & Blockers + +| ID | Beschreibung | Betroffene REQ | Fehlende Info | +|----|-------------|----------------|---------------| +| GAP-003-1 | `forbidNonWhitelisted: false` ist implizit (kein Fehler bei Extra-Feldern) — falls dies geändert wird (auf `true`), würden TC-003-001 und TC-003-002 brechen | REQ-003 | Kein explizites Requirement dokumentiert ob Extra-Felder einen Fehler erzeugen sollen | +| GAP-003-2 | `transform: true` Verhalten bei ungültigen Typen (z.B. `?days=abc`) nicht getestet — NestJS kann 400 werfen oder NaN durchlassen | REQ-003 | Verhalten bei nicht-transformierbaren Werten nicht spezifiziert | + +--- + +## J) Decision Needed + +| ID | Entscheidung | Betroffene REQ | +|----|-------------|----------------| +| DEC-003-1 | Sollen Extra-Felder im Body einen **400-Fehler** erzeugen (`forbidNonWhitelisted: true`) oder **still entfernt** werden (`whitelist: true` ohne `forbidNonWhitelisted`)? Aktuell: silent-strip. | REQ-003 | +| DEC-003-2 | Was passiert bei `?days=abc` (nicht-numerischer Wert für `@IsInt`-Feld)? Soll 400 zurückgegeben werden oder Default-Wert verwendet? | REQ-003 | diff --git a/test-req/REQ-004-error-filter.md b/test-req/REQ-004-error-filter.md new file mode 100644 index 0000000..b5fba7a --- /dev/null +++ b/test-req/REQ-004-error-filter.md @@ -0,0 +1,270 @@ +# REQ-004 — Global ErrorFilter + +**Datum:** 2026-03-17 · **Branch:** first-init +**Eltern-Dokument:** `test-req/backend-requirements.md` +**Framework:** Framework-agnostisch (UNBELEGT) — Given/When/Then + +--- + +## Requirement + +| Feld | Wert | +|------|------| +| **ID** | REQ-004 | +| **Titel** | Global ErrorFilter | +| **Beschreibung** | Alle nicht behandelten Exceptions werden durch `ErrorFilter` abgefangen und im `ReE`-Format zurückgegeben. (1) `HttpException`-Subklassen: HTTP-Status aus `error.getStatus()`; Message aus `error.getResponse().message`; Name aus `error.getResponse().error \|\| error.name`. (2) Non-`HttpException`-Fehler: HTTP 500; Message aus `[error.message]`; Name aus `error.name`. | +| **Quelle** | `apps/server/src/main.ts` Zeile 21; `apps/server/src/common/filters/errors.filter.ts` | +| **Evidenz** | `app.useGlobalFilters(new ErrorFilter())`; `@Catch()` (fängt ALLE Exceptions); Statuscode-Logik Zeilen 16–19; Message-Normalisierung Zeilen 22–27 | +| **Priorität** | High | +| **Status** | CONFIRMED | + +### Exakte Logik aus Code (`errors.filter.ts`) + +```typescript +// Zeilen 16–19: HTTP-Statuscode-Bestimmung +const statusCode: number = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; // 500 für alle non-HttpException + +// Zeilen 22–27: Message-Normalisierung +if (error instanceof HttpException) { + const errorMessage = (error.getResponse() as any)?.message; + message = Array.isArray(errorMessage) ? errorMessage : [errorMessage]; +} else { + message = [error.message]; // Non-HttpException: einzel-element Array +} + +// Zeilen 30–33: Error-Name +const name = error instanceof HttpException + ? (error.getResponse() as any)?.error || error.name + : error.name; + +// Zeile 35: Finale Response +response.status(statusCode).json(ReE.FromData(statusCode, name, message)); +``` + +**Wichtig:** `@Catch()` ohne Argument fängt **alle** Exceptions — inklusive TypeError, RangeError, DB-Fehler usw. + +--- + +## E) Testdimensionen + +| Dimension | Werte | Belegt durch | +|-----------|-------|--------------| +| **Exception-Typ** | `HttpException`-Subklasse (NotFoundException, BadRequestException, UnprocessableEntityException, ...) / Non-HttpException (Error, TypeError, DB-Error) | `errors.filter.ts` Zeile 17 | +| **HTTP-Status** | 400 (Bad Request) / 404 (Not Found) / 422 (Unprocessable Entity) / 500 (Internal Server Error) | `HttpStatus.*` aus NestJS; Filter-Logik | +| **message-Format** | Single-String → Array-wrap / Bereits Array → passthrough | `errors.filter.ts` Zeilen 24–26 | +| **error-Name** | Aus `getResponse().error` / Aus `error.name` als Fallback / Aus `error.name` bei non-HttpException | `errors.filter.ts` Zeilen 30–33 | +| **console.error** | Fehler wird geloggt | `errors.filter.ts` Zeile 22 | + +**Nicht belegbare Dimensionen:** Retry-Logik, Rate-Limiting, Logging-Targets (nur `console.error`) + +--- + +## F) Testmatrix + +| REQ-ID | Feature/Komponente | Szenario | Testtyp | Prio | +/- | Datenvarianten | Erwartetes Ergebnis | Auto | Notes | +|--------|-------------------|----------|---------|------|-----|---------------|---------------------|------|-------| +| REQ-004 | ErrorFilter — HttpException | `NotFoundException` (404) wird zu ReE mit statusCode=404 | API/Unit | High | - | `GET /api/v1/articles/99999` | `{ success:false, statusCode:404, error:string, message:[string] }` | Yes | Standard-Fehlerfall | +| REQ-004 | ErrorFilter — HttpException | `BadRequestException` (400) wird zu ReE mit statusCode=400 | API/Unit | High | - | POST ohne Pflichtfeld | `{ success:false, statusCode:400, ... }` | Yes | | +| REQ-004 | ErrorFilter — HttpException | `UnprocessableEntityException` (422) wird zu ReE mit statusCode=422 | Unit | Med | - | Service wirft 422 | `statusCode: 422` | Yes | | +| REQ-004 | ErrorFilter — HttpException | message ist Array wenn HttpException.getResponse().message bereits Array | Unit | High | - | ValidationPipe-Fehler mit mehreren Messages | `message: string[]` mit >1 Element | Yes | Evidenz: Zeile 25 `Array.isArray() ? passthrough` | +| REQ-004 | ErrorFilter — HttpException | message wird zu Array gewrappt wenn es ein String ist | Unit | High | - | HttpException mit String-Message | `message: ["single message"]` (Array!) | Yes | Evidenz: Zeile 26 `: [errorMessage]` | +| REQ-004 | ErrorFilter — non-HttpException | Nicht-HttpException wird zu HTTP 500 | Unit | High | - | `new Error("crash")` | `statusCode: 500` | Maybe | Benötigt kontrollierten Crash | +| REQ-004 | ErrorFilter — non-HttpException | message bei non-HttpException ist `[error.message]` | Unit | High | - | `new Error("DB connection lost")` | `message: ["DB connection lost"]` | Maybe | | +| REQ-004 | ErrorFilter — non-HttpException | error-Name bei non-HttpException ist `error.name` | Unit | Med | - | `new TypeError("x")` | `error: "TypeError"` | Maybe | | +| REQ-004 | ErrorFilter — Response-Format | Alle Fehler-Responses haben exakt die ReE-Struktur | API | High | - | Verschiedene Fehler | `{ success, statusCode, error, message }` — kein anderes Format | Yes | Integration zwischen ErrorFilter und ReE | +| REQ-004 | ErrorFilter — Scope | Global registriert — gilt für ALLE Controller | API | High | + | Fehler in verschiedenen Controllern | Immer ReE-Format | Yes | `app.useGlobalFilters(new ErrorFilter())` | + +--- + +## G) Testfälle + +--- + +### TC-004-001 — HttpException: NotFoundException → ReE 404 +**REQ-ID:** REQ-004 +**Titel:** NotFoundException wird zu vollständiger ReE-Response mit statusCode=404 +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft; kein Artikel mit id=99999 + +**Given** kein Artikel mit id=99999 existiert +**When** `GET /api/v1/articles/99999` +**Then** +- HTTP-Status-Header: `404` +- `body.success === false` +- `body.statusCode === 404` +- `body.error` ist ein String (nicht leer) +- `body.message` ist ein Array +- `body.message.length >= 1` +- Body enthält KEIN Feld `data` + +**Testdaten:** id=99999 (nicht-existent) +**Orakel:** REQ-004; `errors.filter.ts` Zeilen 17–18; `ReE.FromData()` +**Automatisierung:** Yes + +--- + +### TC-004-002 — HttpException: BadRequestException → ReE 400 +**REQ-ID:** REQ-004 +**Titel:** ValidationPipe-Fehler (400) wird korrekt zu ReE +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `POST /api/v1/articles` mit `{}` +**Then** +- HTTP-Status-Header: `400` +- `body.success === false` +- `body.statusCode === 400` +- `body.message` ist ein Array mit mindestens 1 Element + +**Testdaten:** `{}` +**Orakel:** REQ-004; REQ-003; ValidationPipe wirft `BadRequestException`; ErrorFilter fängt sie ab +**Automatisierung:** Yes + +--- + +### TC-004-003 — message als Array (passthrough, wenn bereits Array) +**REQ-ID:** REQ-004 +**Titel:** Wenn NestJS eine Array-Message liefert, bleibt sie Array +**Typ:** Unit +**Status:** READY + +**Setup:** Unit-Test des `ErrorFilter.catch()` + +**Given** ein `BadRequestException` mit `message: ["field1 error", "field2 error"]` wird geworfen +**When** `ErrorFilter.catch(error, host)` aufgerufen wird +**Then** +- `response.message` ist `["field1 error", "field2 error"]` +- `response.message` wurde **nicht** erneut in ein Array verpackt: nicht `[["field1 error", "field2 error"]]` + +**Testdaten:** `new BadRequestException({ message: ["field1 error", "field2 error"], error: "Bad Request" })` +**Orakel:** `errors.filter.ts` Zeile 25: `Array.isArray(errorMessage) ? errorMessage : [errorMessage]` +**Automatisierung:** Yes + +--- + +### TC-004-004 — message als String → wird zu Array gewrappt +**REQ-ID:** REQ-004 +**Titel:** String-Message in HttpException wird zu einelementigem Array +**Typ:** Unit +**Status:** READY + +**Setup:** Unit-Test des `ErrorFilter.catch()` + +**Given** ein `BadRequestException` mit `message: "single error message"` (String) wird geworfen +**When** `ErrorFilter.catch(error, host)` aufgerufen wird +**Then** +- `response.message` ist `["single error message"]` (Array mit einem Element) +- `response.message` ist NICHT `"single error message"` (kein String) + +**Testdaten:** `new BadRequestException("single error message")` +**Orakel:** `errors.filter.ts` Zeile 26: `: [errorMessage]` — String wird zu Array +**Automatisierung:** Yes + +--- + +### TC-004-005 — Non-HttpException → HTTP 500 +**REQ-ID:** REQ-004 +**Titel:** Nicht-HttpException erzeugt HTTP 500 +**Typ:** Unit +**Status:** READY + +**Setup:** Unit-Test des `ErrorFilter.catch()`; Mock-Response-Objekt + +**Given** ein nicht-HttpException-Fehler `new Error("DB connection lost")` tritt auf +**When** `ErrorFilter.catch(error, host)` aufgerufen wird +**Then** +- `response.status` wird mit `500` aufgerufen +- `body.success === false` +- `body.statusCode === 500` +- `body.message === ["DB connection lost"]` +- `body.error === "Error"` + +**Testdaten:** `new Error("DB connection lost")` +**Orakel:** `errors.filter.ts` Zeile 19: `HttpStatus.INTERNAL_SERVER_ERROR`; Zeile 27: `message = [error.message]`; Zeile 33: `error.name` +**Automatisierung:** Yes (Unit-Test mit gemocktem `host` und `response`) + +--- + +### TC-004-006 — Non-HttpException: TypeError → korrekter error-Name +**REQ-ID:** REQ-004 +**Titel:** TypeError erzeugt `error: "TypeError"` im ReE +**Typ:** Unit +**Status:** READY + +**Setup:** Unit-Test des `ErrorFilter.catch()` + +**Given** ein `new TypeError("Cannot read property of undefined")` tritt auf +**When** `ErrorFilter.catch(error, host)` aufgerufen wird +**Then** +- `body.error === "TypeError"` +- `body.statusCode === 500` + +**Testdaten:** `new TypeError("Cannot read property of undefined")` +**Orakel:** `errors.filter.ts` Zeile 33: `error.name` → `"TypeError"` für TypeError-Instanzen +**Automatisierung:** Yes (Unit) + +--- + +### TC-004-007 — Globaler Scope: ErrorFilter gilt für alle Controller +**REQ-ID:** REQ-004 +**Titel:** ReE-Format wird auch von CustomerController zurückgegeben +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft; kein Customer mit id=99999 + +**Given** kein Customer mit id=99999 existiert +**When** `GET /api/v1/customers/99999` +**Then** +- HTTP Status ist `404` +- `body.success === false` +- `body.statusCode === 404` + +**Testdaten:** id=99999 +**Orakel:** REQ-004 — Global registriert via `app.useGlobalFilters()` +**Automatisierung:** Yes + +--- + +## H) Coverage Summary + +| Metrik | Wert | +|--------|------| +| Requirements in dieser Datei | 1 (REQ-004) | +| Testfälle gesamt | 7 (TC-004-001 bis TC-004-007) | +| Abgedeckte Szenarien | 404, 400, message-Array-passthrough, message-String-wrap, 500, TypeError-Name, globaler Scope | +| READY | 7 | +| BLOCKED | 0 | +| Unit-Tests (Filter direkt) | 4 (TC-004-003 bis TC-004-006) | +| API-Tests (Endpunkt-Level) | 3 (TC-004-001, TC-004-002, TC-004-007) | + +**Nicht abgedeckt (begründet):** +- 422 UnprocessableEntity: in Matrix als Med markiert; kein dedizierter TC (Verhalten analog zu 400/404) +- console.error-Logging: Nicht als funktionaler Test modellierbar ohne Log-Interception-Infrastruktur + +--- + +## I) Gaps & Blockers + +| ID | Beschreibung | Betroffene REQ | Fehlende Info | +|----|-------------|----------------|---------------| +| GAP-004-1 | Unit-Tests für ErrorFilter (TC-004-003 bis TC-004-006) benötigen einen gemockten `ArgumentsHost` mit `switchToHttp()` / `getResponse()` / `getRequest()` | REQ-004 | Test-Framework + Mock-Infrastruktur erforderlich | +| GAP-004-2 | DB-spezifische Fehler (z.B. UNIQUE-Constraint-Verletzung von TypeORM) werden als non-HttpException behandelt → HTTP 500 statt 409. Ist das gewollt? | REQ-004 | Kein spezifischer Handler für DB-Fehler dokumentiert → siehe UQ-001 aus Eltern-Dokument | + +--- + +## J) Decision Needed + +| ID | Entscheidung | Betroffene REQ | +|----|-------------|----------------| +| DEC-004-1 | Sollen DB-Constraint-Fehler (TypeORM QueryFailedError bei UNIQUE-Verletzung) einen spezifischen HTTP-Status bekommen (z.B. 409 Conflict) statt generisch HTTP 500? Aktuell: 500 (non-HttpException). | REQ-004 | +| DEC-004-2 | Soll die interne Fehlermeldung (`error.message`) bei HTTP 500 an den Client gesendet werden? Aktuell: ja. Das kann Implementierungsdetails leaken (z.B. SQL-Fehlertext). | REQ-004 | +| DEC-004-3 | Soll `console.error` durch ein strukturiertes Logging-System (z.B. NestJS Logger) ersetzt werden? Aktuell ist nur `console.error` im Filter vorhanden. | REQ-004 | diff --git a/test-req/REQ-005-cors.md b/test-req/REQ-005-cors.md new file mode 100644 index 0000000..6c9209e --- /dev/null +++ b/test-req/REQ-005-cors.md @@ -0,0 +1,248 @@ +# REQ-005 — CORS Herkunftsbeschränkung + +**Datum:** 2026-03-17 · **Branch:** first-init +**Eltern-Dokument:** `test-req/backend-requirements.md` +**Framework:** Framework-agnostisch (UNBELEGT) — Given/When/Then + +--- + +## Requirement + +| Feld | Wert | +|------|------| +| **ID** | REQ-005 | +| **Titel** | CORS — Herkunftsbeschränkung | +| **Beschreibung** | CORS erlaubt Anfragen von Ursprüngen, die in der Umgebungsvariable `CORS_ORIGIN` konfiguriert sind (kommagetrennte Liste). Wenn `CORS_ORIGIN` nicht gesetzt ist, wird `http://localhost:4200` als Default-Ursprung verwendet. Credentials (Cookies, Authorization-Header) sind erlaubt. Erlaubte HTTP-Methoden: GET, POST, PUT, PATCH, DELETE, OPTIONS. | +| **Quelle** | `apps/server/src/main.ts` Zeilen 14–18 | +| **Evidenz** | `app.enableCors({ origin: process.env.CORS_ORIGIN?.split(',') ?? ['http://localhost:4200'], methods: ['GET','POST','PUT','PATCH','DELETE','OPTIONS'], credentials: true })` | +| **Priorität** | Med | +| **Status** | INFERIERT (Verhalten aus Code ableitbar; kein explizites Requirement-Dokument) | + +### Exakte Konfiguration aus Code (`main.ts`, Zeilen 14–18) + +```typescript +app.enableCors({ + origin: process.env.CORS_ORIGIN?.split(',') ?? ['http://localhost:4200'], + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + credentials: true, +}); +``` + +**Analyse:** +- `CORS_ORIGIN` kann mehrere Ursprünge enthalten, getrennt durch `,` (komma) → `split(',')` → Array +- Wenn `CORS_ORIGIN` undefined oder leer: `??` Operator → Default `['http://localhost:4200']` +- `credentials: true` → `Access-Control-Allow-Credentials: true` im Response-Header +- `methods` → nur diese Methoden in Preflight-Response erlaubt + +--- + +## E) Testdimensionen + +| Dimension | Werte | Belegt durch | +|-----------|-------|--------------| +| **Origin-Varianten** | Erlaubter Ursprung (`http://localhost:4200`) / Nicht-erlaubter Ursprung (`http://evil.com`) / Mehrere Ursprünge via ENV | `main.ts` Zeile 15 | +| **ENV-Konfiguration** | `CORS_ORIGIN` nicht gesetzt (Default) / `CORS_ORIGIN` mit einem Wert / `CORS_ORIGIN` mit mehreren kommagetennten Werten | `process.env.CORS_ORIGIN?.split(',')` | +| **HTTP-Methoden** | GET, POST, PUT, PATCH, DELETE, OPTIONS (alle erlaubt) / Nicht deklarierte Methode (z.B. HEAD) | `methods`-Array | +| **Credentials** | Mit `credentials: true` / Ohne Credentials-Header | `credentials: true` | +| **Preflight** | OPTIONS-Preflight-Request / Direkter Request ohne Preflight | CORS-Standard; `OPTIONS` in `methods` | + +**Nicht belegbare Dimensionen:** Whitelisting auf IP-Ebene, Rate-Limiting, geografische Einschränkungen + +--- + +## F) Testmatrix + +| REQ-ID | Feature/Komponente | Szenario | Testtyp | Prio | +/- | Datenvarianten | Erwartetes Ergebnis | Auto | Notes | +|--------|-------------------|----------|---------|------|-----|---------------|---------------------|------|-------| +| REQ-005 | CORS Default-Origin | Request von `http://localhost:4200` bekommt CORS-Header | API | High | + | `Origin: http://localhost:4200` | Response enthält `Access-Control-Allow-Origin: http://localhost:4200` | Yes | Default-Konfiguration ohne ENV | +| REQ-005 | CORS Default-Origin | Request ohne `Origin`-Header funktioniert | API | Med | + | Kein `Origin`-Header | HTTP 200 (CORS gilt nur für Cross-Origin-Requests) | Yes | INFERIERT: Requests ohne Origin sind keine Cross-Origin-Requests | +| REQ-005 | CORS Credentials | Response enthält `Access-Control-Allow-Credentials: true` | API | Med | + | Request mit `Origin: http://localhost:4200` | Header `Access-Control-Allow-Credentials: true` | Yes | Evidenz: `credentials: true` | +| REQ-005 | CORS Methods | OPTIONS-Preflight für GET ist erfolgreich | API | Med | + | `OPTIONS /api/v1/articles` mit `Origin: http://localhost:4200` | HTTP 204 oder 200; `Access-Control-Allow-Methods` enthält `GET` | Yes | | +| REQ-005 | CORS Methods | OPTIONS-Preflight enthält alle erlaubten Methoden | API | Med | + | `OPTIONS` Request | `Access-Control-Allow-Methods` enthält GET, POST, PUT, PATCH, DELETE, OPTIONS | Yes | Evidenz: methods-Array in `enableCors` | +| REQ-005 | CORS Non-Allowed Origin | Request von nicht-erlaubtem Ursprung wird blockiert | API | High | - | `Origin: http://attacker.com` | KEIN `Access-Control-Allow-Origin`-Header; Browser blockiert Response | Yes | Verhalten ist browserseitig; Server-seitig: kein ACAO-Header | +| REQ-005 | CORS ENV-Konfiguration | Mehrere Ursprünge via kommagetrennte `CORS_ORIGIN` ENV | API | Med | + | `CORS_ORIGIN=http://a.com,http://b.com`; Request von `http://a.com` | ACAO-Header für `http://a.com` | BLOCKED | Benötigt ENV-Setup in Testumgebung | +| REQ-005 | CORS ENV-Konfiguration | Zweiter Ursprung aus `CORS_ORIGIN` ebenfalls erlaubt | API | Med | + | `CORS_ORIGIN=http://a.com,http://b.com`; Request von `http://b.com` | ACAO-Header für `http://b.com` | BLOCKED | Benötigt ENV-Setup | + +--- + +## G) Testfälle + +--- + +### TC-005-001 — Default-Origin: CORS-Header vorhanden +**REQ-ID:** REQ-005 +**Titel:** Request von `http://localhost:4200` erhält `Access-Control-Allow-Origin`-Header +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft ohne `CORS_ORIGIN` ENV gesetzt + +**Given** das Backend ist gestartet ohne `CORS_ORIGIN` Umgebungsvariable +**When** `GET /api/v1/articles` mit HTTP-Header `Origin: http://localhost:4200` +**Then** +- HTTP Status ist `200` +- Response-Header enthält `Access-Control-Allow-Origin: http://localhost:4200` + +**Testdaten:** Header: `Origin: http://localhost:4200` +**Orakel:** REQ-005; `main.ts` Zeile 15: `?? ['http://localhost:4200']` — Default-Origin +**Automatisierung:** Yes + +--- + +### TC-005-002 — Credentials-Header vorhanden +**REQ-ID:** REQ-005 +**Titel:** Response enthält `Access-Control-Allow-Credentials: true` +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `GET /api/v1/articles` mit Header `Origin: http://localhost:4200` +**Then** +- Response-Header enthält `Access-Control-Allow-Credentials: true` + +**Testdaten:** Header: `Origin: http://localhost:4200` +**Orakel:** REQ-005; `main.ts` Zeile 17: `credentials: true` +**Automatisierung:** Yes + +--- + +### TC-005-003 — Preflight: OPTIONS-Request erfolgreich +**REQ-ID:** REQ-005 +**Titel:** OPTIONS-Preflight wird mit CORS-Headern beantwortet +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `OPTIONS /api/v1/articles` mit Headern: +``` +Origin: http://localhost:4200 +Access-Control-Request-Method: POST +Access-Control-Request-Headers: Content-Type +``` +**Then** +- HTTP Status ist `204` oder `200` (kein 404, kein 403) +- Response-Header enthält `Access-Control-Allow-Origin: http://localhost:4200` +- Response-Header enthält `Access-Control-Allow-Methods` mit `POST` darin + +**Testdaten:** Preflight-Headers wie oben +**Orakel:** REQ-005; `methods: ['GET','POST','PUT','PATCH','DELETE','OPTIONS']` in `enableCors` +**Automatisierung:** Yes + +--- + +### TC-005-004 — Preflight: Alle erlaubten Methoden im Header +**REQ-ID:** REQ-005 +**Titel:** `Access-Control-Allow-Methods` enthält alle konfigurierten Methoden +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `OPTIONS /api/v1/articles` mit `Origin: http://localhost:4200` +**Then** +- `Access-Control-Allow-Methods`-Header enthält: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS` + +**Testdaten:** OPTIONS-Request mit Origin-Header +**Orakel:** REQ-005; `main.ts` Zeile 16: `methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']` +**Automatisierung:** Yes + +--- + +### TC-005-005 — Non-Allowed Origin: Kein CORS-Header +**REQ-ID:** REQ-005 +**Titel:** Request von nicht-erlaubtem Ursprung erhält keinen `Access-Control-Allow-Origin`-Header +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft ohne `CORS_ORIGIN` ENV gesetzt + +**Given** das Backend ist gestartet mit Default-Origin `http://localhost:4200` +**When** `GET /api/v1/articles` mit Header `Origin: http://attacker.com` +**Then** +- Response-Header enthält **KEIN** `Access-Control-Allow-Origin`-Header + **ODER** `Access-Control-Allow-Origin` ist NICHT `http://attacker.com` + +**Testdaten:** Header: `Origin: http://attacker.com` +**Orakel:** REQ-005 — Express/NestJS CORS-Middleware gibt keinen ACAO-Header bei nicht-erlaubten Ursprüngen zurück +**Automatisierung:** Yes +**Hinweis:** Der HTTP-Response-Code kann trotzdem 200 sein — CORS-Enforcement findet browserseitig statt. Der Test prüft nur das Fehlen des Headers. + +--- + +### TC-005-006 — ENV: Mehrere Ursprünge (kommagetrennt) +**REQ-ID:** REQ-005 +**Titel:** `CORS_ORIGIN=http://app1.com,http://app2.com` erlaubt beide Ursprünge +**Typ:** API +**Status:** BLOCKED + +**Blocker:** Benötigt Möglichkeit, `CORS_ORIGIN` ENV-Variable in der Testumgebung zu setzen. Testinfrastruktur dafür nicht definiert. + +**Setup:** Backend gestartet mit `CORS_ORIGIN=http://app1.com,http://app2.com` + +**Given** das Backend ist gestartet mit `CORS_ORIGIN=http://app1.com,http://app2.com` +**When** `GET /api/v1/articles` mit `Origin: http://app1.com` +**Then** `Access-Control-Allow-Origin: http://app1.com` im Response + +**And When** `GET /api/v1/articles` mit `Origin: http://app2.com` +**Then** `Access-Control-Allow-Origin: http://app2.com` im Response + +**Testdaten:** ENV-Variable + Origin-Header +**Orakel:** REQ-005; `main.ts` Zeile 15: `process.env.CORS_ORIGIN?.split(',')` → Array mit 2 Elementen +**Automatisierung:** Blocked + +--- + +### TC-005-007 — Request ohne Origin-Header +**REQ-ID:** REQ-005 +**Titel:** Request ohne `Origin`-Header wird normal verarbeitet +**Typ:** API +**Status:** READY + +**Setup:** Backend läuft + +**Given** das Backend ist gestartet +**When** `GET /api/v1/articles` **ohne** `Origin`-Header (z.B. direkter curl-Aufruf, Server-zu-Server) +**Then** +- HTTP Status ist `200` +- Response enthält `body.success === true` + +**Testdaten:** Kein `Origin`-Header +**Orakel:** INFERIERT — CORS gilt nur für Browser-Cross-Origin-Requests; Requests ohne Origin-Header sind keine Cross-Origin-Requests und werden nicht blockiert +**Automatisierung:** Yes + +--- + +## H) Coverage Summary + +| Metrik | Wert | +|--------|------| +| Requirements in dieser Datei | 1 (REQ-005) | +| Testfälle gesamt | 7 (TC-005-001 bis TC-005-007) | +| Abgedeckte Szenarien | Default-Origin CORS-Header, Credentials-Header, OPTIONS-Preflight, alle Methods, non-allowed Origin, ENV Multi-Origin, kein Origin-Header | +| READY | 6 | +| BLOCKED | 1 (TC-005-006 — ENV-Setup fehlt) | + +--- + +## I) Gaps & Blockers + +| ID | Beschreibung | Betroffene REQ | Fehlende Info | +|----|-------------|----------------|---------------| +| GAP-005-1 | TC-005-006 (Multi-Origin via ENV) ist BLOCKED — kein definierter Weg, `CORS_ORIGIN` ENV in Testumgebung zu setzen und Backend neu zu starten | REQ-005 | Test-Setup-Strategie für ENV-Variablen | +| GAP-005-2 | `CORS_ORIGIN` mit führenden/nachfolgenden Leerzeichen in der ENV (`http://a.com , http://b.com`) — `split(',')` ohne `trim()` würde `"http://b.com"` mit Leerzeichen erzeugen → Mismatch | REQ-005 | Kein `trim()` nach `split(',')` im Code — Potential Defect | +| GAP-005-3 | Kein Test für leeren `CORS_ORIGIN`-String (`CORS_ORIGIN=""`) — `"".split(',')` ergibt `[""]` (kein valider Ursprung) statt Default | REQ-005 | Nullary-Coalescing `??` prüft nur `undefined/null`, nicht leeren String | + +--- + +## J) Decision Needed + +| ID | Entscheidung | Betroffene REQ | +|----|-------------|----------------| +| DEC-005-1 | Soll `CORS_ORIGIN` ENV trim()-Unterstützung erhalten? Aktuell kein `trim()` nach `split(',')`. Leerzeichen in ENV-Werten (häufiger Config-Fehler) würden zu CORS-Mismatch führen. | REQ-005 | +| DEC-005-2 | Was ist das gewünschte Verhalten wenn `CORS_ORIGIN=""` (leerer String)? Default-Fallback oder leere Allowlist? `""` ist weder `null` noch `undefined` — `?? default` wird NICHT ausgelöst. | REQ-005 | +| DEC-005-3 | Soll CORS auch für die Swagger-Docs-Routen (`/api/v1/doc`) gelten? Aktuell: CORS wird global aktiviert, gilt also auch für Swagger. Ist das gewünscht? | REQ-005 | diff --git a/test-req/backend-requirements.md b/test-req/backend-requirements.md new file mode 100644 index 0000000..61e0426 --- /dev/null +++ b/test-req/backend-requirements.md @@ -0,0 +1,1279 @@ +# SIMS Backend — Test-Anforderungskatalog + +**Erstellt:** 2026-03-17 +**Branch:** first-init +**Scope:** `apps/server/` (NestJS 11 REST API) +**Methode:** test-agent · Full Pipeline A–K · MODE=DEFAULT +**Framework:** Framework-agnostisch (Given/When/Then + Pseudocode), da kein Testframework vorgegeben — `UNBELEGT` + +--- + +## A) INPUT INVENTORY + +| # | Quelle | Typ | Relevanz | +|---|--------|-----|----------| +| 1 | `apps/server/src/main.ts` | Bootstrap / Konfiguration | Hoch | +| 2 | `apps/server/src/app/app.module.ts` | Root Module | Hoch | +| 3 | `apps/server/src/models/article/controllers/article.controller.ts` | Controller | Hoch | +| 4 | `apps/server/src/models/article/controllers/article-group.controller.ts` | Controller | Mittel | +| 5 | `apps/server/src/models/article/article.service.ts` | Service | Hoch | +| 6 | `apps/server/src/models/article/article-group.service.ts` | Service | Mittel | +| 7 | `apps/server/src/models/article/inventory.service.ts` | Service | Mittel | +| 8 | `apps/server/src/models/article/entities/article.entity.ts` | Entity | Hoch | +| 9 | `apps/server/src/models/article/entities/inventory.entity.ts` | Entity | Mittel | +| 10 | `apps/server/src/models/article/dto/*.dto.ts` | DTOs | Hoch | +| 11 | `apps/server/src/models/customer/controllers/customer.controller.ts` | Controller | Hoch | +| 12 | `apps/server/src/models/customer/customer.service.ts` | Service | Mittel | +| 13 | `apps/server/src/models/customer/entities/customer.entity.ts` | Entity | Hoch | +| 14 | `apps/server/src/models/customer/dto/*.dto.ts` | DTOs | Hoch | +| 15 | `apps/server/src/models/bills/controllers/bill.controller.ts` | Controller | Hoch | +| 16 | `apps/server/src/models/bills/controllers/slipsheet.controller.ts` | Controller | Hoch | +| 17 | `apps/server/src/models/bills/controllers/order-entry.controller.ts` | Controller | Mittel | +| 18 | `apps/server/src/models/bills/controllers/dashboard.controller.ts` | Controller | Mittel | +| 19 | `apps/server/src/models/bills/bill.service.ts` | Service | Hoch | +| 20 | `apps/server/src/models/bills/slipsheet.service.ts` | Service | Hoch | +| 21 | `apps/server/src/models/bills/order-entry.service.ts` | Service | Hoch | +| 22 | `apps/server/src/models/bills/discount.service.ts` | Service | Mittel | +| 23 | `apps/server/src/models/bills/annotation.service.ts` | Service | Niedrig | +| 24 | `apps/server/src/models/bills/dashboard.service.ts` | Service | Mittel | +| 25 | `apps/server/src/models/bills/entities/*.entity.ts` | Entities | Hoch | +| 26 | `apps/server/src/models/bills/dto/*.dto.ts` | DTOs | Hoch | +| 27 | `apps/server/src/models/users/users.controller.ts` | Controller | Niedrig | +| 28 | `apps/server/src/models/users/users.service.ts` | Service | Niedrig | +| 29 | `apps/server/src/models/users/entities/user.entity.ts` | Entity | Niedrig | +| 30 | `apps/server/src/common/base.service.ts` | Shared | Hoch | +| 31 | `apps/server/src/common/res.model.ts` | Shared | Hoch | +| 32 | `apps/server/src/common/filters/errors.filter.ts` | Shared | Mittel | +| 33 | `apps/server/src/common/services/pdfmaker.service.ts` | Shared | Mittel | +| 34 | `CLAUDE.md` (Handoff-Dokument) | Spezifikation / Bugs | Hoch | + +**Fehlende Artefakte:** +- Kein OpenAPI/Swagger-Export (YAML/JSON) vorhanden — Swagger wird nur zur Laufzeit generiert +- Keine bestehenden Testdateien (`*.spec.ts`) gefunden +- Keine Acceptance Criteria / User Stories / Tickets vorhanden +- Kein Testframework explizit vorgegeben → Tests bleiben framework-agnostisch + +--- + +## B) REQUIREMENTS KATALOG + +### Globale Anforderungen + +--- + +**REQ-001** +**Titel:** Globaler API-Prefix +**Beschreibung:** Alle Endpunkte sind unter dem Prefix `/api/v1` erreichbar. +**Quelle:** `apps/server/src/main.ts` — `app.setGlobalPrefix('api/v1')` +**Evidenz:** `setGlobalPrefix` im Bootstrap +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-002** +**Titel:** Response-Wrapper ReS / ReE +**Beschreibung:** Erfolgreiche Antworten haben `{ success: true, data: T }`. Fehleantworten haben `{ success: false, statusCode, error, message[] }`. +**Quelle:** `apps/server/src/common/res.model.ts` +**Evidenz:** `ReS.FromData()` und `ReE.FromData()` Klassenmethoden +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-003** +**Titel:** Global ValidationPipe (whitelist, transform) +**Beschreibung:** Nicht im DTO deklarierte Felder werden verworfen (whitelist). Typen werden transformiert (transform). +**Quelle:** `apps/server/src/main.ts` — `new ValidationPipe({ whitelist: true, transform: true })` +**Evidenz:** ValidationPipe-Konfiguration im Bootstrap +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-004** +**Titel:** Global ErrorFilter +**Beschreibung:** Alle nicht behandelten Exceptions werden durch ErrorFilter abgefangen und im ReE-Format zurückgegeben. +**Quelle:** `apps/server/src/main.ts`, `apps/server/src/common/filters/errors.filter.ts` +**Evidenz:** `app.useGlobalFilters(new ErrorFilter())` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-005** +**Titel:** CORS — Herkunftsbeschränkung +**Beschreibung:** CORS erlaubt Anfragen von `CORS_ORIGIN` (ENV) oder Default `http://localhost:4200`. +**Quelle:** `apps/server/src/main.ts` +**Evidenz:** `cors: { origin: corsOrigin }` im Bootstrap +**Priorität:** Med (UNBELEGT ob ENV wirklich gesetzt wird) +**Status:** INFERIERT + +--- + +### Articles + +--- + +**REQ-010** +**Titel:** Artikel auflisten +**Beschreibung:** `GET /api/v1/articles` gibt alle Artikel mit berechnetem `stock` zurück. Optionale Query-Parameter `?code=X` (Barcode-Suche) und `?id=Y` (ID-Suche) filtern das Ergebnis. +**Quelle:** `article.controller.ts` — `findAll()`; `article.service.ts` — `getAll()`, `getByCode()`, `get()` +**Evidenz:** Controller-Handler mit `@Query('code')` und `@Query('id')`; `getAll()` enthält Subquery für Lagerbestand +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-011** +**Titel:** Artikel-Stock-Berechnung +**Beschreibung:** `stock = inventoryStock - totalAmount`. `totalAmount` ist die Summe aller zugehörigen OrderEntries. Der Wert wird per Subquery berechnet und nicht gespeichert. +**Quelle:** `article.service.ts` — `get()` QueryBuilder +**Evidenz:** `(SELECT SUM(...) FROM order_entry WHERE article_id = :id) AS totalAmount` +**Priorität:** High +**Status:** INFERIERT (Derived-from-code) + +--- + +**REQ-012** +**Titel:** Artikel anlegen +**Beschreibung:** `POST /api/v1/articles` mit Body `CreateArticleDto` legt einen neuen Artikel an und gibt ihn zurück. Felder `name`, `code`, `price`, `type`, `unit`, `artNumber`, `articleGroup.id` sind Pflicht. +**Quelle:** `article.controller.ts` — `create()`; `dto/create-article.dto.ts` +**Evidenz:** DTO-Felder ohne Optional-Decorator +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-013** +**Titel:** Artikel updaten +**Beschreibung:** `PATCH /api/v1/articles/:id` mit Body `UpdateArticleDto` aktualisiert einen Artikel. Nicht-existente IDs geben 404 zurück. +**Quelle:** `article.controller.ts` — `update()`; `base.service.ts` — `get()` mit `throwsException` +**Evidenz:** `throwsException: true` im Service-Aufruf +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-014** +**Titel:** Artikel löschen +**Beschreibung:** `DELETE /api/v1/articles/:id` löscht einen Artikel. Nicht-existente IDs geben 404 zurück. Erfolgreich gibt `null` zurück. +**Quelle:** `article.controller.ts` — `remove()` +**Evidenz:** `throwsException: true`, `ReS.FromData(null)` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-015** +**Titel:** Inventurbuchung +**Beschreibung:** `POST /api/v1/articles/:id/inventory` mit `{ newStock: number }` setzt den Lagerbestand (`inventoryStock`) auf den neuen Wert, erstellt einen Inventory-Eintrag mit `diff = newStock - oldStock` und gibt den aktualisierten Artikel zurück. +**Quelle:** `article.service.ts` — `makeInventory()` +**Evidenz:** `diff = newStock - article.inventoryStock`; `create({ amountNew: newStock, diff, article })` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-016** +**Titel:** Inventurbuchung — Fehlertoleranz +**Beschreibung:** Wenn das Erstellen des Inventory-Eintrags fehlschlägt, soll die Aktualisierung des Artikels trotzdem erfolgreich sein (kein Rollback des Artikel-Updates). +**Quelle:** `article.service.ts` — `makeInventory()` try/catch +**Evidenz:** `try { await inventoryService.create(...) } catch { /* silent */ }` +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-017** +**Titel:** CSV-Import — Preview +**Beschreibung:** `POST /api/v1/articles/import?preview=true` mit Multipart-Datei gibt eine Vorschau (parsed Artikel-Liste) zurück ohne zu speichern. +**Quelle:** `article.controller.ts`, `article.service.ts` — `importCsv(true, file)` +**Evidenz:** `if (preview) return ReS.FromData(articles)` +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-018** +**Titel:** CSV-Import — Ausführung +**Beschreibung:** `POST /api/v1/articles/import` (kein `?preview`) führt den Import aus und gibt `[successCount, failedCount]` zurück. +**Quelle:** `article.service.ts` — `importCsv(false, file)` +**Evidenz:** `createArticlesFromImport()` und `updateArticlesFromImport()` werden aufgerufen +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-019** +**Titel:** CSV-Import — Preisberechnung +**Beschreibung:** `price = Math.ceil((brutto / pe) * 100) / 100` +**Quelle:** `article.service.ts` — `importCsv()` +**Evidenz:** Explizite Berechnungsformel im Service +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-020** +**Titel:** Artikelgruppen CRUD +**Beschreibung:** `GET/POST/PATCH /api/v1/articlegroups` und `GET /api/v1/articlegroups/:id` für Artikelgruppen-Verwaltung. Pflichtfeld: `name`. +**Quelle:** `article-group.controller.ts` +**Evidenz:** Controller-Handler und DTO +**Priorität:** Low +**Status:** CONFIRMED + +--- + +### Customers + +--- + +**REQ-030** +**Titel:** Kunden auflisten +**Beschreibung:** `GET /api/v1/customers` gibt alle Kunden zurück. +**Quelle:** `customer.controller.ts` — `findAll()` +**Evidenz:** Controller-Handler +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-031** +**Titel:** Kunde mit Rabatten abrufen +**Beschreibung:** `GET /api/v1/customers/:id` gibt den Kunden inkl. seiner Rabatte zurück (Relation `discounts`). Nicht-existente IDs geben 404. +**Quelle:** `customer.controller.ts` — `findOne()`; `customerService.get(id, ['discounts'], true)` +**Evidenz:** `relations: ['discounts']` im Service-Aufruf +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-032** +**Titel:** Kunde anlegen +**Beschreibung:** `POST /api/v1/customers` mit `CreateCustomerDto`. Pflichtfeld: `customerNumber` (eindeutig). +**Quelle:** `customer.controller.ts`, `entities/customer.entity.ts` +**Evidenz:** `@Column({ unique: true })` auf `customerNumber` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-033** +**Titel:** Duplikat-Kundennummer +**Beschreibung:** Wird eine bereits vergebene `customerNumber` verwendet, gibt der Endpunkt einen Fehler zurück (UNIQUE-Constraint-Verletzung). +**Quelle:** `entities/customer.entity.ts` — `@Column({ unique: true })` +**Evidenz:** Datenbankconstraint; Fehler via ErrorFilter +**Priorität:** High +**Status:** INFERIERT (Derived-from-code; genaue HTTP-Statuscode-Spezifikation UNKLAR) + +--- + +**REQ-034** +**Titel:** Lieferscheine eines Kunden +**Beschreibung:** `GET /api/v1/customers/:id/slipsheets` gibt alle Lieferscheine des Kunden zurück. +**Quelle:** `customer.controller.ts` — `findSlipsheets()` +**Evidenz:** `slipsheetService.findFromCustomer(id)` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-035** +**Titel:** Rechnungen eines Kunden +**Beschreibung:** `GET /api/v1/customers/:id/bills` gibt alle Rechnungen des Kunden zurück. +**Quelle:** `customer.controller.ts` — `findBills()` +**Evidenz:** `billService.findFromCustomer(id)` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-036** +**Titel:** Rabatt anlegen / updaten +**Beschreibung:** `PUT /api/v1/customers/:id/discounts` mit `CreateUpdateDiscountDto` erstellt oder aktualisiert einen Rabatt für den Kunden. Wenn `id` im Body vorhanden → Update, sonst → Create. +**Quelle:** `customer.controller.ts` — `addDiscount()`; `discount.service.ts` — `createOrUpdate()` +**Evidenz:** `if (createUpdateDiscountDto.id)` → update-Pfad +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-037** +**Titel:** Rabatt-Artikelgruppe unveränderlich +**Beschreibung:** Beim Update eines Rabatts darf die `articleGroup` nicht geändert werden. Bei Versuch → Fehler (UNKLAR ob 400 oder 422). +**Quelle:** `discount.service.ts` — `createOrUpdate()` Validierung +**Evidenz:** `if (existing.articleGroupId !== articleGroup.id) throw ...` +**Priorität:** Med +**Status:** CONFIRMED (HTTP-Status UNKLAR) + +--- + +### Slipsheets (Lieferscheine) + +--- + +**REQ-040** +**Titel:** Lieferscheine auflisten +**Beschreibung:** `GET /api/v1/slipsheets` gibt alle Lieferscheine zurück. Optional filterbar nach `?customerId` (nur OPEN/CHANGED) und `?state`. +**Quelle:** `slipsheet.controller.ts` — `findAll()` +**Evidenz:** Auto-Filter bei customerId-Query: `state IN [OPEN, CHANGED]` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-041** +**Titel:** Lieferschein by ID +**Beschreibung:** `GET /api/v1/slipsheets/:id` gibt den Lieferschein mit allen Relationen zurück. Nicht-existente IDs geben 404. +**Quelle:** `slipsheet.controller.ts` — `findOne()`; Bug #1 gefixt (String → Number Cast) +**Evidenz:** `id: string` Parameter wird zu `+id` gecastet +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-042** +**Titel:** Order zu Lieferschein hinzufügen (neu) +**Beschreibung:** `POST /api/v1/slipsheets` mit `AddOrderEntryDto` fügt einen Order-Entry zu einem OPEN Lieferschein des Kunden hinzu. Existiert kein OPEN Lieferschein, wird ein neuer erstellt. +**Quelle:** `slipsheet.controller.ts` — `addOrder()` (ohne ID); `order-entry.service.ts` +**Evidenz:** `slipsheetService.findOpenForCustomer(customer)` erstellt bei Bedarf +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-043** +**Titel:** Order zu bestehendem Lieferschein hinzufügen +**Beschreibung:** `POST /api/v1/slipsheets/:id` mit `AddOrderEntryDto` fügt einen Entry zu einem bestehenden Lieferschein hinzu. +**Quelle:** `slipsheet.controller.ts` — `addOrderToExisting()` +**Evidenz:** `orderEntryService.addOrderToSlipsheet({ customer, slipsheet, addOrderEntry })` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-044** +**Titel:** Order-Entry — Artikel XOR Text +**Beschreibung:** Ein Order-Entry muss entweder einen `article` (mit ID) ODER einen `text` enthalten, aber nicht beides und nicht keins von beiden. Bei Verstoß → BadRequest (400). +**Quelle:** `order-entry.service.ts` — `addOrderToSlipsheet()` Validierung +**Evidenz:** `if (article && text) throw BadRequestException`; `if (!article && !text) throw BadRequestException` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-045** +**Titel:** Order-Entry — Mengenerhöhung +**Beschreibung:** Wird ein Artikel hinzugefügt, der im gleichen Lieferschein bereits vorhanden ist (und `article.singlePos = false`), wird die Menge des bestehenden Eintrags erhöht (kein neuer Eintrag). +**Quelle:** `order-entry.service.ts` — `addEntryByArticle()` +**Evidenz:** `if (article.singlePos || !existingEntry) create(); else increment()` +**Priorität:** High +**Status:** INFERIERT (Derived-from-code) + +--- + +**REQ-046** +**Titel:** Order-Entry — singlePos erzeugt immer neuen Eintrag +**Beschreibung:** Bei `article.singlePos = true` wird immer ein neuer Eintrag erstellt, auch wenn der Artikel bereits im Lieferschein ist. +**Quelle:** `order-entry.service.ts` — `addEntryByArticle()` +**Evidenz:** `if (article.singlePos || !existingEntry) create new entry` +**Priorität:** Med +**Status:** INFERIERT (Derived-from-code) + +--- + +**REQ-047** +**Titel:** Rabattberechnung bei Order-Entry +**Beschreibung:** Beim Hinzufügen eines Artikel-Eintrags werden `customerRabatt` (Kundenrabatt) und `articleGroupRabatt` (Artikelgruppen-Rabatt) berechnet und am Entry gespeichert. +**Quelle:** `order-entry.service.ts` — `addEntryByArticle()` +**Evidenz:** Discount-Lookup im Service, Speicherung in `orderEntry.customerRabatt` und `orderEntry.articleGroupRabatt` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-048** +**Titel:** Lieferschein schließen +**Beschreibung:** `POST /api/v1/slipsheets/:id/close` schließt den Lieferschein (state → CLOSED) und generiert das PDF. Lieferschein ohne Einträge kann nicht geschlossen werden. +**Quelle:** `slipsheet.controller.ts` — `close()`; `slipsheet.service.ts` — `generateSlipsheet(id, true)` +**Evidenz:** `if (!slipsheet.orderEntries?.length) throw ...` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-049** +**Titel:** Lieferschein-PDF abrufen +**Beschreibung:** `GET /api/v1/slipsheets/:id/pdf` gibt das PDF als StreamableFile zurück. Wenn das PDF noch nicht existiert, wird es generiert. +**Quelle:** `slipsheet.controller.ts` — `getPdf()` +**Evidenz:** `?close=true` Parameter in Query +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-050** +**Titel:** Lieferschein drucken +**Beschreibung:** `POST /api/v1/slipsheets/:id/print` druckt das PDF und gibt `true`/`false` zurück. +**Quelle:** `slipsheet.controller.ts` — `print()` +**Evidenz:** `printerService.print(path)` +**Priorität:** Low +**Status:** CONFIRMED + +--- + +**REQ-051** +**Titel:** Lieferschein-Annotation hinzufügen +**Beschreibung:** `POST /api/v1/slipsheets/:id/annotation` mit `{ text: string }` erstellt eine Notiz am Lieferschein. +**Quelle:** `slipsheet.controller.ts` — `addAnnotation()` +**Evidenz:** `annotationService.create({ text, slipsheet })` +**Priorität:** Low +**Status:** CONFIRMED + +--- + +**REQ-052** +**Titel:** Lieferschein-Nummer Generierung +**Beschreibung:** Lieferscheinnummern werden als `MAX(slipsheetnumber) + 1` vergeben (nur ganzzahlige, nicht `/`-formatierte Nummern werden berücksichtigt). +**Quelle:** `slipsheet.service.ts` — `generateNumber()` +**Evidenz:** SQL: `SELECT MAX(slipsheetnumber) WHERE NOT LIKE '%/%'` +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-053** +**Titel:** Order-Entry updaten (Menge) +**Beschreibung:** `PUT /api/v1/order-entries/:id` aktualisiert einen Entry. Bei `amount = 0` wird der Entry gelöscht und der Lieferschein als CHANGED markiert. +**Quelle:** `order-entry.service.ts` — `update()` +**Evidenz:** `if (inputs.amount === 0) delete; else update` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-054** +**Titel:** Lieferschein-State Änderung bei OrderEntry-Update +**Beschreibung:** Wenn einem Lieferschein, der nicht OPEN ist, ein Entry hinzugefügt oder geändert wird, wechselt der State auf CHANGED. +**Quelle:** `slipsheet.service.ts` — `changed()`; `order-entry.service.ts` +**Evidenz:** `if (slip.state !== OPEN) slip.state = CHANGED` +**Priorität:** Med +**Status:** CONFIRMED + +--- + +### Bills (Rechnungen) + +--- + +**REQ-060** +**Titel:** Rechnungen auflisten +**Beschreibung:** `GET /api/v1/bills` gibt alle Rechnungen zurück. Optional filterbar nach `?state`. +**Quelle:** `bill.controller.ts` — `findAll()` +**Evidenz:** Query-Parameter `?state` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-061** +**Titel:** Rechnung aus Lieferscheinen generieren +**Beschreibung:** `POST /api/v1/bills/generate` mit einem Array von Lieferschein-IDs erstellt eine Rechnung. Validierungen: alle Lieferscheine müssen CLOSED sein, alle müssen zum gleichen Kunden gehören, keiner darf eine abweichende Rechnung referenzieren. +**Quelle:** `bill.service.ts` — `generateBill()` +**Evidenz:** `if (slipsheet.state !== CLOSED) throw`; `if (customerId !== firstCustomer) throw` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-062** +**Titel:** Rechnung generieren — Nummernvergabe +**Beschreibung:** Die Rechnungsnummer wird als `MAX(billNumber) + 1` vergeben. +**Quelle:** `bill.service.ts` — `createBillWithRetry()` +**Evidenz:** `SELECT MAX(billNumber) + 1` SQL-Query +**Priorität:** High +**Status:** CONFIRMED (Race Condition Risiko vorhanden — siehe REQ-063) + +--- + +**REQ-063** +**Titel:** Rechnungsnummer Eindeutigkeit (Race Condition — OFFEN) +**Beschreibung:** Die Rechnungsnummer MUSS eindeutig sein. Die aktuelle Implementierung hat keine DB-Sperre beim SELECT MAX(). Bei gleichzeitigen Anfragen kann dieselbe Nummer vergeben werden. +**Quelle:** `bill.service.ts` — `createBillWithRetry()`; `CLAUDE.md` (bekannte Bugs) +**Evidenz:** Kein BEGIN TRANSACTION / SELECT FOR UPDATE vor Nummernvergabe +**Priorität:** High +**Status:** UNKLAR (bekanntes offenes Problem, kein Fix in Code sichtbar) + +--- + +**REQ-064** +**Titel:** Rechnung-PDF abrufen +**Beschreibung:** `GET /api/v1/bills/:id/pdf` gibt das vorhandene PDF zurück. Wenn kein PDF existiert, gibt es 404 zurück — KEINE Neugenerierung. +**Quelle:** `bill.controller.ts` — `getPdf()`; Bug #2 gefixt +**Evidenz:** `if (!fs.existsSync(path)) throw NotFoundException` +**Priorität:** High +**Status:** CONFIRMED + +--- + +**REQ-065** +**Titel:** Rechnung-PDF neu generieren +**Beschreibung:** `POST /api/v1/bills/:id` generiert das PDF für eine bestehende Rechnung neu. +**Quelle:** `bill.controller.ts` — `recreate()` +**Evidenz:** `billService.regenerateBillPdf(id)` +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-066** +**Titel:** Rechnung updaten +**Beschreibung:** `PUT /api/v1/bills/:id` mit `{ billNumber: string, billDate: string (ISO) }` aktualisiert eine Rechnung. +**Quelle:** `bill.controller.ts` — `update()`; `dto/update-bill.dto.ts` +**Evidenz:** `@IsString()` billNumber, `@IsDateString()` billDate +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-067** +**Titel:** Bill-State bei Lieferschein-Änderung +**Beschreibung:** Wenn einer Rechnung zugeordnete Lieferscheine geändert werden (Order-Entry update), wechselt der Rechnungs-State auf CHANGED. +**Quelle:** `order-entry.service.ts` — `update()` +**Evidenz:** `if (entry.slipsheet.billId) billService.changed(bill)` +**Priorität:** Med +**Status:** INFERIERT (Derived-from-code) + +--- + +### Dashboard + +--- + +**REQ-070** +**Titel:** Dashboard Summary +**Beschreibung:** `GET /api/v1/dashboard/summary` gibt KPIs zurück: offene/geänderte Lieferscheine, offene Rechnungen, Niedrig-Bestand-Artikel, Top-Moving Articles, Stock-Trend, Inventur-Aktivitäten. +**Quelle:** `dashboard.controller.ts`, `dashboard.service.ts` +**Evidenz:** `getSummary()` Return-Objekt +**Priorität:** Med +**Status:** CONFIRMED + +--- + +**REQ-071** +**Titel:** Dashboard Query-Parameter Validierung +**Beschreibung:** `days` muss `>= 1` und `<= 120` sein (wird durch Service normalisiert). `threshold` muss `>= 0` und `<= 100000`. `from`/`to` müssen ISO-Datumsstrings sein. Bei ungültigen Werten werden Default-Werte verwendet (days=30, threshold=10). +**Quelle:** `dashboard.service.ts` — `normalizeNumber()`; `dto/dashboard-summary-query.dto.ts` +**Evidenz:** `@Min(1)`, `@IsInt` Decorators; Service-seitige Normalisierung +**Priorität:** Med +**Status:** CONFIRMED + +--- + +### Users + +--- + +**REQ-080** +**Titel:** Benutzer CRUD +**Beschreibung:** `GET/PUT/DELETE /api/v1/users` und `GET /api/v1/users/:id` für Benutzerverwaltung. +**Quelle:** `users.controller.ts` +**Evidenz:** Controller-Handler +**Priorität:** Low +**Status:** CONFIRMED + +--- + +**REQ-081** +**Titel:** Auth-Schutz (DEAKTIVIERT / OFFEN) +**Beschreibung:** Auth-Guards sind aktuell auskommentiert. Die API ist vollständig offen erreichbar ohne Token. +**Quelle:** `CLAUDE.md` (P0 — "Auth komplett aktivieren oder entfernen") +**Evidenz:** `@ApiBearerAuth` Decorator vorhanden, aber Guard nicht aktiv +**Priorität:** High +**Status:** UNKLAR (bewusste Entscheidung oder Fehler?) + +--- + +## C) UNKLARHEITEN & OFFENE FRAGEN + +| ID | Frage | Warum unklar | Was klärt es | +|----|-------|--------------|--------------| +| UQ-001 | Welcher HTTP-Status wird bei UNIQUE-Constraint-Verletzung (doppelte customerNumber / billNumber) zurückgegeben? | ErrorFilter fängt DB-Fehler ab, aber spezifischer Status nicht dokumentiert | Klärung ErrorFilter-Mapping oder Test gegen echte DB | +| UQ-002 | Soll Auth (JWT Guard) aktiviert werden oder dauerhaft deaktiviert bleiben? | CLAUDE.md listet es als P0 offen | Explizite Produktentscheidung erforderlich | +| UQ-003 | Darf `DELETE /api/v1/customers/:id/discounts/:dId` bereits implementiert sein? Es ist in CLAUDE.md dokumentiert aber kein Controller-Handler gefunden. | Kein Handler im Code gefunden | Code-Review / Bestätigung ob Route absichtlich fehlt | +| UQ-004 | Welches Format haben Lieferscheinnummern mit `/`? (die bei generateNumber() ausgeschlossen werden) | Keine Dokumentation für dieses Format gefunden | Klärung Business-Logik: wann werden `/`-Nummern vergeben? | +| UQ-005 | Was passiert wenn `generateBill()` bei einem Fehler die erstellte Rechnung zurückrollt — ist das bereits implementiert oder noch offen? | CLAUDE.md listet es als known bug, Code-Review zeigte try/catch aber unklarer Rollback | Code-Prüfung des Transaktionsverhaltens | +| UQ-006 | Welchen HTTP-Status gibt `POST /slipsheets/:id/close` zurück wenn der Lieferschein keine Einträge hat? | Code wirft Exception, genaue Exception-Klasse + Statuscode nicht verifiziert | Code-Prüfung der Exception-Klasse | +| UQ-007 | Ist `UsersService.getByName()` ein Bug (parameter typed as `number` but used as string)? | `getByName(name: number)` aber SQL-Query als String | Code-Review erforderlich | +| UQ-008 | `state`-Filter in `GET /bills` und `GET /slipsheets` — werden ungültige State-Werte ignoriert oder führen zu einem Fehler? | Keine DTO-Validierung für `?state` Query-Parameter sichtbar | Code-Prüfung der Service-Implementierung | + +--- + +## D) RISIKEN & TESTAUSWIRKUNGEN + +| ID | Risiko | Quelle | Betroffene REQs | Testauswirkung | +|----|--------|--------|-----------------|----------------| +| R-001 | **Race Condition Nummernvergabe**: `MAX(billNumber/slipsheetnumber) + 1` ohne DB-Lock | `bill.service.ts`, `slipsheet.service.ts`; CLAUDE.md | REQ-052, REQ-063 | Nebenläufige Tests könnten unvorhersehbare Nummern produzieren; Tests müssen sequenziell laufen | +| R-002 | **Auth deaktiviert**: Alle Endpoints ohne Authentifizierung erreichbar | `CLAUDE.md` P0 | REQ-081 | Tests können ohne Token durchgeführt werden, aber Auth-Aktivierung würde alle Tests brechen | +| R-003 | **PDF-Dateisystem-Abhängigkeit**: `savePDFToFileSystem()` schreibt auf Disk — fehlt Verzeichnis → Error | `pdfmaker.service.ts` | REQ-049, REQ-064, REQ-065 | Integrationstests brauchen schreibbares Temp-Verzeichnis | +| R-004 | **SQLite-native-Binary**: `sqlite3` braucht kompiliertes Native-Binary | CLAUDE.md Setup | Alle DB-Tests | Tests könnten auf CI fehlschlagen wenn Binary fehlt | +| R-005 | **Silent Inventory-Failure**: Fehler bei Inventory-Erstellung wird verschluckt | `article.service.ts` | REQ-016 | Test muss verifizieren dass Artikel-Update trotzdem durchgeht UND Inventory nicht erstellt wird | +| R-006 | **inventoryDate-Typ**: War früher `@CreateDateColumn`, jetzt `@Column` — Migration könnte fehlen | CLAUDE.md Bug #4 | REQ-015 | Wenn DB ohne Migration → Column-Typ-Konflikt möglich | +| R-007 | **Offener DELETE-Endpunkt für Discounts**: CLAUDE.md dokumentiert Route, aber kein Controller-Handler sichtbar | UQ-003 | REQ-036 | Frontend könnte 404 beim Löschen von Rabatten erhalten | +| R-008 | **`state`-Filter im Frontend nicht verarbeitet**: CLAUDE.md P2 — Backend ignoriert `?state`-Filter | CLAUDE.md P2 | REQ-040, REQ-060 | Tests müssen verifizieren ob State-Filter tatsächlich funktioniert | + +--- + +## E) TESTDIMENSIONEN + +Basierend auf belegbaren Eingaben: + +| Dimension | Werte | Belegt durch | +|-----------|-------|--------------| +| **Datenvarianten** | Existente ID / nicht-existente ID / ungültige ID (String, 0, negativ) | Alle Controller mit `:id`-Parametern | +| **Pflichtfelder** | Alle vorhanden / einzelne fehlend / komplett leer | DTOs mit `@IsString`, `@IsNumber` ohne Optional | +| **Enum-Werte (State)** | `open`, `closed`, `changed` (Slipsheet); `open`, `closed` (Bill) | Entity-Definitionen | +| **Order-Entry Typen** | Artikel-Entry / Text-Entry / Beides / Keines | `order-entry.service.ts` Validierung | +| **singlePos-Flag** | `true` / `false` | `article.entity.ts`, `order-entry.service.ts` | +| **Rabatt-Szenarien** | Kein Rabatt / Kundenrabatt / Artikelgruppen-Rabatt / Beide | `order-entry.service.ts` | +| **CSV-Import Modi** | Preview=true / Preview=false / Ungültige Datei | `article.controller.ts` | +| **Dashboard Query** | Ohne Parameter / Mit allen Parametern / Grenzwerte (days=1, days=120, threshold=0) | DTO + Service Normalisierung | + +**Nicht belegbare Dimensionen:** +- Rollen / Berechtigungen (Auth deaktiviert → keine Rollentests möglich) +- Locale / Mehrsprachigkeit (keine Evidenz) +- Performance-Schwellenwerte (keine Evidenz) + +--- + +## F) TESTMATRIX (Traceability) + +| REQ-ID | Feature / Komponente | Szenario | Testtyp | Prio | +/- | Datenvarianten | Erwartetes Ergebnis | Auto | Notes | +|--------|---------------------|----------|---------|------|-----|---------------|--------------------|----|-------| +| REQ-001 | API Routing | Alle Endpunkte unter `/api/v1` erreichbar | API | High | + | GET /api/v1/articles | HTTP 200 | Yes | | +| REQ-002 | Response-Wrapper | Erfolgreiche Response hat `success: true, data: T` | API | High | + | Standard GET | `{ success: true, data: [...] }` | Yes | | +| REQ-002 | Response-Wrapper | Fehlerhafte Response hat `success: false` | API | High | - | Ungültige ID | `{ success: false, statusCode, error, message }` | Yes | | +| REQ-003 | ValidationPipe | Unbekannte Felder werden verworfen (whitelist) | API | High | - | Extra-Feld in POST | Extra-Feld nicht in Response/DB | Yes | | +| REQ-003 | ValidationPipe | Fehlende Pflichtfelder → 400 | API | High | - | POST ohne `name` | HTTP 400 | Yes | | +| REQ-010 | ArticleController | Alle Artikel abrufen | API | High | + | Standard | HTTP 200, Array | Yes | | +| REQ-010 | ArticleController | Artikel nach Barcode filtern | API | High | + | `?code=ABC` | Gefundener Artikel | Yes | | +| REQ-010 | ArticleController | Barcode nicht gefunden | API | High | - | `?code=NICHTEXISTENT` | HTTP 404 oder leeres Array | BLOCKED (UQ-008) | Genaues Verhalten unklar | +| REQ-011 | ArticleService | Stock-Berechnung korrekt | Unit | High | + | inventoryStock=10, totalAmount=3 | stock=7 | Yes | | +| REQ-012 | ArticleController | Artikel anlegen mit allen Pflichtfeldern | API | High | + | Valid DTO | HTTP 201, Artikel zurück | Yes | | +| REQ-012 | ArticleController | Artikel anlegen ohne Pflichtfeld `name` | API | High | - | DTO ohne name | HTTP 400 | Yes | | +| REQ-013 | ArticleController | Artikel updaten existente ID | API | High | + | PATCH mit valid ID | HTTP 200, aktualisierter Artikel | Yes | | +| REQ-013 | ArticleController | Artikel updaten nicht-existente ID | API | High | - | PATCH mit id=99999 | HTTP 404 | Yes | | +| REQ-014 | ArticleController | Artikel löschen existente ID | API | High | + | DELETE valid ID | HTTP 200, data: null | Yes | | +| REQ-014 | ArticleController | Artikel löschen nicht-existente ID | API | High | - | DELETE id=99999 | HTTP 404 | Yes | | +| REQ-015 | ArticleService | Inventurbuchung setzt neuen Stock | Unit | High | + | newStock=20, alt=10 | inventoryStock=20, diff=10 | Yes | | +| REQ-015 | ArticleService | Inventurbuchung erstellt Inventory-Eintrag | Unit | High | + | makeInventory() | InventoryEntity mit diff=10 | Yes | | +| REQ-016 | ArticleService | Inventory-Fehler blockiert nicht das Artikel-Update | Unit | Med | - | inventoryService.create() wirft Error | Artikel wird trotzdem gespeichert | Yes | Potential Defect wenn try/catch entfernt | +| REQ-017 | ArticleController | CSV-Import Preview | API | Med | + | POST /import?preview=true + Datei | HTTP 200, Preview-Liste | Maybe | Benötigt Multipart-Setup | +| REQ-018 | ArticleController | CSV-Import Ausführung | API | Med | + | POST /import ohne Preview | HTTP 200, [success, failed] | Maybe | | +| REQ-019 | ArticleService | CSV Preisberechnung | Unit | Med | + | brutto=10.99, pe=2 | price=5.50 (Math.ceil) | Yes | | +| REQ-030 | CustomerController | Alle Kunden abrufen | API | High | + | GET /customers | HTTP 200, Array | Yes | | +| REQ-031 | CustomerController | Kunde by ID mit Discounts | API | High | + | GET /customers/1 | HTTP 200, Kunde mit discounts[] | Yes | | +| REQ-031 | CustomerController | Kunde nicht gefunden | API | High | - | GET /customers/99999 | HTTP 404 | Yes | | +| REQ-032 | CustomerController | Kunde anlegen | API | High | + | POST mit customerNumber | HTTP 201 | Yes | | +| REQ-033 | CustomerController | Doppelte customerNumber | API | High | - | POST mit existenter Number | HTTP 4xx (Status UNKLAR) | BLOCKED (UQ-001) | | +| REQ-034 | CustomerController | Lieferscheine eines Kunden | API | High | + | GET /customers/1/slipsheets | HTTP 200, Array | Yes | | +| REQ-035 | CustomerController | Rechnungen eines Kunden | API | High | + | GET /customers/1/bills | HTTP 200, Array | Yes | | +| REQ-036 | CustomerController | Rabatt erstellen | API | Med | + | PUT /customers/1/discounts ohne id | HTTP 200, neuer Discount | Yes | | +| REQ-036 | CustomerController | Rabatt updaten | API | Med | + | PUT /customers/1/discounts mit id | HTTP 200, aktualisierter Discount | Yes | | +| REQ-037 | DiscountService | Rabatt-ArticleGroup unveränderlich | Unit | Med | - | Update mit anderem articleGroupId | Fehler (Status UNKLAR) | BLOCKED (UQ-001) | | +| REQ-040 | SlipsheetController | Alle Lieferscheine abrufen | API | High | + | GET /slipsheets | HTTP 200, Array | Yes | | +| REQ-040 | SlipsheetController | Lieferscheine nach customerId filtern | API | High | + | GET /slipsheets?customerId=1 | Nur OPEN/CHANGED Lieferscheine | Yes | | +| REQ-041 | SlipsheetController | Lieferschein by ID | API | High | + | GET /slipsheets/1 | HTTP 200, mit Relationen | Yes | | +| REQ-041 | SlipsheetController | Lieferschein nicht gefunden | API | High | - | GET /slipsheets/99999 | HTTP 404 | Yes | Bug #1 Fix verifizieren | +| REQ-044 | OrderEntryService | Artikel UND Text gleichzeitig → 400 | Unit | High | - | article + text gesetzt | BadRequest 400 | Yes | | +| REQ-044 | OrderEntryService | Weder Artikel noch Text → 400 | Unit | High | - | article = null, text = null | BadRequest 400 | Yes | | +| REQ-045 | OrderEntryService | Artikel hinzufügen erhöht Menge | Unit | High | + | singlePos=false, Entry existiert | amount += 1 | Yes | | +| REQ-046 | OrderEntryService | singlePos erzeugt neuen Entry | Unit | Med | + | singlePos=true, Entry existiert | Neuer Entry | Yes | | +| REQ-047 | OrderEntryService | Rabatte werden gespeichert | Unit | High | + | Kunde mit Rabatt + Artikelgruppe | customerRabatt und articleGroupRabatt gesetzt | Yes | | +| REQ-048 | SlipsheetController | Lieferschein schließen | API | High | + | POST /slipsheets/1/close | HTTP 200, state=closed | Yes | | +| REQ-048 | SlipsheetController | Leeren Lieferschein schließen → Fehler | API | High | - | Lieferschein ohne Entries | HTTP 4xx | BLOCKED (UQ-006) | | +| REQ-052 | SlipsheetService | Lieferscheinnummer Generierung | Unit | Med | + | MAX=5 | Nächste Nummer=6 | Yes | | +| REQ-053 | OrderEntryController | Entry updaten | API | High | + | PUT /order-entries/1 | HTTP 200, aktualisierter Entry | Yes | | +| REQ-053 | OrderEntryController | Entry mit amount=0 löschen | API | High | + | PUT /order-entries/1 amount=0 | Entry gelöscht, Lieferschein CHANGED | Yes | | +| REQ-054 | SlipsheetService | State-Änderung bei Entry-Update | Unit | Med | + | CLOSED Lieferschein, Entry geändert | Lieferschein state=CHANGED | Yes | | +| REQ-060 | BillController | Alle Rechnungen abrufen | API | High | + | GET /bills | HTTP 200, Array | Yes | | +| REQ-061 | BillService | Rechnung aus CLOSED Lieferscheinen | Unit | High | + | Alle Slipsheets closed, gleicher Kunde | Rechnung erstellt | Yes | | +| REQ-061 | BillService | Rechnung aus OPEN Lieferschein → Fehler | Unit | High | - | Slipsheet state=open | Exception | Yes | | +| REQ-061 | BillService | Rechnung aus verschiedenen Kunden → Fehler | Unit | High | - | Slipsheets verschiedener Kunden | Exception | Yes | | +| REQ-063 | BillService | Rechnungsnummer eindeutig (Race Condition) | NON-REQ | High | - | Parallele Requests | BLOCKED — kein DB-Lock | BLOCKED (R-001) | Known Bug | +| REQ-064 | BillController | PDF abrufen wenn vorhanden | API | High | + | GET /bills/1/pdf, PDF existiert | StreamableFile | Maybe | Filesystem-Setup nötig | +| REQ-064 | BillController | PDF abrufen wenn nicht vorhanden → 404 | API | High | - | GET /bills/1/pdf, PDF fehlt | HTTP 404 | Yes | Bug #2 Fix verifizieren | +| REQ-065 | BillController | PDF neu generieren | API | Med | + | POST /bills/1 | HTTP 200, Rechnung | Maybe | Filesystem nötig | +| REQ-066 | BillController | Rechnung updaten | API | Med | + | PUT /bills/1 | HTTP 200, aktualisierte Rechnung | Yes | | +| REQ-067 | OrderEntryService | Bill-State CHANGED bei Entry-Update | Unit | Med | + | Entry in Slipsheet mit Bill | Bill state=CHANGED | Yes | | +| REQ-070 | DashboardController | Summary abrufen | API | Med | + | GET /dashboard/summary | HTTP 200, KPI-Objekt | Yes | | +| REQ-071 | DashboardService | Normalisierung days | Unit | Med | + | days=0 → default 30; days=200 → 120 | Normalisierter Wert | Yes | | +| REQ-081 | Auth | Auth ist deaktiviert | NON-REQ | High | + | Beliebiger Request ohne Token | HTTP 200 (kein 401) | Yes | Dokumentiert als bekanntes Risiko | + +--- + +## G) TESTFÄLLE (Spezifikation — Given/When/Then) + +> Framework: **framework-agnostisch** (UNBELEGT) +> Setup-Annahme: In-Memory SQLite Testdatenbank oder Test-SQLite-Datei, HTTP-Testclient (z.B. supertest-äquivalent) + +--- + +### TC-001 — Artikel-Liste abrufen +**REQ-ID:** REQ-010 +**Titel:** GET /articles gibt alle Artikel zurück +**Typ:** API/Integration +**Status:** READY + +**Setup:** Datenbank mit 2 Artikeln befüllt + +**Given** die Datenbank enthält 2 Artikel (id=1, code="A001"; id=2, code="A002") +**When** `GET /api/v1/articles` +**Then** +- HTTP Status: `200` +- Body: `{ success: true, data: [Article, Article] }` +- `data.length === 2` +- Jeder Artikel enthält Feld `stock` (number) + +**Orakel:** REQ-010, REQ-011, REQ-002 +**Automatisierung:** Yes + +--- + +### TC-002 — Artikel-Stock-Berechnung +**REQ-ID:** REQ-011 +**Titel:** stock = inventoryStock - totalAmount +**Typ:** Unit +**Status:** READY + +**Setup:** Artikel mit `inventoryStock=10`, dazu 3 OrderEntries mit amount=1 + +**Given** ein Artikel hat `inventoryStock=10` +**And** 3 OrderEntries referenzieren den Artikel mit je `amount=1` +**When** `articleService.get(articleId)` aufgerufen wird +**Then** +- `article.stock === 7` +- `article.inventoryStock === 10` + +**Orakel:** REQ-011 (Derived-from-code) +**Automatisierung:** Yes + +--- + +### TC-003 — Artikel anlegen: Erfolgsfall +**REQ-ID:** REQ-012 +**Titel:** POST /articles legt neuen Artikel an +**Typ:** API +**Status:** READY + +**Given** keine Artikel in der DB +**When** `POST /api/v1/articles` mit Body: +```json +{ "name": "Test", "code": "X001", "price": 9.99, "type": "T", "unit": "Stk", "artNumber": "001", "articleGroup": { "id": 1 } } +``` +**Then** +- HTTP Status: `201` +- Body: `{ success: true, data: { id: number, name: "Test", code: "X001", ... } }` + +**Orakel:** REQ-012 +**Automatisierung:** Yes + +--- + +### TC-004 — Artikel anlegen: Pflichtfeld fehlt +**REQ-ID:** REQ-012, REQ-003 +**Titel:** POST /articles ohne Pflichtfeld `name` → 400 +**Typ:** API +**Status:** READY + +**Given** valide DB +**When** `POST /api/v1/articles` mit Body ohne `name` +**Then** +- HTTP Status: `400` +- Body: `{ success: false, ... }` + +**Orakel:** REQ-003 (ValidationPipe) +**Automatisierung:** Yes + +--- + +### TC-005 — Artikel updaten: nicht-existente ID +**REQ-ID:** REQ-013 +**Titel:** PATCH /articles/:id mit ungültiger ID → 404 +**Typ:** API +**Status:** READY + +**Given** Artikel mit id=99999 existiert nicht +**When** `PATCH /api/v1/articles/99999` mit `{ "name": "Neu" }` +**Then** +- HTTP Status: `404` + +**Orakel:** REQ-013 +**Automatisierung:** Yes + +--- + +### TC-006 — Inventurbuchung: Normalfall +**REQ-ID:** REQ-015 +**Titel:** POST /articles/:id/inventory setzt neuen Stock +**Typ:** API +**Status:** READY + +**Setup:** Artikel mit id=1, `inventoryStock=5` + +**Given** Artikel id=1 mit inventoryStock=5 +**When** `POST /api/v1/articles/1/inventory` mit `{ "newStock": 20 }` +**Then** +- HTTP Status: `200` +- `data.inventoryStock === 20` +- In der DB existiert ein Inventory-Eintrag mit `amountNew=20`, `diff=15` + +**Orakel:** REQ-015 +**Automatisierung:** Yes + +--- + +### TC-007 — Inventurbuchung: Fehlertoleranz bei Inventory-Erstellung +**REQ-ID:** REQ-016 +**Titel:** Inventory-Erstellungsfehler blockiert nicht den Artikel-Update +**Typ:** Unit +**Status:** READY + +**Setup:** Mock `inventoryService.create()` wirft Error + +**Given** `inventoryService.create` wird mit Error gemockt +**When** `articleService.makeInventory(article, newStock)` aufgerufen wird +**Then** +- Kein Error wird nach außen geworfen +- `article.inventoryStock` wurde aktualisiert + +**Orakel:** REQ-016 +**Potential Defect:** Wenn try/catch entfernt wird, bricht dieser Test — dann ist REQ-016 verletzt +**Automatisierung:** Yes + +--- + +### TC-008 — CSV Preisberechnung +**REQ-ID:** REQ-019 +**Titel:** Preisformel Math.ceil((brutto/pe)*100)/100 +**Typ:** Unit +**Status:** READY + +**Given** brutto=10.99, pe=2 +**When** `importCsv()` berechnet den Preis +**Then** `price === 5.50` + +**Given** brutto=10.00, pe=3 +**When** berechnet +**Then** `price === Math.ceil((10/3)*100)/100 === 3.34` + +**Orakel:** REQ-019 (Derived-from-code) +**Automatisierung:** Yes + +--- + +### TC-009 — Order-Entry: Artikel UND Text → 400 +**REQ-ID:** REQ-044 +**Titel:** Gleichzeitig article + text → BadRequest +**Typ:** Unit +**Status:** READY + +**Given** valide Slipsheet und Kunde +**When** `orderEntryService.addOrderToSlipsheet({ article: {id:1}, text: "Manuell", ... })` +**Then** +- `BadRequestException` wird geworfen + +**Orakel:** REQ-044 +**Automatisierung:** Yes + +--- + +### TC-010 — Order-Entry: Weder Artikel noch Text → 400 +**REQ-ID:** REQ-044 +**Titel:** Weder article noch text → BadRequest +**Typ:** Unit +**Status:** READY + +**Given** valide Slipsheet und Kunde +**When** `orderEntryService.addOrderToSlipsheet({ article: null, text: null, ... })` +**Then** +- `BadRequestException` wird geworfen + +**Orakel:** REQ-044 +**Automatisierung:** Yes + +--- + +### TC-011 — Order-Entry: Mengenerhöhung (singlePos=false) +**REQ-ID:** REQ-045 +**Titel:** Zweiter gleicher Artikel erhöht Menge +**Typ:** Unit +**Status:** READY + +**Setup:** Artikel mit singlePos=false; Lieferschein mit existierendem Entry für diesen Artikel (amount=2) + +**Given** Lieferschein hat bereits Entry mit Artikel A (amount=2) +**And** Artikel A hat singlePos=false +**When** Artikel A erneut hinzugefügt (amount=1) +**Then** Entry.amount === 3 (erhöht, kein neuer Entry) + +**Orakel:** REQ-045 +**Automatisierung:** Yes + +--- + +### TC-012 — Order-Entry: singlePos erzeugt neuen Entry +**REQ-ID:** REQ-046 +**Titel:** singlePos=true erzeugt immer neuen Entry +**Typ:** Unit +**Status:** READY + +**Setup:** Artikel mit singlePos=true; Lieferschein mit existierendem Entry + +**Given** Entry für Artikel A existiert bereits (amount=2) +**And** Artikel A hat singlePos=true +**When** Artikel A erneut hinzugefügt +**Then** Neuer Entry erstellt, gesamt 2 Entries für Artikel A + +**Orakel:** REQ-046 +**Automatisierung:** Yes + +--- + +### TC-013 — Rechnung generieren: Erfolgsfall +**REQ-ID:** REQ-061 +**Titel:** generateBill mit validen CLOSED Lieferscheinen +**Typ:** Unit +**Status:** READY + +**Setup:** 2 CLOSED Lieferscheine, gleicher Kunde + +**Given** Lieferschein 1 (state=CLOSED, customer=K1) +**And** Lieferschein 2 (state=CLOSED, customer=K1) +**When** `billService.generateBill([LS1, LS2])` +**Then** +- Rechnung erstellt +- `bill.state === "closed"` +- LS1 und LS2 referenzieren die neue Rechnung + +**Orakel:** REQ-061 +**Automatisierung:** Yes + +--- + +### TC-014 — Rechnung generieren: Lieferschein OPEN → Fehler +**REQ-ID:** REQ-061 +**Titel:** generateBill mit OPEN Lieferschein → Exception +**Typ:** Unit +**Status:** READY + +**Given** Lieferschein 1 (state=OPEN) +**When** `billService.generateBill([LS1])` +**Then** Exception wird geworfen + +**Orakel:** REQ-061 +**Automatisierung:** Yes + +--- + +### TC-015 — Rechnung generieren: Verschiedene Kunden → Fehler +**REQ-ID:** REQ-061 +**Titel:** generateBill mit Lieferscheinen verschiedener Kunden → Exception +**Typ:** Unit +**Status:** READY + +**Given** LS1 (state=CLOSED, customer=K1), LS2 (state=CLOSED, customer=K2) +**When** `billService.generateBill([LS1, LS2])` +**Then** Exception wird geworfen + +**Orakel:** REQ-061 +**Automatisierung:** Yes + +--- + +### TC-016 — Rechnung-PDF: Nicht vorhanden → 404 +**REQ-ID:** REQ-064 +**Titel:** GET /bills/:id/pdf wenn kein PDF → 404 (kein Regenerate) +**Typ:** API +**Status:** READY + +**Setup:** Rechnung in DB, aber kein PDF auf Filesystem + +**Given** Rechnung id=1 existiert, PDF-Datei existiert NICHT +**When** `GET /api/v1/bills/1/pdf` +**Then** +- HTTP Status: `404` +- PDF wird NICHT neu generiert + +**Orakel:** REQ-064; Bug #2 Fix +**Potential Defect:** Wenn Bug #2 nicht vollständig gefixt → PDF wird trotzdem generiert → Test schlägt fehl → Code defekt +**Automatisierung:** Yes (Filesystem-Mock nötig) + +--- + +### TC-017 — Lieferschein by ID: Bug #1 Fix verifizieren +**REQ-ID:** REQ-041 +**Titel:** GET /slipsheets/:id gibt korrekten Lieferschein zurück +**Typ:** API +**Status:** READY + +**Setup:** Lieferschein mit id=5 + +**Given** Lieferschein id=5 existiert +**When** `GET /api/v1/slipsheets/5` +**Then** +- HTTP Status: `200` +- `data.id === 5` +- `data` ist kein `undefined` + +**Orakel:** REQ-041; Bug #1 Fix +**Potential Defect:** Wenn Cast `+id` fehlt → `data === undefined` → Test schlägt fehl → Code defekt +**Automatisierung:** Yes + +--- + +### TC-018 — Order-Entry amount=0 löscht Entry +**REQ-ID:** REQ-053 +**Titel:** PUT /order-entries/:id mit amount=0 löscht Entry und markiert Lieferschein CHANGED +**Typ:** API +**Status:** READY + +**Setup:** Entry id=1 in CLOSED Lieferschein id=2 + +**Given** OrderEntry id=1, amount=3 in Lieferschein id=2 (CLOSED) +**When** `PUT /api/v1/order-entries/1` mit `{ "amount": 0 }` +**Then** +- Entry id=1 nicht mehr in DB +- Lieferschein id=2 state === "changed" + +**Orakel:** REQ-053, REQ-054 +**Automatisierung:** Yes + +--- + +### TC-019 — Dashboard Summary: Normalfall +**REQ-ID:** REQ-070 +**Titel:** GET /dashboard/summary gibt KPI-Objekt zurück +**Typ:** API +**Status:** READY + +**Given** Standard-Datenbestand (mindestens 1 Artikel, 1 Lieferschein) +**When** `GET /api/v1/dashboard/summary` +**Then** +- HTTP Status: `200` +- Body enthält Felder: `kpis`, `lowStockItems`, `topMovingArticles`, `stockTrend`, `documentQueue`, `recentInventoryActivities` + +**Orakel:** REQ-070 +**Automatisierung:** Yes + +--- + +### TC-020 — Dashboard Query: Normalisierung +**REQ-ID:** REQ-071 +**Titel:** Ungültige days/threshold werden normalisiert +**Typ:** Unit +**Status:** READY + +**Given** Query: `days=0`, `threshold=-5` +**When** `dashboardService.getSummary({ days: 0, threshold: -5 })` +**Then** Interne Normalisierung verwendet `days=30` (Default), `threshold=0` (Minimum) + +**Given** Query: `days=500` +**When** `dashboardService.getSummary({ days: 500 })` +**Then** `days` wird auf 120 begrenzt + +**Orakel:** REQ-071 +**Automatisierung:** Yes + +--- + +### TC-021 — Whitelist: Extra-Felder werden verworfen +**REQ-ID:** REQ-003 +**Titel:** ValidationPipe verwirft unbekannte Felder +**Typ:** API +**Status:** READY + +**Given** valide API +**When** `POST /api/v1/customers` mit `{ "customerNumber": "K001", "hackerField": "malicious" }` +**Then** +- HTTP Status: `201` +- Gespeicherte Entity hat kein `hackerField` + +**Orakel:** REQ-003 +**Automatisierung:** Yes + +--- + +### TC-022 — Rabatt-Update: ArticleGroup unveränderlich +**REQ-ID:** REQ-037 +**Titel:** Rabatt-Update mit geänderter ArticleGroup → Fehler +**Typ:** Unit +**Status:** BLOCKED + +**Blocker:** UQ-001 — genaue Exception/HTTP-Status unklar + +**Given** Discount id=1 mit articleGroupId=5 +**When** `discountService.createOrUpdate(customer, { id: 1, articleGroup: {id: 9}, value: 10 })` +**Then** Exception wird geworfen (genaue Klasse: BLOCKED) + +**Orakel:** REQ-037 +**Automatisierung:** Blocked + +--- + +### TC-023 — Auth deaktiviert: Requests ohne Token erfolgreich +**REQ-ID:** REQ-081 +**Titel:** API ohne Authorization-Header erreichbar +**Typ:** API / NON-REQ +**Status:** READY + +**Given** kein `Authorization: Bearer ...` Header +**When** `GET /api/v1/articles` +**Then** +- HTTP Status: `200` (kein 401) + +**Orakel:** REQ-081 +**Automatisierung:** Yes +**Hinweis:** Test dokumentiert aktuellen Ist-Zustand. Wenn Auth aktiviert wird, muss dieser Test angepasst werden. + +--- + +### TC-024 — Lieferschein-State: customerId-Filter +**REQ-ID:** REQ-040 +**Titel:** GET /slipsheets?customerId=X gibt nur OPEN/CHANGED zurück +**Typ:** API +**Status:** READY + +**Setup:** Kunde K1 mit 3 Lieferscheinen: OPEN, CLOSED, CHANGED + +**Given** Kunde K1 hat: LS1 (OPEN), LS2 (CLOSED), LS3 (CHANGED) +**When** `GET /api/v1/slipsheets?customerId=K1` +**Then** +- `data.length === 2` +- Enthält LS1 (OPEN) und LS3 (CHANGED) +- LS2 (CLOSED) NICHT enthalten + +**Orakel:** REQ-040 +**Automatisierung:** Yes + +--- + +### TC-025 — Lieferschein-Nummer Generierung +**REQ-ID:** REQ-052 +**Titel:** generateNumber gibt MAX+1 zurück +**Typ:** Unit +**Status:** READY + +**Setup:** DB mit Lieferscheinen: slipsheetnumber IN ['1','2','3','5'] + +**Given** höchste ganzzahlige Nummer = 5 +**When** `slipsheetService.generateNumber()` +**Then** Rückgabe: `"6"` + +**Given** keine Lieferscheine in DB +**When** `slipsheetService.generateNumber()` +**Then** Rückgabe: `"1"` + +**Orakel:** REQ-052 +**Automatisierung:** Yes + +--- + +## H) COVERAGE SUMMARY + +| Metrik | Wert | +|--------|------| +| Requirements gesamt | 35 (REQ-001 bis REQ-081) | +| Abgedeckte Requirements | 32 | +| Testfälle gesamt | 25 (TC-001 bis TC-025) | +| READY | 23 | +| BLOCKED | 2 (TC-022) + mehrere Matrix-Zeilen | + +**Fehlende / unvollständige Abdeckung:** + +| REQ-ID | Grund | +|--------|-------| +| REQ-005 | CORS-Konfiguration — Testinfrastruktur für ENV-Variable nötig | +| REQ-017/018 | CSV-Import — Multipart-Datei-Setup fehlt; als Maybe markiert | +| REQ-020 | Artikelgruppen CRUD — nur Low-Prio, kein dedizierter TC | +| REQ-033 | Duplikat-customerNumber — HTTP-Status unklar (UQ-001 offen) | +| REQ-037 | Rabatt unveränderlich — HTTP-Status unklar (UQ-001 offen) | +| REQ-063 | Race Condition Nummernvergabe — kein DB-Lock implementiert; BLOCKED | +| REQ-080 | Users CRUD — Low-Prio; kein dedizierter TC | + +--- + +## I) GAPS & BLOCKERS + +| ID | Beschreibung | Betroffene REQs | Fehlende Info | +|----|-------------|-----------------|---------------| +| GAP-001 | Kein Testframework definiert → Keine lauffähigen Testdateien generiert | Alle | Framework-Entscheidung (Jest/Vitest/Mocha/Supertest) | +| GAP-002 | Kein OpenAPI-Export → Kontraktvalidierung nicht möglich | Alle | Swagger YAML/JSON Export benötigt | +| GAP-003 | PDF-Tests benötigen Filesystem-Mock oder Temp-Verzeichnis | REQ-049, REQ-064, REQ-065 | Setup-Strategie unklar | +| GAP-004 | CSV-Import Tests benötigen Multipart-Fixture | REQ-017, REQ-018 | CSV-Testdatei + Multipart-Client | +| GAP-005 | `DELETE /customers/:id/discounts/:dId` fehlt im Code laut Exploration | REQ-036 | Backend-Implementierung prüfen | +| GAP-006 | Race Condition (REQ-063) nicht testbar ohne DB-Lock | REQ-052, REQ-063 | Transaktionaler Lock implementieren | +| GAP-007 | Auth-Aktivierungspfad nicht getestet | REQ-081 | Entscheidung über Auth-Strategie (UQ-002) | +| BLOCKED-001 | TC-022 (Rabatt-Update) | REQ-037 | HTTP-Status nach Exception unklar (UQ-001) | +| BLOCKED-002 | TC-048-negative (leerer Lieferschein schließen) | REQ-048 | Exception-Klasse und HTTP-Status unklar (UQ-006) | +| BLOCKED-003 | Duplikat-customerNumber-Test | REQ-033 | HTTP-Status nach UNIQUE constraint unklar (UQ-001) | + +--- + +## J) DECISION NEEDED + +| ID | Entscheidung | Betroffene REQs | Widersprüchliche Evidenz / Warum offen | +|----|-------------|-----------------|----------------------------------------| +| DEC-001 | **Testframework** — Jest (NestJS-Standard) oder anderes? | Alle | Kein `jest.config.ts` oder ähnliches im Code gefunden | +| DEC-002 | **Auth** — Guards aktivieren (P0 lt. CLAUDE.md) oder dauerhaft deaktivieren? | REQ-081 | CLAUDE.md: "Option A: JWT Guard global einbauen; Option B: Auth entfernen" | +| DEC-003 | **HTTP-Status bei UNIQUE-Constraint-Verletzung** — 409 Conflict oder 400 Bad Request? | REQ-033, REQ-037 | ErrorFilter fängt allgemein ab; kein spezifischer Handler für DB-Constraints | +| DEC-004 | **Race Condition Fix** — DB-Transaktion mit Lock für Nummernvergabe implementieren? | REQ-063, REQ-052 | CLAUDE.md listet als P1 offen; keine Implementierung sichtbar | +| DEC-005 | **DELETE /customers/:id/discounts/:dId** — Ist dieser Endpunkt implementiert? | REQ-036 | CLAUDE.md dokumentiert Route, kein Controller-Handler im Code gefunden | +| DEC-006 | **Lieferscheinnummer-Format mit '/'** — Was bedeuten diese Nummern? Wann werden sie vergeben? | REQ-052 | Code schließt `/`-Nummern aus `generateNumber()` aus, aber keine Dokumentation warum | +| DEC-007 | **Integrationstests vs. Unit-Tests** — Sollen Tests gegen echte SQLite-DB oder gemockte Repositories laufen? | Alle | Keine Test-Setup-Strategie definiert | + +--- + +## K) TEST FILE MAP + +> **Status:** BLOCKED (GAP-001 — Kein Testframework definiert) +> Sobald Framework entschieden: Test-Dateien können generiert werden. + +**Vorgeschlagene Dateistruktur (bei Jest/NestJS-Standard):** + +``` +apps/server/src/ +├── models/ +│ ├── article/ +│ │ ├── __tests__/ +│ │ │ ├── article.service.spec.ts → TC-001, TC-002, TC-006, TC-007, TC-008 +│ │ │ └── article.controller.spec.ts → TC-003, TC-004, TC-005 +│ ├── bills/ +│ │ ├── __tests__/ +│ │ │ ├── bill.service.spec.ts → TC-013, TC-014, TC-015 +│ │ │ ├── bill.controller.spec.ts → TC-016, TC-060 +│ │ │ ├── slipsheet.service.spec.ts → TC-017, TC-024, TC-025 +│ │ │ ├── slipsheet.controller.spec.ts → TC-041, TC-048 +│ │ │ ├── order-entry.service.spec.ts → TC-009, TC-010, TC-011, TC-012, TC-018 +│ │ │ └── discount.service.spec.ts → TC-022 (BLOCKED) +│ ├── customer/ +│ │ ├── __tests__/ +│ │ │ └── customer.controller.spec.ts → TC-030, TC-031 +├── common/ +│ ├── __tests__/ +│ │ └── validation.spec.ts → TC-021 +├── dashboard/ +│ ├── __tests__/ +│ │ └── dashboard.service.spec.ts → TC-019, TC-020 +└── e2e/ + ├── articles.e2e.spec.ts → TC-001, TC-003, TC-004 + ├── slipsheets.e2e.spec.ts → TC-017, TC-024 + ├── bills.e2e.spec.ts → TC-016 + └── auth.e2e.spec.ts → TC-023 +``` + +--- + +*Ende des Dokuments* +*Generiert von test-agent · 2026-03-17 · SIMS Backend v1 (Branch: first-init)* diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..9ab5406 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,22 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "rootDir": ".", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "es2015", + "module": "esnext", + "lib": ["es2020", "dom"], + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "baseUrl": ".", + "paths": { + "@sim-system/domain": ["libs/domain/src/index.ts"] + } + }, + "exclude": ["node_modules", "tmp"] +}