diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d7cff25..daaaaa0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,9 +1,37 @@ +# Dependabot: Update-PRs gebündelt je Ökosystem, damit sie gemeinsam geprüft und gemergt werden. +# Sicherheitsupdates kommen unabhängig vom Zeitplan sofort (Repo-Einstellung "Dependabot security updates"). version: 2 updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: Europe/Vienna + open-pull-requests-limit: 5 + labels: [dependencies, javascript] + commit-message: + prefix: chore + include: scope + groups: + entwicklungswerkzeuge: + dependency-type: development + update-types: [minor, patch] + # Major-Versionen kommen als eigener PR (können Lint-Regeln oder Node-Anforderungen ändern) + - package-ecosystem: github-actions directory: / schedule: - interval: monthly + interval: weekly + day: monday + time: "06:00" + timezone: Europe/Vienna + open-pull-requests-limit: 5 + labels: [dependencies, github-actions] + commit-message: + prefix: chore + include: scope groups: actions: - patterns: ['*'] + patterns: ["*"] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4dc2d86..3a5bf4f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,6 +6,7 @@ - [ ] Änderungen nur in `src/`, `test/`, `scripts/` oder Doku — keine gebaute HTML eingecheckt - [ ] **Keine echten Reportdaten** (ZIP/CSV, E-Mail-Adressen, Namen) im Diff -- [ ] `npm run gate:data`, `npm run check` und `npm test` lokal grün +- [ ] `npm run gate:data`, `npm run lint` und `npm test` lokal grün - [ ] Im Browser mit `test/sample.zip` geprüft -- [ ] `CHANGELOG.md` ergänzt (Abschnitt `[Unreleased]`) +- [ ] `CHANGELOG.md` ergänzt (Abschnitt `[Unreleased]`) und betroffene Doku im selben PR angepasst + (`npm run gate:docs`; nur wenn wirklich keine Doku nötig ist: Label `keine-doku`) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bbe67b..de6b40f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,8 @@ on: push: branches: [main] pull_request: + # labeled/unlabeled: Doku-Gate reagiert auf das Label "keine-doku" + types: [opened, synchronize, reopened, labeled, unlabeled] workflow_dispatch: permissions: @@ -55,3 +57,73 @@ jobs: name: Hornetsecurity-Report-Viewer path: dist/Hornetsecurity-Report-Viewer.html retention-days: 14 + + lint: + name: Lint (JS, Markdown, PowerShell) + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 22 + cache: npm + + - name: Abhängigkeiten installieren (Lockfile, ohne Install-Skripte) + run: npm ci --ignore-scripts --no-fund --no-audit + + - name: ESLint + run: npm run lint:js + + - name: markdownlint + run: npm run lint:md + + - name: PSScriptAnalyzer + shell: pwsh + run: | + if (-not (Get-Module -ListAvailable -Name PSScriptAnalyzer)) { + Install-Module PSScriptAnalyzer -Scope CurrentUser -Force -ErrorAction Stop + } + ./scripts/lint-ps.ps1 + + docs: + name: Doku-Gate + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 22 + + - name: Doku zum Code passend? + env: + DOCS_GATE_BASE: ${{ github.event.pull_request.base.sha }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} + run: npm run gate:docs + + dependencies: + name: Abhängigkeiten prüfen + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Neue Abhängigkeiten auf Sicherheitslücken und Lizenzen prüfen + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: low + deny-licenses: GPL-2.0-only, GPL-2.0-or-later, GPL-3.0-only, GPL-3.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later + comment-summary-in-pr: never diff --git a/.gitignore b/.gitignore index c2eb610..ec7ddd1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ !/docs/ !/build.ps1 !/package.json +!/package-lock.json +!/eslint.config.js +!/.markdownlint.json !/README.md !/CHANGELOG.md !/LICENSE @@ -21,6 +24,7 @@ /test/* !/test/core.test.js !/test/make-sample.js +!/test/gates.test.js !/test/sample.zip # Echte Report-Dateien nirgends, auch nicht in erlaubten Ordnern diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..3c8c0ed --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/main/schema/markdownlint-config-schema.json", + "default": true, + "MD013": false, + "MD024": { "siblings_only": true }, + "MD033": false, + "MD041": false +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 607949c..bec843d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,23 @@ Versionierung nach [Semantic Versioning](https://semver.org/lang/de/). ## [Unreleased] +### Hinzugefügt + +- Linter als Pull-Request-Check: ESLint, markdownlint und PSScriptAnalyzer (`npm run lint`). +- Doku-Gate als Pull-Request-Check (`scripts/check-docs.js`): CHANGELOG-Eintrag bei + Produktänderungen, Entwickler-Doku bei Werkzeug-Änderungen, Konsistenz von npm-Skripten, + Links und CHANGELOG; Ausnahme per Label `keine-doku`. +- Abhängigkeitsprüfung (Dependency Review) als Pull-Request-Check; Dependabot zusätzlich für + npm, wöchentlich und gebündelt; Dependabot-Sicherheitsupdates aktiviert. + +### Geändert + +- Entwicklung benötigt Node.js ≥ 22.13 (Linter); die ausgelieferte HTML-Datei bleibt ohne Abhängigkeiten. +- `build.ps1` als UTF-8 mit BOM gespeichert, damit Windows PowerShell 5.1 Umlaute in + Meldungen korrekt anzeigt; Statusausgabe über `Write-Output`. + ### Behoben + - Release-Workflow: Release-Notizen werden korrekt aus dem CHANGELOG übernommen; bestehende Tags lassen sich manuell (erneut) veröffentlichen (`workflow_dispatch`). @@ -15,6 +31,7 @@ Versionierung nach [Semantic Versioning](https://semver.org/lang/de/). Erste öffentliche Version. ### Hinzugefügt + - Portable Einzeldatei `Hornetsecurity-Report-Viewer.html`: Report-ZIP des Hornetsecurity Security Awareness Service per Drag & Drop laden, Auswertung komplett offline im Browser. - KPI-Kacheln: ESI mit Vormonats-Delta, simulierte, erfolgreiche und gemeldete Angriffe, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f94caa9..6cbe155 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,20 +10,29 @@ Danke für Ihr Interesse! Fehlermeldungen und Verbesserungen sind willkommen. Die CI bricht ab, wenn sie fremde E-Mail-Domains oder nicht erlaubte Dateien findet. 2. **Nur `src/` ändern**, nie die gebaute HTML-Datei. Sie entsteht mit `build.ps1` in `dist/` und wird nicht eingecheckt. -3. **Keine externen Abhängigkeiten** — die HTML-Datei muss offline über `file://` laufen - (kein CDN, kein `fetch`). +3. **Die HTML-Datei bleibt ohne Abhängigkeiten** — sie muss offline über `file://` laufen + (kein CDN, kein `fetch`). npm-Pakete gibt es nur als Entwicklungswerkzeuge (Linter). 4. **Report-Inhalte nur per `textContent`** in den DOM schreiben, nie per `innerHTML`. +5. **Doku gehört in denselben Pull Request** wie der Code (siehe [Doku-Gate](#doku-gate)). ## Entwicklungsumgebung -Benötigt: [Node.js](https://nodejs.org/) ≥ 20 und PowerShell (Windows PowerShell 5.1 oder -PowerShell 7). Es gibt keine npm-Abhängigkeiten, `npm install` ist nicht nötig. +Benötigt: + +- [Node.js](https://nodejs.org/) ≥ 22.13 +- PowerShell (Windows PowerShell 5.1 oder PowerShell 7) für den Build +- für `npm run lint:ps` das Modul PSScriptAnalyzer: + `Install-Module PSScriptAnalyzer -Scope CurrentUser` ```powershell -npm run gate:data # Daten-Gate: nur erlaubte Dateien, keine echten Reportdaten -npm run check # Syntax-Prüfung -npm test # Test-Suite gegen test/sample.zip -npm run build # → dist/Hornetsecurity-Report-Viewer.html +npm ci # Entwicklungswerkzeuge exakt nach package-lock.json installieren +npm run gate:data # Daten-Gate: nur erlaubte Dateien, keine echten Reportdaten +npm run gate:docs # Doku-Gate gegen origin/main +npm run check # Syntax-Prüfung +npm run lint # alle Linter: npm run lint:js (ESLint), npm run lint:md (markdownlint), + # npm run lint:ps (PSScriptAnalyzer) +npm test # Test-Suiten: Kernlogik gegen test/sample.zip und Doku-Gate-Regeln +npm run build # → dist/Hornetsecurity-Report-Viewer.html (oder .\build.ps1) ``` Danach `dist/Hornetsecurity-Report-Viewer.html` im Browser öffnen und `test/sample.zip` @@ -33,11 +42,46 @@ hineinziehen. Sollwerte des Beispiel-Reports: ESI 85, 163 gesendet, 12 erfolgrei Wird der Generator geändert, `npm run sample` ausführen und die Sollwerte in `test/core.test.js` anpassen. +## Pull-Request-Checks + +Jeder Pull Request gegen `main` muss alle Checks bestehen, bevor er gemergt werden kann: + +| Check | Prüft | +| --- | --- | +| **Gates (Daten, Syntax, Tests, Build)** | Daten-Gate, Syntax, beide Test-Suiten, Build und Build-Ergebnis | +| **Lint (JS, Markdown, PowerShell)** | ESLint (`eslint.config.js`), markdownlint (`.markdownlint.json`), PSScriptAnalyzer | +| **Doku-Gate** | Doku passt zum Code (siehe unten) | +| **Abhängigkeiten prüfen** | Neue oder geänderte npm-Pakete und Actions: keine bekannten Sicherheitslücken, keine GPL/AGPL-Lizenzen | + +### Doku-Gate + +`scripts/check-docs.js` vergleicht den Pull Request mit `main`: + +- **Produkt geändert** (`src/`, `build.ps1`) → Eintrag in `CHANGELOG.md` unter `[Unreleased]` + (bzw. im Abschnitt der neuen Version bei einem Release-PR). +- **Entwicklungs-Werkzeuge geändert** (Workflows, `scripts/`, Linter-Konfiguration, npm-Skripte, + `test/make-sample.js`, `.gitignore`, Dependabot) → `CONTRIBUTING.md`, `README.md` oder + `CHANGELOG.md` anpassen. +- **Version geändert** → `CHANGELOG.md` anpassen. +- **Immer:** Jedes npm-Skript ist hier oder im README dokumentiert, relative Links in + Markdown-Dateien funktionieren, jede Version im CHANGELOG hat Datum und Link-Referenz. + +Ausnahmen: Dependabot-PRs, die nur Abhängigkeiten ändern, brauchen keine Doku. Ist in einem +anderen Fall wirklich keine Doku nötig (z. B. reiner Tippfehler im Code-Kommentar), das Label +**`keine-doku`** setzen — der Check meldet dann eine Warnung statt eines Fehlers. + +### Abhängigkeiten und Dependabot + +Dependabot prüft npm-Pakete und GitHub Actions jeden Montag und bündelt Minor- und +Patch-Updates je Ökosystem in einem PR (Labels `dependencies` + `javascript` bzw. +`github-actions`, Commit-Präfix `chore(deps)`). Sicherheitsupdates kommen sofort. +Update-PRs durchlaufen dieselben Checks und werden wie jeder andere PR gemergt. + ## Ablauf 1. Fork bzw. Branch anlegen, Änderung mit Test. 2. Eintrag unter `[Unreleased]` in `CHANGELOG.md`. -3. Pull Request gegen `main` — die CI-Gates müssen grün sein. +3. Pull Request gegen `main` — alle Checks müssen grün sein. Commit-Präfixe: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`. diff --git a/README.md b/README.md index d20eb26..9e147d3 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ vollständig erfundener Beispiel-Report. ### Was das Dashboard zeigt | Bereich | Inhalt | -|---|---| +| --- | --- | | **KPI-Kacheln** | ESI (Employee Security Index) mit Vormonats-Delta, simulierte / erfolgreiche / gemeldete Angriffe, Meldequote, riskante Interaktionen (Klicks, Logins, geöffnete Dateien, Makros), E-Training-Fortschritt | | **Diagramme** | Angriffe und Meldungen je Mitarbeiter-Level, Mitarbeiter-Verteilung, E-Training-Status | | **Monats-Verlauf** | ESI, Melde- und Erfolgsquote über die Monate (ab dem zweiten geladenen Report) | @@ -64,7 +64,7 @@ Ohne Internetverbindung, ohne Server, ohne Administratorrechte. Der Statistik-Export des Security Awareness Service, z. B. `security_awareness_service_statistics.zip`: | Datei | Inhalt | -|---|---| +| --- | --- | | `company_statistics.csv` | Firmen-Gesamtwerte (ESI, Angriffe, Level 1–5, E-Trainings) | | `group_statistics.csv` | Dieselben Werte je Gruppe | | `user_statistics.csv` | Werte je Mitarbeiter | @@ -80,33 +80,42 @@ Format-Eigenheiten, die der Viewer berücksichtigt: Listenfelder im Python-Stil ## Entwicklung -``` +```text src/ ├── core.js ZIP-Leser, DEFLATE (puff-Port als Fallback), CSV-Parser, Datenmodell (in Node testbar) ├── ui.js Rendering: KPI, SVG-Diagramme, Tabellen, Tooltips, Verlauf, Drag & Drop └── template.html Gerüst und CSS (Farbsystem hell/dunkel, Druck-Styles) test/ ├── core.test.js Tests: Inflate-Fuzz gegen zlib, ZIP-/CSV-Grenzfälle, Datenmodell-Sollwerte +├── gates.test.js Tests der Doku-Gate-Regeln ├── make-sample.js Generator für den synthetischen Beispiel-Report (deterministisch) └── sample.zip Synthetischer Beispiel-Report (nur erfundene Daten) scripts/ -└── check-data.js Daten-Gate: nur erlaubte Pfade, keine echten Reportdaten +├── check-data.js Daten-Gate: nur erlaubte Pfade, keine echten Reportdaten +├── check-docs.js Doku-Gate: Doku passt zum Code (PR-Check) +├── lint-md.js markdownlint-Lauf über alle Markdown-Dateien +└── lint-ps.ps1 PSScriptAnalyzer-Lauf über alle PowerShell-Skripte build.ps1 setzt core.js + ui.js + Version ins Template ein → dist/Hornetsecurity-Report-Viewer.html ``` ```powershell +npm ci # Entwicklungswerkzeuge (Linter) installieren npm run gate:data # Daten-Gate +npm run gate:docs # Doku-Gate npm run check # Syntax-Prüfung +npm run lint # ESLint, markdownlint, PSScriptAnalyzer npm test # Tests (müssen „0 fehlgeschlagen" melden) .\build.ps1 # Build → dist/Hornetsecurity-Report-Viewer.html ``` -Voraussetzungen: Node.js ≥ 20 und PowerShell. Es gibt keine npm-Abhängigkeiten. +Voraussetzungen: Node.js ≥ 22.13 und PowerShell. npm-Pakete gibt es nur als Entwicklungswerkzeuge; +die ausgelieferte HTML-Datei hat keine Abhängigkeiten. Die Version steht ausschließlich in `package.json` und wird beim Build in die Fußzeile übernommen. ### Qualitäts-Gates -Jeder Push und jeder Pull Request durchläuft in der [CI](.github/workflows/ci.yml): +Jeder Pull Request muss in der [CI](.github/workflows/ci.yml) alle Checks bestehen, bevor er +nach `main` gemergt werden kann: 1. **Daten-Gate:** Die eingecheckte Beispiel-ZIP muss byte-gleich aus dem Generator stammen. Alle Dateien, auch die Inhalte der ZIP, dürfen nur E-Mail-Adressen erlaubter Domains @@ -116,6 +125,16 @@ Jeder Push und jeder Pull Request durchläuft in der [CI](.github/workflows/ci.y 3. **Test-Suite** (ZIP/DEFLATE-Fuzzing, CSV-Parser, Datenmodell, Robustheit). 4. **Build und Build-Prüfung:** Die HTML-Datei ist eigenständig (keine externen Skripte), und Version sowie Lizenzhinweis sind enthalten. +5. **Lint:** ESLint für JavaScript, markdownlint für die Dokumentation, PSScriptAnalyzer für + die PowerShell-Skripte. +6. **Doku-Gate:** Produktänderungen brauchen einen CHANGELOG-Eintrag, Änderungen an + Werkzeugen und Abläufen eine angepasste Entwickler-Doku. npm-Skripte, Links und + CHANGELOG-Struktur werden auf Konsistenz geprüft. +7. **Abhängigkeiten prüfen:** Neue oder geänderte Pakete und Actions dürfen keine bekannten + Sicherheitslücken und keine GPL/AGPL-Lizenz mitbringen. + +Dependabot hält npm-Pakete und Actions aktuell (wöchentlich, gebündelt, Sicherheitsupdates +sofort). Details zu allen Checks stehen in [CONTRIBUTING.md](CONTRIBUTING.md#pull-request-checks). Releases baut der [Release-Workflow](.github/workflows/release.yml) aus einem Tag `vX.Y.Z`. Er bricht ab, wenn Tag und `package.json` nicht übereinstimmen. diff --git a/SECURITY.md b/SECURITY.md index aac9d5b..05eba42 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,7 +9,7 @@ Sicherheitskorrekturen gibt es für die jeweils neueste Version (siehe [Releases Bitte **nicht** als öffentliches Issue melden, sondern vertraulich: - über [GitHub Security Advisories](https://github.com/Computerhof/Hornetsecurity-Report-Viewer/security/advisories/new) oder -- per E-Mail an **office@chrive-solutions.at** (Betreff „Security: Hornetsecurity Report Viewer"). +- per E-Mail an [office@chrive-solutions.at](mailto:office@chrive-solutions.at) (Betreff „Security: Hornetsecurity Report Viewer"). Wir bestätigen den Eingang in der Regel innerhalb von 5 Werktagen und stimmen den Veröffentlichungszeitpunkt einer Korrektur mit Ihnen ab. diff --git a/build.ps1 b/build.ps1 index 592207e..b8a94e9 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,4 +1,4 @@ -# Baut die portable Einzeldatei dist/Hornetsecurity-Report-Viewer.html aus src/ +# Baut die portable Einzeldatei dist/Hornetsecurity-Report-Viewer.html aus src/ # Die Version kommt ausschließlich aus package.json (Single Source of Truth). # Läuft unter Windows PowerShell 5.1 und PowerShell 7 (Windows/Linux/macOS). $ErrorActionPreference = 'Stop' @@ -24,4 +24,4 @@ New-Item -ItemType Directory -Force $dist | Out-Null $outPath = Join-Path $dist 'Hornetsecurity-Report-Viewer.html' [System.IO.File]::WriteAllText($outPath, $out, [System.Text.UTF8Encoding]::new($false)) -Write-Host "Gebaut: $outPath (v$version, $([math]::Round((Get-Item $outPath).Length / 1KB)) KB)" +Write-Output "Gebaut: $outPath (v$version, $([math]::Round((Get-Item $outPath).Length / 1KB)) KB)" diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..9ee8b8f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,48 @@ +/* ESLint-Konfiguration (Flat Config). `npm run lint:js` */ +'use strict'; + +const js = require('@eslint/js'); +const globals = require('globals'); + +module.exports = [ + { ignores: ['dist/**', 'local/**', 'node_modules/**', '*.html'] }, + js.configs.recommended, + { + /* Browser-Code: klassische Skripte, die build.ps1 in die HTML-Datei einsetzt */ + files: ['src/**/*.js'], + languageOptions: { + ecmaVersion: 2020, + sourceType: 'script', + globals: { ...globals.browser } + } + }, + { + /* core.js exportiert zusätzlich per module.exports für die Node-Tests */ + files: ['src/core.js'], + languageOptions: { globals: { module: 'readonly' } } + }, + { + /* ui.js liest PRVCore aus core.js (gleiche HTML-Seite) */ + files: ['src/ui.js'], + languageOptions: { globals: { PRVCore: 'readonly' } } + }, + { + /* Node-Skripte: Tests, Generator, Gates, diese Datei */ + files: ['test/**/*.js', 'scripts/**/*.js', 'eslint.config.js'], + languageOptions: { + ecmaVersion: 2024, + sourceType: 'commonjs', + globals: { ...globals.node } + } + }, + { + rules: { + eqeqeq: ['error', 'always', { null: 'ignore' }], + 'no-var': 'off', /* Browser-Code bewusst im ES5-Stil */ + 'no-unused-vars': ['error', { args: 'after-used', argsIgnorePattern: '^_', caughtErrors: 'none' }], + 'no-implied-eval': 'error', + 'no-new-func': 'error', + 'no-eval': 'error' + } + } +]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..afe89ec --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1861 @@ +{ + "name": "hornetsecurity-report-viewer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hornetsecurity-report-viewer", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@eslint/js": "^10.0.1", + "eslint": "^10.13.0", + "globals": "^17.13.0", + "markdownlint": "^0.42.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "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.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=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/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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": "10.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.13.0.tgz", + "integrity": "sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@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", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "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", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "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": "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/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/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/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.23" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz", + "integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "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/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/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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-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-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/katex": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.19.0.tgz", + "integrity": "sha512-v6Tznz3zJ7u3niRCoDTsumM2+HA2XXcCu+WAacCeHD2z3p9A9Ks987o5FzfTGBN0e8A0vjEgIDvLbICrXpdw/Q==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^15.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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/markdownlint": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.42.0.tgz", + "integrity": "sha512-kzsCV/rPWRFn3jUhcB919YF4Qgeh0g9tcogoXFadGSo6Zxj6662YOqWXv0jDn8nv5KCCxDfMKJZv6N4HkNkvTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "4.0.3", + "micromark-core-commonmark": "2.0.4", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.2", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.3", + "re2js": "2.8.6", + "string-width": "8.3.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/micromark": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.3.tgz", + "integrity": "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz", + "integrity": "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.1.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.3" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.2.tgz", + "integrity": "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz", + "integrity": "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-edit-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz", + "integrity": "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/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/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/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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/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/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/re2js": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/re2js/-/re2js-2.8.6.tgz", + "integrity": "sha512-xLgQil4kIUCrAzVk9fRSkxkFNwmygLFjVxXrLc65aE1F0+Zsb8rxumFBy4XKyvgMCTL6kilDq3EZ0piE2dP/Dg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "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/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/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/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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/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/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" + } + } + } +} diff --git a/package.json b/package.json index 16feeab..64f6684 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,27 @@ "url": "https://github.com/Computerhof/Hornetsecurity-Report-Viewer.git" }, "engines": { - "node": ">=20" + "node": ">=22.13" }, "scripts": { - "check": "node --check src/core.js && node --check src/ui.js && node --check test/make-sample.js && node --check scripts/check-data.js", - "test": "node test/core.test.js test/sample.zip", + "check": "node --check src/core.js && node --check src/ui.js && node --check test/core.test.js && node --check test/gates.test.js && node --check test/make-sample.js && node --check scripts/check-data.js && node --check scripts/check-docs.js && node --check scripts/lint-md.js", + "lint": "npm run lint:js && npm run lint:md && npm run lint:ps", + "lint:js": "eslint .", + "lint:md": "node scripts/lint-md.js", + "lint:ps": "pwsh -NoProfile -File scripts/lint-ps.ps1", + "test": "node test/core.test.js test/sample.zip && node test/gates.test.js", "sample": "node test/make-sample.js", "gate:data": "node test/make-sample.js --check && node scripts/check-data.js", + "gate:docs": "node scripts/check-docs.js", "build": "pwsh -NoProfile -File build.ps1" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "eslint": "^10.13.0", + "globals": "^17.13.0", + "markdownlint": "^0.42.0" + }, + "overrides": { + "katex": "^0.19.0" } } diff --git a/scripts/check-data.js b/scripts/check-data.js index 1df596d..852d735 100644 --- a/scripts/check-data.js +++ b/scripts/check-data.js @@ -20,9 +20,10 @@ const ALLOWED_PATHS = [ /^\.gitignore$/, /^\.gitattributes$/, /^\.github\/(workflows\/[\w.-]+\.yml|dependabot\.yml|ISSUE_TEMPLATE\/[\w.-]+\.(yml|md)|pull_request_template\.md)$/, /^src\/(core\.js|ui\.js|template\.html)$/, - /^test\/(core\.test\.js|make-sample\.js|sample\.zip)$/, - /^scripts\/check-data\.js$/, + /^test\/(core\.test\.js|gates\.test\.js|make-sample\.js|sample\.zip)$/, + /^scripts\/[\w-]+\.(js|ps1)$/, /^docs\/[\w.-]+\.(md|png|svg)$/, + /^(eslint\.config\.js|\.markdownlint\.json|package-lock\.json)$/, /^(build\.ps1|package\.json|README\.md|CHANGELOG\.md|LICENSE|SECURITY\.md|CONTRIBUTING\.md)$/ ]; const REPORT_FILE = /\.(zip|csv|xlsx?|json\.gz)$/i; diff --git a/scripts/check-docs.js b/scripts/check-docs.js new file mode 100644 index 0000000..9cccd22 --- /dev/null +++ b/scripts/check-docs.js @@ -0,0 +1,208 @@ +/* + * Doku-Gate: Dokumentation muss mit dem Code Schritt halten. + * Läuft als PR-Check in der CI und lokal (`npm run gate:docs`, Vergleich gegen origin/main). + * + * Immer geprüft (Konsistenz): + * - Jedes npm-Skript aus package.json ist in CONTRIBUTING.md oder README.md dokumentiert. + * - Relative Links in Markdown-Dateien zeigen auf existierende Dateien. + * - CHANGELOG.md hat einen Abschnitt [Unreleased], einen Abschnitt für die aktuelle Version + * und zu jeder Version eine Link-Referenz. + * + * Gegen den Basis-Stand des PR geprüft (Diff): + * - Produkt geändert (src/, build.ps1) → CHANGELOG.md geändert, Eintrag unter [Unreleased] + * oder im Abschnitt der neuen Version. + * - Entwicklungs-Werkzeuge geändert (CI, Gates, Linter-Konfiguration, npm-Skripte, Generator) + * → CONTRIBUTING.md, README.md oder CHANGELOG.md geändert. + * - Version in package.json geändert → CHANGELOG.md geändert. + * + * Ausnahmen: Dependabot-PRs, die nur Abhängigkeiten anfassen; PR-Label `keine-doku` + * (bewusste, sichtbare Entscheidung — die fehlende Doku wird trotzdem als Warnung gemeldet). + */ +'use strict'; + +const { execFileSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '..'); +const SKIP_LABEL = 'keine-doku'; +const DEPENDABOT = 'dependabot[bot]'; + +const PRODUCT = [/^src\//, /^build\.ps1$/]; +const TOOLING = [/^\.github\/workflows\//, /^scripts\//, /^eslint\.config\.js$/, /^\.markdownlint\.json$/, + /^test\/make-sample\.js$/, /^\.gitignore$/, /^\.github\/dependabot\.yml$/]; +const DEV_DOCS = ['CONTRIBUTING.md', 'README.md', 'CHANGELOG.md']; +const DEPENDENCY_FILES = [/^package\.json$/, /^package-lock\.json$/, /^\.github\/workflows\/[\w.-]+\.yml$/]; +/* package.json-Felder, deren Änderung nur Abhängigkeiten betrifft */ +const DEPENDENCY_FIELDS = new Set(['devDependencies', 'dependencies', 'overrides']); + +const matches = (file, patterns) => patterns.some(re => re.test(file)); + +/* ---------- CHANGELOG ---------- */ + +function changelogSections(text) { + const sections = new Map(); + let current = null; + for (const line of text.split(/\r?\n/)) { + const h = /^## \[([^\]]+)\](.*)$/.exec(line); + if (h) { current = h[1]; sections.set(current, { suffix: h[2].trim(), body: [] }); continue; } + if (/^\[[^\]]+\]:\s/.test(line)) { current = null; continue; } + if (current) sections.get(current).body.push(line); + } + return sections; +} +const hasEntries = (section) => !!section && section.body.some(l => /^\s*[-*]\s+\S/.test(l)); + +/* ---------- Regeln (rein, ohne Dateisystem — testbar) ---------- */ + +/** + * @param {object} ctx + * @param {object} ctx.pkg package.json (aktueller Stand) + * @param {object} [ctx.basePkg] package.json im Basis-Stand + * @param {object} ctx.docs { 'README.md': text, 'CONTRIBUTING.md': text, 'CHANGELOG.md': text, ... } + * @param {function} ctx.fileExists (repoRelativePath) => boolean + * @param {string[]} [ctx.changed] geänderte Dateien gegenüber Basis (undefined = kein Diff verfügbar) + * @param {string[]} [ctx.labels] PR-Labels + * @param {string} [ctx.author] PR-Autor + * @returns {{errors: string[], warnings: string[], notes: string[]}} + */ +function evaluate(ctx) { + const errors = [], warnings = [], notes = []; + const changelog = ctx.docs['CHANGELOG.md'] || ''; + const sections = changelogSections(changelog); + + /* 1. npm-Skripte dokumentiert */ + const devDocText = (ctx.docs['CONTRIBUTING.md'] || '') + '\n' + (ctx.docs['README.md'] || ''); + for (const name of Object.keys(ctx.pkg.scripts || {})) { + const documented = name === 'test' + ? /npm (run )?test\b/.test(devDocText) + : new RegExp('npm run ' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?![\\w:-])').test(devDocText); + if (!documented) errors.push(`package.json: npm-Skript "${name}" ist weder in CONTRIBUTING.md noch in README.md dokumentiert`); + } + + /* 2. Relative Links */ + for (const [file, text] of Object.entries(ctx.docs)) { + const dir = path.posix.dirname(file); + for (const m of text.matchAll(/\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) { + const target = m[1]; + if (/^(https?:|mailto:|#)/i.test(target)) continue; + const rel = path.posix.normalize(path.posix.join(dir, decodeURIComponent(target.split('#')[0]))); + if (!ctx.fileExists(rel)) errors.push(`${file}: Link auf nicht vorhandene Datei "${target}"`); + } + } + + /* 3. CHANGELOG-Struktur */ + if (!sections.has('Unreleased')) errors.push('CHANGELOG.md: Abschnitt "## [Unreleased]" fehlt'); + const version = ctx.pkg.version; + if (!sections.has(version)) { + errors.push(`CHANGELOG.md: kein Abschnitt "## [${version}] - JJJJ-MM-TT" für die Version aus package.json`); + } + for (const [name, s] of sections) { + if (name === 'Unreleased') continue; + if (!/^- \d{4}-\d{2}-\d{2}$/.test(s.suffix)) errors.push(`CHANGELOG.md: Abschnitt [${name}] ohne Datum im Format "- JJJJ-MM-TT"`); + if (!new RegExp('^\\[' + name.replace(/\./g, '\\.') + '\\]: https?://', 'm').test(changelog)) { + errors.push(`CHANGELOG.md: Link-Referenz "[${name}]: …" fehlt`); + } + } + + /* 4. Diff-Regeln */ + if (!ctx.changed) { + notes.push('Kein Basis-Stand angegeben — nur Konsistenzprüfung.'); + return { errors, warnings, notes }; + } + const changed = ctx.changed; + const touched = (f) => changed.includes(f); + + if (ctx.author === DEPENDABOT && changed.length > 0 && changed.every(f => matches(f, DEPENDENCY_FILES)) + && packageChangeIsDependenciesOnly(ctx.basePkg, ctx.pkg)) { + notes.push('Dependabot-PR, nur Abhängigkeiten geändert — keine Doku nötig.'); + return { errors, warnings, notes }; + } + + const required = []; + const productFiles = changed.filter(f => matches(f, PRODUCT)); + if (productFiles.length) { + const target = version !== (ctx.basePkg && ctx.basePkg.version) ? version : 'Unreleased'; + if (!touched('CHANGELOG.md') || !hasEntries(sections.get(target))) { + required.push(`Produkt geändert (${productFiles.join(', ')}) → Eintrag in CHANGELOG.md unter [${target}]`); + } + } + const toolingFiles = changed.filter(f => matches(f, TOOLING)) + .concat(packageToolingChanged(ctx.basePkg, ctx.pkg) ? ['package.json (Skripte/Engines)'] : []); + if (toolingFiles.length && !DEV_DOCS.some(touched)) { + required.push(`Entwicklungs-Werkzeuge geändert (${toolingFiles.join(', ')}) → ${DEV_DOCS.slice(0, -1).join(', ')} oder ${DEV_DOCS[DEV_DOCS.length - 1]} anpassen`); + } + if (ctx.basePkg && ctx.basePkg.version !== version && !touched('CHANGELOG.md')) { + required.push(`Version ${ctx.basePkg.version} → ${version} geändert → CHANGELOG.md anpassen`); + } + + if (required.length && (ctx.labels || []).includes(SKIP_LABEL)) { + warnings.push(...required.map(r => `Label "${SKIP_LABEL}" gesetzt, übersprungen: ${r}`)); + } else { + errors.push(...required); + } + return { errors, warnings, notes }; +} + +function strip(pkg, fields) { + const copy = { ...(pkg || {}) }; + for (const f of fields) delete copy[f]; + return JSON.stringify(copy); +} +function packageChangeIsDependenciesOnly(basePkg, pkg) { + if (!basePkg) return false; + return strip(basePkg, DEPENDENCY_FIELDS) === strip(pkg, DEPENDENCY_FIELDS); +} +function packageToolingChanged(basePkg, pkg) { + if (!basePkg) return false; + return ['scripts', 'engines'].some(f => JSON.stringify(basePkg[f] || null) !== JSON.stringify(pkg[f] || null)); +} + +/* ---------- Git/Dateisystem ---------- */ + +function git(args) { + return execFileSync('git', args, { cwd: ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); +} + +function run() { + const base = process.env.DOCS_GATE_BASE || (process.argv[2] || 'origin/main'); + const tracked = git(['ls-files', '-z', '--cached', '--others', '--exclude-standard']).split('\0').filter(Boolean); + const trackedSet = new Set(tracked); + const docs = {}; + for (const f of tracked) if (/\.md$/i.test(f) && fs.existsSync(path.join(ROOT, f))) docs[f] = fs.readFileSync(path.join(ROOT, f), 'utf8'); + + let changed, basePkg; + try { + const mergeBase = git(['merge-base', base, 'HEAD']).trim(); + changed = git(['diff', '--name-only', mergeBase]).split('\n').filter(Boolean) + .concat(git(['ls-files', '--others', '--exclude-standard']).split('\n').filter(Boolean)); + changed = [...new Set(changed)]; + try { basePkg = JSON.parse(git(['show', mergeBase + ':package.json'])); } catch (_e) { basePkg = undefined; } + } catch (_e) { + changed = undefined; + } + + const result = evaluate({ + pkg: JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')), + basePkg, + docs, + fileExists: (f) => trackedSet.has(f) || tracked.some(t => t.startsWith(f.replace(/\/?$/, '/'))), + changed, + labels: (process.env.PR_LABELS || '').split(',').map(s => s.trim()).filter(Boolean), + author: process.env.PR_AUTHOR || '' + }); + + const gha = !!process.env.GITHUB_ACTIONS; + for (const n of result.notes) console.log(n); + for (const w of result.warnings) console.log(gha ? `::warning title=Doku-Gate::${w}` : `WARNUNG: ${w}`); + if (result.errors.length) { + for (const e of result.errors) console.error(gha ? `::error title=Doku-Gate::${e}` : `FEHLER: ${e}`); + console.error(`\nDoku-Gate FEHLGESCHLAGEN (${result.errors.length}). Doku im selben PR nachziehen` + + ` oder — nur wenn wirklich keine Doku nötig ist — das Label "${SKIP_LABEL}" setzen.`); + process.exit(1); + } + console.log(`Doku-Gate bestanden${changed ? ` (${changed.length} geänderte Dateien gegenüber ${base})` : ''}.`); +} + +module.exports = { evaluate, changelogSections }; +if (require.main === module) run(); diff --git a/scripts/lint-md.js b/scripts/lint-md.js new file mode 100644 index 0000000..4f0e09b --- /dev/null +++ b/scripts/lint-md.js @@ -0,0 +1,40 @@ +/* + * Markdown-Linter: prüft alle Markdown-Dateien des Repositorys mit markdownlint + * (Regeln in .markdownlint.json). `npm run lint:md` + * + * Bewusst die Bibliothek statt markdownlint-cli2: Die CLI zieht für die Dateisuche + * Pakete mit offenen Sicherheitsmeldungen nach; die Dateiliste liefert hier git. + */ +'use strict'; + +const { execFileSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '..'); + +async function main() { + const { lint } = await import('markdownlint/promise'); + const files = execFileSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', '*.md'], + { cwd: ROOT, encoding: 'utf8' }).split('\0').filter(f => f && fs.existsSync(path.join(ROOT, f))); + const config = JSON.parse(fs.readFileSync(path.join(ROOT, '.markdownlint.json'), 'utf8')); + delete config.$schema; + + const results = await lint({ files: files.map(f => path.join(ROOT, f)), config }); + let count = 0; + for (const [file, issues] of Object.entries(results)) { + for (const i of issues) { + count++; + const rule = i.ruleNames.join('/'); + const detail = i.errorDetail ? ` [${i.errorDetail}]` : ''; + console.error(`${path.relative(ROOT, file)}:${i.lineNumber} ${rule} ${i.ruleDescription}${detail}`); + } + } + if (count) { + console.error(`\nMarkdown-Lint: ${count} Befund(e).`); + process.exit(1); + } + console.log(`Markdown-Lint bestanden: ${files.length} Dateien.`); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/scripts/lint-ps.ps1 b/scripts/lint-ps.ps1 new file mode 100644 index 0000000..0e4af13 --- /dev/null +++ b/scripts/lint-ps.ps1 @@ -0,0 +1,23 @@ +# PowerShell-Linter: prüft alle PowerShell-Skripte des Repositorys mit PSScriptAnalyzer. +# `npm run lint:ps` — benötigt das Modul PSScriptAnalyzer (Install-Module PSScriptAnalyzer -Scope CurrentUser). +$ErrorActionPreference = 'Stop' +$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) + +if (-not (Get-Module -ListAvailable -Name PSScriptAnalyzer)) { + throw 'Modul PSScriptAnalyzer fehlt: Install-Module PSScriptAnalyzer -Scope CurrentUser' +} +Import-Module PSScriptAnalyzer + +# Nur Dateien des Repositorys (verfolgt oder neu, nicht ignoriert) — wie die übrigen Gates +$files = @(git -C $root ls-files --cached --others --exclude-standard -- '*.ps1' '*.psm1' | + ForEach-Object { Join-Path $root $_ } | Where-Object { Test-Path $_ }) + +$findings = @($files | ForEach-Object { Invoke-ScriptAnalyzer -Path $_ -Severity Warning, Error }) +foreach ($f in $findings) { + Write-Output ('{0}:{1} {2} {3}' -f (Resolve-Path -Relative $f.ScriptPath), $f.Line, $f.RuleName, $f.Message) +} +if ($findings.Count -gt 0) { + Write-Output "PowerShell-Lint: $($findings.Count) Befund(e)." + exit 1 +} +Write-Output "PowerShell-Lint bestanden: $($files.Count) Datei(en)." diff --git a/src/core.js b/src/core.js index d863006..d32322f 100644 --- a/src/core.js +++ b/src/core.js @@ -412,7 +412,7 @@ var PRVCore = (function () { var full = String(f.name); var base = full.split('/').pop().split('\\').pop(); /* macOS-Archiv-Metadaten aussortieren (__MACOSX/, AppleDouble ._*, .DS_Store) */ - if (/(^|[\/\\])__MACOSX([\/\\]|$)/.test(full) || /^\._/.test(base) || /^\.DS_Store$/i.test(base)) return; + if (/(^|[/\\])__MACOSX([/\\]|$)/.test(full) || /^\._/.test(base) || /^\.DS_Store$/i.test(base)) return; var match = null; for (var i = 0; i < FILE_KEYS.length; i++) { if (FILE_KEYS[i].pattern.test(base)) { match = FILE_KEYS[i].key; break; } diff --git a/test/core.test.js b/test/core.test.js index 0585396..e13ae43 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -2,7 +2,6 @@ 'use strict'; const fs = require('fs'); -const path = require('path'); const zlib = require('zlib'); const core = require('../src/core.js'); diff --git a/test/gates.test.js b/test/gates.test.js new file mode 100644 index 0000000..159954e --- /dev/null +++ b/test/gates.test.js @@ -0,0 +1,114 @@ +/* Tests für die Regeln des Doku-Gates (scripts/check-docs.js) — `node test/gates.test.js` */ +'use strict'; + +const { evaluate, changelogSections } = require('../scripts/check-docs.js'); + +let passed = 0, failed = 0; +function check(cond, name, detail) { + if (cond) { passed++; return; } + failed++; + console.error(' FAIL: ' + name + (detail ? ' — ' + JSON.stringify(detail) : '')); +} + +const CHANGELOG = [ + '# Changelog', '', '## [Unreleased]', '', '## [1.0.0] - 2026-10-23', '', '### Hinzugefügt', '', '- Erste Version.', '', + '[Unreleased]: https://example.com/compare/v1.0.0...HEAD', '[1.0.0]: https://example.com/releases/tag/v1.0.0', '' +].join('\n'); +const CONTRIBUTING = 'npm run lint\nnpm test\n'; +const PKG = { name: 'x', version: '1.0.0', scripts: { lint: 'eslint .', test: 'node t.js' }, devDependencies: { eslint: '^10.0.0' } }; + +function ctx(over) { + return Object.assign({ + pkg: PKG, + basePkg: PKG, + docs: { 'CHANGELOG.md': CHANGELOG, 'CONTRIBUTING.md': CONTRIBUTING, 'README.md': '[Lizenz](LICENSE)' }, + fileExists: (f) => ['LICENSE', 'CHANGELOG.md', 'README.md', 'CONTRIBUTING.md'].includes(f), + changed: [], + labels: [], + author: 'someone' + }, over); +} +const withUnreleased = (entry) => CHANGELOG.replace('## [Unreleased]\n', '## [Unreleased]\n\n### Behoben\n\n- ' + entry + '\n'); + +console.log('Doku-Gate…'); + +/* Konsistenz */ +let r = evaluate(ctx({ changed: undefined })); +check(r.errors.length === 0, 'konsistenter Stand ohne Diff: keine Fehler', r.errors); + +r = evaluate(ctx({ pkg: { ...PKG, scripts: { ...PKG.scripts, 'gate:data': 'x' } } })); +check(r.errors.some(e => e.includes('"gate:data"')), 'undokumentiertes npm-Skript erkannt', r.errors); + +r = evaluate(ctx({ pkg: { ...PKG, scripts: { lint: 'x' } }, docs: { 'CHANGELOG.md': CHANGELOG, 'CONTRIBUTING.md': 'npm run lint:js' } })); +check(r.errors.some(e => e.includes('"lint"')), '"npm run lint:js" dokumentiert nicht "lint"', r.errors); + +r = evaluate(ctx({ docs: { 'CHANGELOG.md': CHANGELOG, 'CONTRIBUTING.md': CONTRIBUTING, 'README.md': '[x](docs/fehlt.png) [y](https://a.b) [z](#anker) [m](mailto:a@example.com)' } })); +check(r.errors.length === 1 && r.errors[0].includes('docs/fehlt.png'), 'kaputter relativer Link erkannt, externe ignoriert', r.errors); + +r = evaluate(ctx({ docs: { 'CHANGELOG.md': CHANGELOG, 'CONTRIBUTING.md': CONTRIBUTING, 'docs/a.md': '[b](../LICENSE)' } })); +check(r.errors.length === 0, 'Link relativ zum Unterordner aufgelöst', r.errors); + +r = evaluate(ctx({ pkg: { ...PKG, version: '1.1.0' } })); +check(r.errors.some(e => e.includes('[1.1.0]')), 'Version ohne CHANGELOG-Abschnitt erkannt', r.errors); + +r = evaluate(ctx({ docs: { 'CHANGELOG.md': CHANGELOG.replace('[1.0.0]: https://example.com/releases/tag/v1.0.0\n', ''), 'CONTRIBUTING.md': CONTRIBUTING } })); +check(r.errors.some(e => e.includes('Link-Referenz "[1.0.0]')), 'fehlende Link-Referenz erkannt', r.errors); + +r = evaluate(ctx({ docs: { 'CHANGELOG.md': CHANGELOG.replace('## [Unreleased]\n', ''), 'CONTRIBUTING.md': CONTRIBUTING } })); +check(r.errors.some(e => e.includes('[Unreleased]')), 'fehlender Unreleased-Abschnitt erkannt', r.errors); + +/* Diff: Produkt */ +r = evaluate(ctx({ changed: ['src/ui.js'] })); +check(r.errors.some(e => e.includes('src/ui.js') && e.includes('[Unreleased]')), 'Produktänderung ohne CHANGELOG blockiert', r.errors); + +r = evaluate(ctx({ changed: ['src/ui.js', 'CHANGELOG.md'] })); +check(r.errors.length === 1, 'CHANGELOG angefasst, aber Unreleased leer → blockiert', r.errors); + +r = evaluate(ctx({ changed: ['src/ui.js', 'CHANGELOG.md'], docs: { 'CHANGELOG.md': withUnreleased('Fehler behoben.'), 'CONTRIBUTING.md': CONTRIBUTING } })); +check(r.errors.length === 0, 'Produktänderung mit Unreleased-Eintrag ok', r.errors); + +const cl110 = CHANGELOG.replace('## [1.0.0]', '## [1.1.0] - 2026-11-01\n\n### Hinzugefügt\n\n- Neu.\n\n## [1.0.0]') + .replace('[1.0.0]: https', '[1.1.0]: https://example.com/releases/tag/v1.1.0\n[1.0.0]: https'); +r = evaluate(ctx({ pkg: { ...PKG, version: '1.1.0' }, changed: ['src/ui.js', 'package.json', 'CHANGELOG.md'], docs: { 'CHANGELOG.md': cl110, 'CONTRIBUTING.md': CONTRIBUTING } })); +check(r.errors.length === 0, 'Release-PR: Eintrag im Abschnitt der neuen Version ok', r.errors); + +r = evaluate(ctx({ pkg: { ...PKG, version: '1.1.0' }, changed: ['package.json'], docs: { 'CHANGELOG.md': cl110, 'CONTRIBUTING.md': CONTRIBUTING } })); +check(r.errors.some(e => e.includes('1.0.0 → 1.1.0')), 'Versionssprung ohne CHANGELOG-Änderung blockiert', r.errors); + +/* Diff: Werkzeuge */ +r = evaluate(ctx({ changed: ['.github/workflows/ci.yml'] })); +check(r.errors.some(e => e.includes('Entwicklungs-Werkzeuge')), 'Workflow-Änderung ohne Doku blockiert', r.errors); + +r = evaluate(ctx({ changed: ['.github/workflows/ci.yml', 'CONTRIBUTING.md'] })); +check(r.errors.length === 0, 'Workflow-Änderung mit CONTRIBUTING ok', r.errors); + +r = evaluate(ctx({ pkg: { ...PKG, scripts: { ...PKG.scripts, test: 'node t2.js' } }, changed: ['package.json'] })); +check(r.errors.some(e => e.includes('package.json (Skripte/Engines)')), 'geändertes npm-Skript ohne Doku blockiert', r.errors); + +r = evaluate(ctx({ changed: ['test/core.test.js'] })); +check(r.errors.length === 0, 'reine Teständerung braucht keine Doku', r.errors); + +/* Ausnahmen */ +r = evaluate(ctx({ changed: ['src/ui.js'], labels: ['keine-doku'] })); +check(r.errors.length === 0 && r.warnings.length === 1, 'Label keine-doku: Warnung statt Fehler', r); + +const bumped = { ...PKG, devDependencies: { eslint: '^10.1.0' } }; +r = evaluate(ctx({ pkg: bumped, author: 'dependabot[bot]', changed: ['package.json', 'package-lock.json'] })); +check(r.errors.length === 0 && r.notes.some(n => n.includes('Dependabot')), 'Dependabot-Abhängigkeitsupdate ausgenommen', r); + +r = evaluate(ctx({ author: 'dependabot[bot]', changed: ['.github/workflows/ci.yml'] })); +check(r.errors.length === 0, 'Dependabot-Actions-Update ausgenommen', r.errors); + +r = evaluate(ctx({ pkg: { ...bumped, scripts: { lint: 'x', test: 'y' } }, author: 'dependabot[bot]', changed: ['package.json'] })); +check(r.errors.length > 0, 'Dependabot mit Skript-Änderung NICHT ausgenommen', r.errors); + +r = evaluate(ctx({ pkg: bumped, author: 'someone', changed: ['package.json', 'package-lock.json'] })); +check(r.errors.length === 0, 'manuelles Abhängigkeitsupdate braucht keine Doku', r.errors); + +/* Parser */ +const s = changelogSections(withUnreleased('X')); +check(s.get('Unreleased').body.some(l => l === '- X'), 'changelogSections: Unreleased-Eintrag gelesen'); +check(!s.get('1.0.0').body.some(l => l.startsWith('[')), 'changelogSections: Link-Referenzen nicht im Abschnitt'); + +console.log(`\nErgebnis: ${passed} bestanden, ${failed} fehlgeschlagen.`); +process.exit(failed > 0 ? 1 : 0);