From b29e08e954d256eccff39699ee98ab32e7a48341 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sun, 22 Feb 2026 00:29:49 +0100 Subject: [PATCH] feat(ci): add deep quality assertions (html, security, links, spelling) --- .gitea/workflows/deploy.yml | 60 ++- .htmlvalidate.json | 13 + cspell.json | 97 +++++ package.json | 7 + pnpm-lock.yaml | 793 ++++++++++++++++++++++++++++++++++++ scripts/check-html.ts | 82 ++++ scripts/check-links.sh | 26 ++ scripts/check-security.ts | 54 +++ 8 files changed, 1130 insertions(+), 2 deletions(-) create mode 100644 .htmlvalidate.json create mode 100644 cspell.json create mode 100644 scripts/check-html.ts create mode 100644 scripts/check-links.sh create mode 100644 scripts/check-security.ts diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 53c8a2cb..1248b98c 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -187,6 +187,7 @@ jobs: if: github.event.inputs.skip_checks != 'true' run: | pnpm lint + pnpm check:spell pnpm typecheck pnpm test @@ -709,11 +710,66 @@ jobs: path: backstop_data/html_report/ # ────────────────────────────────────────────────────────────────────────────── - # JOB 9: Notifications + # JOB 9: Quality Assertions + # ────────────────────────────────────────────────────────────────────────────── + quality_assertions: + name: 🛡️ Quality Gates + needs: [prepare, deploy, smoke_test] + if: success() && needs.prepare.outputs.target != 'skip' + runs-on: docker + container: + image: catthehacker/ubuntu:act-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 10 + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: 🔐 Registry Auth + run: | + echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc + echo "//${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}/:_authToken=${{ secrets.REGISTRY_PASS }}" >> .npmrc + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: 🌐 HTML DOM Validation + env: + NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }} + GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} + run: pnpm check:html + - name: 🔒 Security Headers Scan + env: + NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }} + GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} + run: pnpm check:security + - name: 🔗 Lychee Deep Link Crawl + env: + NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }} + GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} + run: pnpm check:links + + # ────────────────────────────────────────────────────────────────────────────── + # JOB 10: Notifications # ────────────────────────────────────────────────────────────────────────────── notifications: name: 🔔 Notify - needs: [prepare, deploy, smoke_test, lighthouse, wcag, visual_regression] + needs: [prepare, deploy, smoke_test, lighthouse, wcag, visual_regression, quality_assertions] if: always() runs-on: docker container: diff --git a/.htmlvalidate.json b/.htmlvalidate.json new file mode 100644 index 00000000..25e0a805 --- /dev/null +++ b/.htmlvalidate.json @@ -0,0 +1,13 @@ +{ + "extends": ["html-validate:recommended", "html-validate:document"], + "rules": { + "require-sri": "off", + "meta-refresh": "off", + "heading-level": "warn", + "no-trailing-whitespace": "off", + "wcag/h37": "warn", + "no-inline-style": "off", + "svg-focusable": "off", + "attribute-boolean-style": "off" + } +} diff --git a/cspell.json b/cspell.json new file mode 100644 index 00000000..95214436 --- /dev/null +++ b/cspell.json @@ -0,0 +1,97 @@ +{ + "version": "0.2", + "language": "en,de", + "dictionaries": ["de-de", "html", "css", "typescript", "npm"], + "words": [ + "Datasheet", + "datasheets", + "Bodemer", + "Mintel", + "Umami", + "Energiezukunft", + "Energiewende", + "Solarparks", + "Energiekabel", + "Kabelinfrastruktur", + "Großprojekte", + "Zertifizierte", + "Erstberatung", + "Vertriebs", + "Windparkbau", + "Kabelherausforderungen", + "Energieprojekt", + "mittelspannungskabel", + "niederspannungskabel", + "hochspannungskabel", + "solarkabel", + "extralight", + "medv", + "Crect", + "Csvg", + "mintel", + "Zurück", + "Übersicht", + "Raiffeisenstraße", + "Remshalden", + "Experte", + "hochwertige", + "Stromkabel", + "Mittelspannungslösungen", + "Zuverlässige", + "Infrastruktur", + "eine", + "grüne", + "Weiterer", + "Artikel", + "Vorheriger", + "Beitrag", + "Nächster", + "Lösungen", + "Bereit", + "Planung", + "Lieferung", + "hochwertiger", + "erwecken", + "Ihre", + "Projekte", + "Leben", + "Strategischer", + "schnelle", + "Nachhaltige", + "Expertenberatung", + "Qualität", + "nach", + "Projekt", + "anfragen", + "Kostenlose", + "Vorhaben", + "kopiert", + "Teilen", + "Inhalt", + "produkte", + "Fokus", + "drei", + "typische", + "fokus", + "Warum", + "ideale", + "Kabel", + "Deutsch", + "Spannung", + "unbekannt" + ], + "ignorePaths": [ + "node_modules", + ".next", + "public", + "pnpm-lock.yaml", + "*.svg", + "*.mp4", + "directus", + "backstop_data", + ".gitea", + "out", + "coverage", + "*.json" + ] +} diff --git a/package.json b/package.json index ab975fc7..4d397cfc 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "devDependencies": { "@commitlint/cli": "^20.4.0", "@commitlint/config-conventional": "^20.4.0", + "@cspell/dict-de-de": "^4.1.2", "@lhci/cli": "^0.15.1", "@mintel/eslint-config": "1.8.3", "@mintel/tsconfig": "1.8.3", @@ -69,8 +70,10 @@ "backstopjs": "^6.3.25", "cheerio": "^1.2.0", "critters": "^0.0.25", + "cspell": "^9.6.4", "eslint": "^9.18.0", "happy-dom": "^20.6.1", + "html-validate": "^10.8.0", "husky": "^9.1.7", "lint-staged": "^16.2.7", "lucide-react": "^0.563.0", @@ -100,6 +103,10 @@ "check:mdx": "node scripts/validate-mdx.mjs", "check:a11y": "pa11y-ci", "check:wcag": "tsx ./scripts/wcag-sitemap.ts", + "check:html": "tsx ./scripts/check-html.ts", + "check:spell": "cspell \"content/**/*.{md,mdx}\" \"app/**/*.tsx\" \"components/**/*.tsx\"", + "check:security": "tsx ./scripts/check-security.ts", + "check:links": "bash ./scripts/check-links.sh", "backstop:reference": "backstop reference --config=backstop.config.js --docker", "backstop:test": "backstop test --config=backstop.config.js --docker", "backstop:approve": "backstop approve --config=backstop.config.js --docker", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a74b2e6a..3989e825 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,6 +135,9 @@ importers: '@commitlint/config-conventional': specifier: ^20.4.0 version: 20.4.1 + '@cspell/dict-de-de': + specifier: ^4.1.2 + version: 4.1.2 '@lhci/cli': specifier: ^0.15.1 version: 0.15.1 @@ -204,12 +207,18 @@ importers: critters: specifier: ^0.0.25 version: 0.0.25 + cspell: + specifier: ^9.6.4 + version: 9.6.4 eslint: specifier: ^9.18.0 version: 9.39.2(jiti@2.6.1) happy-dom: specifier: ^20.6.1 version: 20.6.1 + html-validate: + specifier: ^10.8.0 + version: 10.8.0(vitest@4.0.18) husky: specifier: ^9.1.7 version: 9.1.7 @@ -438,6 +447,243 @@ packages: resolution: {integrity: sha512-aO5l99BQJ0X34ft8b0h7QFkQlqxC6e7ZPVmBKz13xM9O8obDaM1Cld4sQlJDXXU/VFuUzQ30mVtHjVz74TuStw==} engines: {node: '>=v18'} + '@cspell/cspell-bundled-dicts@9.6.4': + resolution: {integrity: sha512-OIiPQuB7XQ6rnUv4KaCwHr9vNwbh6VZ4GfgQjcThT0oz0hkL6E5Ar3tq54K9jyqE9ylcHqpRuXUgnKgio6Hlig==} + engines: {node: '>=20'} + + '@cspell/cspell-json-reporter@9.6.4': + resolution: {integrity: sha512-rGYSDnDWACrUyovfN8M/LM8CCFSKjYd2kehbNS7YMPk0Jk+rLk6sgt5WYu3ty45otXCkiO07bjUo/81wBLet7A==} + engines: {node: '>=20'} + + '@cspell/cspell-performance-monitor@9.6.4': + resolution: {integrity: sha512-exuqxV1IVfZkasg57ZjUbaHeZDd6Mdbsbe5FBT3+XaVnRij+wpY2oEW9+kIOL5MOQE3bgQKgu37iMtA1NlCrGA==} + engines: {node: '>=20.18'} + + '@cspell/cspell-pipe@9.6.4': + resolution: {integrity: sha512-vVxajTG9Ko01oHk8HPsMLajcLrd9AfkOk6vdgFI4FD7ZPq1CY0hfTmfmJ8bzZ4/QkqXglTvePdSgHQVJeltwWw==} + engines: {node: '>=20'} + + '@cspell/cspell-resolver@9.6.4': + resolution: {integrity: sha512-3xsgZEqqH9Uj8ZYLBnWbnsHz8wphgaeuWKcNDqgwoMjvwTMQLGoXjHht8Jx5yxd2e080lB7fJax8TaBdCzmFFA==} + engines: {node: '>=20'} + + '@cspell/cspell-service-bus@9.6.4': + resolution: {integrity: sha512-oGNEzP1gJ43rLklJQjOk5PsfX0mZkLjV19djGptb9xZQeC2qAUxnaAbZtWt5CE8ni2iiTaRmgNRbUqAhRCnjew==} + engines: {node: '>=20'} + + '@cspell/cspell-types@9.6.4': + resolution: {integrity: sha512-lf6d+BdMkJIFCxx2FpajLpqVGGyaGUNFU6jhEM6QUPeGuoA5et2kJXrL0NSY2uWLOVyYYc/FPjzlbe8trA9tBQ==} + engines: {node: '>=20'} + + '@cspell/cspell-worker@9.6.4': + resolution: {integrity: sha512-anacKDOZzDfPzuDeFOXGI2tFBYiRRCSnIZP/AOyJ9zTvEQcqq5p/ak18nJ5OQyDr2NG7ovJiCDT5YNiH2Vdg/g==} + engines: {node: '>=20.18'} + + '@cspell/dict-ada@4.1.1': + resolution: {integrity: sha512-E+0YW9RhZod/9Qy2gxfNZiHJjCYFlCdI69br1eviQQWB8yOTJX0JHXLs79kOYhSW0kINPVUdvddEBe6Lu6CjGQ==} + + '@cspell/dict-al@1.1.1': + resolution: {integrity: sha512-sD8GCaZetgQL4+MaJLXqbzWcRjfKVp8x+px3HuCaaiATAAtvjwUQ5/Iubiqwfd1boIh2Y1/3EgM3TLQ7Q8e0wQ==} + + '@cspell/dict-aws@4.0.17': + resolution: {integrity: sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==} + + '@cspell/dict-bash@4.2.2': + resolution: {integrity: sha512-kyWbwtX3TsCf5l49gGQIZkRLaB/P8g73GDRm41Zu8Mv51kjl2H7Au0TsEvHv7jzcsRLS6aUYaZv6Zsvk1fOz+Q==} + + '@cspell/dict-companies@3.2.10': + resolution: {integrity: sha512-bJ1qnO1DkTn7JYGXvxp8FRQc4yq6tRXnrII+jbP8hHmq5TX5o1Wu+rdfpoUQaMWTl6balRvcMYiINDesnpR9Bw==} + + '@cspell/dict-cpp@7.0.2': + resolution: {integrity: sha512-dfbeERiVNeqmo/npivdR6rDiBCqZi3QtjH2Z0HFcXwpdj6i97dX1xaKyK2GUsO/p4u1TOv63Dmj5Vm48haDpuA==} + + '@cspell/dict-cryptocurrencies@5.0.5': + resolution: {integrity: sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==} + + '@cspell/dict-csharp@4.0.8': + resolution: {integrity: sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==} + + '@cspell/dict-css@4.0.19': + resolution: {integrity: sha512-VYHtPnZt/Zd/ATbW3rtexWpBnHUohUrQOHff/2JBhsVgxOrksAxJnLAO43Q1ayLJBJUUwNVo+RU0sx0aaysZfg==} + + '@cspell/dict-dart@2.3.2': + resolution: {integrity: sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==} + + '@cspell/dict-data-science@2.0.13': + resolution: {integrity: sha512-l1HMEhBJkPmw4I2YGVu2eBSKM89K9pVF+N6qIr5Uo5H3O979jVodtuwP8I7LyPrJnC6nz28oxeGRCLh9xC5CVA==} + + '@cspell/dict-de-de@4.1.2': + resolution: {integrity: sha512-pRb5bIQc2pJU6bVEsSfhMkB+XXJauaXg3KS+KjO38HBHAe2vg611keKYSkry90Rbn2KVMOLAVjN0hN/qiy5X0A==} + + '@cspell/dict-django@4.1.6': + resolution: {integrity: sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==} + + '@cspell/dict-docker@1.1.17': + resolution: {integrity: sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==} + + '@cspell/dict-dotnet@5.0.12': + resolution: {integrity: sha512-FiV934kNieIjGTkiApu/WKvLYi/KBpvfWB2TSqpDQtmXZlt3uSa5blwblO1ZC8OvjH8RCq/31H5IdEYmTaZS7A==} + + '@cspell/dict-elixir@4.0.8': + resolution: {integrity: sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==} + + '@cspell/dict-en-common-misspellings@2.1.12': + resolution: {integrity: sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==} + + '@cspell/dict-en-gb-mit@3.1.18': + resolution: {integrity: sha512-AXaMzbaxhSc32MSzKX0cpwT+Thv1vPfxQz1nTly1VHw3wQcwPqVFSqrLOYwa8VNqAPR45583nnhD6iqJ9YESoQ==} + + '@cspell/dict-en_us@4.4.29': + resolution: {integrity: sha512-G3B27++9ziRdgbrY/G/QZdFAnMzzx17u8nCb2Xyd4q6luLpzViRM/CW3jA+Mb/cGT5zR/9N+Yz9SrGu1s0bq7g==} + + '@cspell/dict-filetypes@3.0.15': + resolution: {integrity: sha512-uDMeqYlLlK476w/muEFQGBy9BdQWS0mQ7BJiy/iQv5XUWZxE2O54ZQd9nW8GyQMzAgoyg5SG4hf9l039Qt66oA==} + + '@cspell/dict-flutter@1.1.1': + resolution: {integrity: sha512-UlOzRcH2tNbFhZmHJN48Za/2/MEdRHl2BMkCWZBYs+30b91mWvBfzaN4IJQU7dUZtowKayVIF9FzvLZtZokc5A==} + + '@cspell/dict-fonts@4.0.5': + resolution: {integrity: sha512-BbpkX10DUX/xzHs6lb7yzDf/LPjwYIBJHJlUXSBXDtK/1HaeS+Wqol4Mlm2+NAgZ7ikIE5DQMViTgBUY3ezNoQ==} + + '@cspell/dict-fsharp@1.1.1': + resolution: {integrity: sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==} + + '@cspell/dict-fullstack@3.2.8': + resolution: {integrity: sha512-J6EeoeThvx/DFrcA2rJiCA6vfqwJMbkG0IcXhlsmRZmasIpanmxgt90OEaUazbZahFiuJT8wrhgQ1QgD1MsqBw==} + + '@cspell/dict-gaming-terms@1.1.2': + resolution: {integrity: sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==} + + '@cspell/dict-git@3.1.0': + resolution: {integrity: sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==} + + '@cspell/dict-golang@6.0.26': + resolution: {integrity: sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==} + + '@cspell/dict-google@1.0.9': + resolution: {integrity: sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==} + + '@cspell/dict-haskell@4.0.6': + resolution: {integrity: sha512-ib8SA5qgftExpYNjWhpYIgvDsZ/0wvKKxSP+kuSkkak520iPvTJumEpIE+qPcmJQo4NzdKMN8nEfaeci4OcFAQ==} + + '@cspell/dict-html-symbol-entities@4.0.5': + resolution: {integrity: sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==} + + '@cspell/dict-html@4.0.14': + resolution: {integrity: sha512-2bf7n+kS92g+cMKV0wr9o/Oq9n8JzU7CcrB96gIh2GHgnF+0xDOqO2W/1KeFAqOfqosoOVE48t+4dnEMkkoJ2Q==} + + '@cspell/dict-java@5.0.12': + resolution: {integrity: sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==} + + '@cspell/dict-julia@1.1.1': + resolution: {integrity: sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==} + + '@cspell/dict-k8s@1.0.12': + resolution: {integrity: sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==} + + '@cspell/dict-kotlin@1.1.1': + resolution: {integrity: sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==} + + '@cspell/dict-latex@5.0.0': + resolution: {integrity: sha512-HUrIqUVohM6P0+5b7BsdAdb0STIv0aaFBvguI7pLcreljlcX3FSPUxea7ticzNlCNeVrEaiEn/ws9m6rYUeuNw==} + + '@cspell/dict-lorem-ipsum@4.0.5': + resolution: {integrity: sha512-9a4TJYRcPWPBKkQAJ/whCu4uCAEgv/O2xAaZEI0n4y1/l18Yyx8pBKoIX5QuVXjjmKEkK7hi5SxyIsH7pFEK9Q==} + + '@cspell/dict-lua@4.0.8': + resolution: {integrity: sha512-N4PkgNDMu9JVsRu7JBS/3E/dvfItRgk9w5ga2dKq+JupP2Y3lojNaAVFhXISh4Y0a6qXDn2clA6nvnavQ/jjLA==} + + '@cspell/dict-makefile@1.0.5': + resolution: {integrity: sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==} + + '@cspell/dict-markdown@2.0.14': + resolution: {integrity: sha512-uLKPNJsUcumMQTsZZgAK9RgDLyQhUz/uvbQTEkvF/Q4XfC1i/BnA8XrOrd0+Vp6+tPOKyA+omI5LRWfMu5K/Lw==} + peerDependencies: + '@cspell/dict-css': ^4.0.19 + '@cspell/dict-html': ^4.0.14 + '@cspell/dict-html-symbol-entities': ^4.0.5 + '@cspell/dict-typescript': ^3.2.3 + + '@cspell/dict-monkeyc@1.0.12': + resolution: {integrity: sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==} + + '@cspell/dict-node@5.0.9': + resolution: {integrity: sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==} + + '@cspell/dict-npm@5.2.34': + resolution: {integrity: sha512-M2MtfmYeHIPBuC8esMU4JQXHKma7Xt7VyBWUk67B62KDu61sxebQ2HeizdqmN2sLEJsTkq3bZT5PGzHpZ0LEWQ==} + + '@cspell/dict-php@4.1.1': + resolution: {integrity: sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==} + + '@cspell/dict-powershell@5.0.15': + resolution: {integrity: sha512-l4S5PAcvCFcVDMJShrYD0X6Huv9dcsQPlsVsBGbH38wvuN7gS7+GxZFAjTNxDmTY1wrNi1cCatSg6Pu2BW4rgg==} + + '@cspell/dict-public-licenses@2.0.15': + resolution: {integrity: sha512-cJEOs901H13Pfy0fl4dCD1U+xpWIMaEPq8MeYU83FfDZvellAuSo4GqWCripfIqlhns/L6+UZEIJSOZnjgy7Wg==} + + '@cspell/dict-python@4.2.25': + resolution: {integrity: sha512-hDdN0YhKgpbtZVRjQ2c8jk+n0wQdidAKj1Fk8w7KEHb3YlY5uPJ0mAKJk7AJKPNLOlILoUmN+HAVJz+cfSbWYg==} + + '@cspell/dict-r@2.1.1': + resolution: {integrity: sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==} + + '@cspell/dict-ruby@5.1.0': + resolution: {integrity: sha512-9PJQB3cfkBULrMLp5kSAcFPpzf8oz9vFN+QYZABhQwWkGbuzCIXSorHrmWSASlx4yejt3brjaWS57zZ/YL5ZQQ==} + + '@cspell/dict-rust@4.1.2': + resolution: {integrity: sha512-O1FHrumYcO+HZti3dHfBPUdnDFkI+nbYK3pxYmiM1sr+G0ebOd6qchmswS0Wsc6ZdEVNiPYJY/gZQR6jfW3uOg==} + + '@cspell/dict-scala@5.0.9': + resolution: {integrity: sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==} + + '@cspell/dict-shell@1.1.2': + resolution: {integrity: sha512-WqOUvnwcHK1X61wAfwyXq04cn7KYyskg90j4lLg3sGGKMW9Sq13hs91pqrjC44Q+lQLgCobrTkMDw9Wyl9nRFA==} + + '@cspell/dict-software-terms@5.1.21': + resolution: {integrity: sha512-3lAB4OXsf6rs5zbwe4/nKmwyAJAvjs5KTRrPckzHx7q9dYpviW+UxDyhevCCsRfmcu24OhYP7BVQWXxLvYk4xA==} + + '@cspell/dict-sql@2.2.1': + resolution: {integrity: sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==} + + '@cspell/dict-svelte@1.0.7': + resolution: {integrity: sha512-hGZsGqP0WdzKkdpeVLBivRuSNzOTvN036EBmpOwxH+FTY2DuUH7ecW+cSaMwOgmq5JFSdTcbTNFlNC8HN8lhaQ==} + + '@cspell/dict-swift@2.0.6': + resolution: {integrity: sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==} + + '@cspell/dict-terraform@1.1.3': + resolution: {integrity: sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==} + + '@cspell/dict-typescript@3.2.3': + resolution: {integrity: sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==} + + '@cspell/dict-vue@3.0.5': + resolution: {integrity: sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==} + + '@cspell/dict-zig@1.0.0': + resolution: {integrity: sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==} + + '@cspell/dynamic-import@9.6.4': + resolution: {integrity: sha512-1VnL9ahT3s17DLWl4MeO1pYg7zcVT3X9cKynI2/U86zNK5xMGS5icvjp7X65tsCAVNcWOtkqVFfrxi7kWxn67g==} + engines: {node: '>=20'} + + '@cspell/filetypes@9.6.4': + resolution: {integrity: sha512-a1aZ/8vGnhTknxTukjzo3m8CISyHW2MWnbedywg5SDEl5RMJitmzX90QZiQdSvEcqzqmtoAgSEZNBT2LX2gIKg==} + engines: {node: '>=20'} + + '@cspell/rpc@9.6.4': + resolution: {integrity: sha512-vGI1788Rx5Yml9N1/pD4zGd8Vrchi2Y01ADf9NiiOaNVVdf4PU1GCssLCsiIzhYQneErpQ8pJi/mS2F/QMZbRA==} + engines: {node: '>=20.18'} + + '@cspell/strong-weak-map@9.6.4': + resolution: {integrity: sha512-AQrUbA0JUOEQgwItnfUQ6Ydk0hWY/uV3VhLwZWyrnT9eiQynmTnRTHtOCkkSl9+M4P0N4Raa2eGFRLcPAFksaw==} + engines: {node: '>=20'} + + '@cspell/url@9.6.4': + resolution: {integrity: sha512-h6VMlb7bDyGJfwLtipxxtHlT+ojzUXZz14AqZ/NEzY3LfOhfJTGpRcWLYFsgG/L0Ma4qjsYbPJt/Sj1C14j0VA==} + engines: {node: '>=20'} + '@csstools/color-helpers@6.0.1': resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==} engines: {node: '>=20.19.0'} @@ -1048,6 +1294,10 @@ packages: '@hapi/topo@6.0.2': resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + '@html-validate/stylish@4.3.0': + resolution: {integrity: sha512-eUfvKpRJg5TvzSfTf2EovrQoTKjkRnPUOUnXVJ2cQ4GbC/bQw98oxN+DdSf+HxOBK00YOhsP52xWdJPV1o4n5w==} + engines: {node: '>= 18'} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -2447,6 +2697,12 @@ packages: peerDependencies: webpack: '>=4.40.0' + '@sidvind/better-ajv-errors@4.0.1': + resolution: {integrity: sha512-6arF1ssKxItxgitPYXafUoLmsVBA6K7m9+ZGj6hLDoBl7nWpJ33EInwQUdHTle2METeWGxgQiqSex20KZRykew==} + engines: {node: '>= 18'} + peerDependencies: + ajv: ^7.0.0 || ^8.0.0 + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -3190,6 +3446,9 @@ packages: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + array-union@1.0.2: resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} engines: {node: '>=0.10.0'} @@ -3299,6 +3558,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.3: + resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} + engines: {node: 20 || >=22} + bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} peerDependencies: @@ -3390,6 +3653,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.2: + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} + engines: {node: 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -3452,6 +3719,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk-template@1.1.2: + resolution: {integrity: sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==} + engines: {node: '>=14.16'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -3545,6 +3816,10 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + clear-module@4.1.2: + resolution: {integrity: sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==} + engines: {node: '>=8'} + cli-cursor@2.1.0: resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} engines: {node: '>=4'} @@ -3634,6 +3909,10 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} + comment-json@4.5.1: + resolution: {integrity: sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==} + engines: {node: '>= 6'} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -3703,6 +3982,9 @@ packages: core-js@3.48.0: resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} @@ -3747,6 +4029,47 @@ packages: csp_evaluator@1.1.5: resolution: {integrity: sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw==} + cspell-config-lib@9.6.4: + resolution: {integrity: sha512-MecJNR9bIlcPBhyZFsXP6Q2n8qQ2IR9N9HiIz0yh0gBNVydp3LR5JITP5Ji8m7hexmZzVeoXms/dVN74XbS95g==} + engines: {node: '>=20'} + + cspell-dictionary@9.6.4: + resolution: {integrity: sha512-Ik9ZQVqV/fJfMt5X6IkC7yHGVH46/qjcqCNWwrMSwvROLM3SemNxxZoLvh0wi0GXz9WF1lHcxLJVdeKUk6QB8g==} + engines: {node: '>=20'} + + cspell-gitignore@9.6.4: + resolution: {integrity: sha512-a8asE9BsjJgJ506WUGh5VHrTdVEE8hWELjCJB2atPrW6iY5e4aCIugy0gkRC1ZH9/TseadlmMLrFzHUkJUjzsg==} + engines: {node: '>=20'} + hasBin: true + + cspell-glob@9.6.4: + resolution: {integrity: sha512-253VrjbR8QU15h8GtpDQLX5Ti9uNSuNod2T7f8YEElQOb9I/kUXoCj3Cq4P390IC99klqSHIDxHsxd77ex19lA==} + engines: {node: '>=20'} + + cspell-grammar@9.6.4: + resolution: {integrity: sha512-rvZyTB45/XSRWx7eAsrvTTAZvBTREr/2G2JWVMdqrptFyq1XReAKHhw/x1HJkNgWC9LKAK3bVQJpjLsNG37U9A==} + engines: {node: '>=20'} + hasBin: true + + cspell-io@9.6.4: + resolution: {integrity: sha512-bmvJ4yn5QK2FZWTkZA4sx2qJqIi8BrUUUV7W209drSwkYjhJtXqP0RyF6Qx4Xuu2D1s0UilEtO5Jd+E9UJkQ6w==} + engines: {node: '>=20'} + + cspell-lib@9.6.4: + resolution: {integrity: sha512-fUodKcIHTwvokuowB25XyFzBxlk73yj1QRw2por3BxDz9fAim1zAIohAPAnGuzj3LowYnTMjHLYE7RFDUSxy5A==} + engines: {node: '>=20'} + + cspell-trie-lib@9.6.4: + resolution: {integrity: sha512-JKwyRtyybbaTrixwI1OgU5Hvva2Z5zHVWl92WBa9U7KijAyiD/Ehp3T3DCYuBwGks7egw7MgWPySkXXnpme6mw==} + engines: {node: '>=20'} + peerDependencies: + '@cspell/cspell-types': 9.6.4 + + cspell@9.6.4: + resolution: {integrity: sha512-rZJmgcyBGKX3KcJ3KC9JYVHeKhDEVbmCheSp8eRGMYw6MCG9o7FHqQjGA/u4lEu4A0psr7ACP/5ym/QHyntRbA==} + engines: {node: '>=20.18'} + hasBin: true + css-line-break@2.1.0: resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} @@ -4087,6 +4410,10 @@ packages: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + env-paths@4.0.0: + resolution: {integrity: sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==} + engines: {node: '>=20'} + envinfo@7.21.0: resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==} engines: {node: '>=4'} @@ -4391,6 +4718,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@6.0.0: + resolution: {integrity: sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==} + engines: {node: '>=6.0.0'} + fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -4570,6 +4901,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + gensequence@8.0.8: + resolution: {integrity: sha512-omMVniXEXpdx/vKxGnPRoO2394Otlze28TyxECbFVyoSpZ9H3EO7lemjcB12OpQJzRW4e5tt/dL1rOxry6aMHg==} + engines: {node: '>=20'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -4636,6 +4971,10 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -4755,6 +5094,25 @@ packages: resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} engines: {node: '>=14'} + html-validate@10.8.0: + resolution: {integrity: sha512-SJzCC9XzYOvyJWSHWYwXwqi8WIg6LcO3Pz+MGyxOSkMYiNerkUm/pv4uJsOM2pfHObX4r8EcPyhSjQ7jCdajnw==} + engines: {node: ^20.19.0 || >= 22.12.0} + hasBin: true + peerDependencies: + jest: ^28.1.3 || ^29.0.3 || ^30.0.0 + jest-diff: ^28.1.3 || ^29.0.3 || ^30.0.0 + jest-snapshot: ^28.1.3 || ^29.0.3 || ^30.0.0 + vitest: ^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.1 + peerDependenciesMeta: + jest: + optional: true + jest-diff: + optional: true + jest-snapshot: + optional: true + vitest: + optional: true + html2canvas@1.4.1: resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} engines: {node: '>=8.0.0'} @@ -5057,6 +5415,10 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-safe-filename@0.1.1: + resolution: {integrity: sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==} + engines: {node: '>=20'} + is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} @@ -5722,6 +6084,10 @@ packages: resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} engines: {node: 20 || >=22} + minimatch@10.2.2: + resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5743,6 +6109,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} @@ -6075,6 +6445,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parent-module@2.0.0: + resolution: {integrity: sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==} + engines: {node: '>=8'} + parse-cache-control@1.0.1: resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} @@ -6130,6 +6504,10 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -6826,6 +7204,10 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + smol-toml@1.6.0: + resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} + engines: {node: '>= 18'} + socket.io-adapter@2.5.6: resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} @@ -7516,6 +7898,12 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -7718,6 +8106,10 @@ packages: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} engines: {node: '>=8'} + xdg-basedir@5.1.0: + resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} + engines: {node: '>=12'} + xlsx@0.18.5: resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} engines: {node: '>=0.8'} @@ -8068,6 +8460,230 @@ snapshots: conventional-commits-parser: 6.2.1 picocolors: 1.1.1 + '@cspell/cspell-bundled-dicts@9.6.4': + dependencies: + '@cspell/dict-ada': 4.1.1 + '@cspell/dict-al': 1.1.1 + '@cspell/dict-aws': 4.0.17 + '@cspell/dict-bash': 4.2.2 + '@cspell/dict-companies': 3.2.10 + '@cspell/dict-cpp': 7.0.2 + '@cspell/dict-cryptocurrencies': 5.0.5 + '@cspell/dict-csharp': 4.0.8 + '@cspell/dict-css': 4.0.19 + '@cspell/dict-dart': 2.3.2 + '@cspell/dict-data-science': 2.0.13 + '@cspell/dict-django': 4.1.6 + '@cspell/dict-docker': 1.1.17 + '@cspell/dict-dotnet': 5.0.12 + '@cspell/dict-elixir': 4.0.8 + '@cspell/dict-en-common-misspellings': 2.1.12 + '@cspell/dict-en-gb-mit': 3.1.18 + '@cspell/dict-en_us': 4.4.29 + '@cspell/dict-filetypes': 3.0.15 + '@cspell/dict-flutter': 1.1.1 + '@cspell/dict-fonts': 4.0.5 + '@cspell/dict-fsharp': 1.1.1 + '@cspell/dict-fullstack': 3.2.8 + '@cspell/dict-gaming-terms': 1.1.2 + '@cspell/dict-git': 3.1.0 + '@cspell/dict-golang': 6.0.26 + '@cspell/dict-google': 1.0.9 + '@cspell/dict-haskell': 4.0.6 + '@cspell/dict-html': 4.0.14 + '@cspell/dict-html-symbol-entities': 4.0.5 + '@cspell/dict-java': 5.0.12 + '@cspell/dict-julia': 1.1.1 + '@cspell/dict-k8s': 1.0.12 + '@cspell/dict-kotlin': 1.1.1 + '@cspell/dict-latex': 5.0.0 + '@cspell/dict-lorem-ipsum': 4.0.5 + '@cspell/dict-lua': 4.0.8 + '@cspell/dict-makefile': 1.0.5 + '@cspell/dict-markdown': 2.0.14(@cspell/dict-css@4.0.19)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.14)(@cspell/dict-typescript@3.2.3) + '@cspell/dict-monkeyc': 1.0.12 + '@cspell/dict-node': 5.0.9 + '@cspell/dict-npm': 5.2.34 + '@cspell/dict-php': 4.1.1 + '@cspell/dict-powershell': 5.0.15 + '@cspell/dict-public-licenses': 2.0.15 + '@cspell/dict-python': 4.2.25 + '@cspell/dict-r': 2.1.1 + '@cspell/dict-ruby': 5.1.0 + '@cspell/dict-rust': 4.1.2 + '@cspell/dict-scala': 5.0.9 + '@cspell/dict-shell': 1.1.2 + '@cspell/dict-software-terms': 5.1.21 + '@cspell/dict-sql': 2.2.1 + '@cspell/dict-svelte': 1.0.7 + '@cspell/dict-swift': 2.0.6 + '@cspell/dict-terraform': 1.1.3 + '@cspell/dict-typescript': 3.2.3 + '@cspell/dict-vue': 3.0.5 + '@cspell/dict-zig': 1.0.0 + + '@cspell/cspell-json-reporter@9.6.4': + dependencies: + '@cspell/cspell-types': 9.6.4 + + '@cspell/cspell-performance-monitor@9.6.4': {} + + '@cspell/cspell-pipe@9.6.4': {} + + '@cspell/cspell-resolver@9.6.4': + dependencies: + global-directory: 4.0.1 + + '@cspell/cspell-service-bus@9.6.4': {} + + '@cspell/cspell-types@9.6.4': {} + + '@cspell/cspell-worker@9.6.4': + dependencies: + cspell-lib: 9.6.4 + + '@cspell/dict-ada@4.1.1': {} + + '@cspell/dict-al@1.1.1': {} + + '@cspell/dict-aws@4.0.17': {} + + '@cspell/dict-bash@4.2.2': + dependencies: + '@cspell/dict-shell': 1.1.2 + + '@cspell/dict-companies@3.2.10': {} + + '@cspell/dict-cpp@7.0.2': {} + + '@cspell/dict-cryptocurrencies@5.0.5': {} + + '@cspell/dict-csharp@4.0.8': {} + + '@cspell/dict-css@4.0.19': {} + + '@cspell/dict-dart@2.3.2': {} + + '@cspell/dict-data-science@2.0.13': {} + + '@cspell/dict-de-de@4.1.2': {} + + '@cspell/dict-django@4.1.6': {} + + '@cspell/dict-docker@1.1.17': {} + + '@cspell/dict-dotnet@5.0.12': {} + + '@cspell/dict-elixir@4.0.8': {} + + '@cspell/dict-en-common-misspellings@2.1.12': {} + + '@cspell/dict-en-gb-mit@3.1.18': {} + + '@cspell/dict-en_us@4.4.29': {} + + '@cspell/dict-filetypes@3.0.15': {} + + '@cspell/dict-flutter@1.1.1': {} + + '@cspell/dict-fonts@4.0.5': {} + + '@cspell/dict-fsharp@1.1.1': {} + + '@cspell/dict-fullstack@3.2.8': {} + + '@cspell/dict-gaming-terms@1.1.2': {} + + '@cspell/dict-git@3.1.0': {} + + '@cspell/dict-golang@6.0.26': {} + + '@cspell/dict-google@1.0.9': {} + + '@cspell/dict-haskell@4.0.6': {} + + '@cspell/dict-html-symbol-entities@4.0.5': {} + + '@cspell/dict-html@4.0.14': {} + + '@cspell/dict-java@5.0.12': {} + + '@cspell/dict-julia@1.1.1': {} + + '@cspell/dict-k8s@1.0.12': {} + + '@cspell/dict-kotlin@1.1.1': {} + + '@cspell/dict-latex@5.0.0': {} + + '@cspell/dict-lorem-ipsum@4.0.5': {} + + '@cspell/dict-lua@4.0.8': {} + + '@cspell/dict-makefile@1.0.5': {} + + '@cspell/dict-markdown@2.0.14(@cspell/dict-css@4.0.19)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.14)(@cspell/dict-typescript@3.2.3)': + dependencies: + '@cspell/dict-css': 4.0.19 + '@cspell/dict-html': 4.0.14 + '@cspell/dict-html-symbol-entities': 4.0.5 + '@cspell/dict-typescript': 3.2.3 + + '@cspell/dict-monkeyc@1.0.12': {} + + '@cspell/dict-node@5.0.9': {} + + '@cspell/dict-npm@5.2.34': {} + + '@cspell/dict-php@4.1.1': {} + + '@cspell/dict-powershell@5.0.15': {} + + '@cspell/dict-public-licenses@2.0.15': {} + + '@cspell/dict-python@4.2.25': + dependencies: + '@cspell/dict-data-science': 2.0.13 + + '@cspell/dict-r@2.1.1': {} + + '@cspell/dict-ruby@5.1.0': {} + + '@cspell/dict-rust@4.1.2': {} + + '@cspell/dict-scala@5.0.9': {} + + '@cspell/dict-shell@1.1.2': {} + + '@cspell/dict-software-terms@5.1.21': {} + + '@cspell/dict-sql@2.2.1': {} + + '@cspell/dict-svelte@1.0.7': {} + + '@cspell/dict-swift@2.0.6': {} + + '@cspell/dict-terraform@1.1.3': {} + + '@cspell/dict-typescript@3.2.3': {} + + '@cspell/dict-vue@3.0.5': {} + + '@cspell/dict-zig@1.0.0': {} + + '@cspell/dynamic-import@9.6.4': + dependencies: + '@cspell/url': 9.6.4 + import-meta-resolve: 4.2.0 + + '@cspell/filetypes@9.6.4': {} + + '@cspell/rpc@9.6.4': {} + + '@cspell/strong-weak-map@9.6.4': {} + + '@cspell/url@9.6.4': {} + '@csstools/color-helpers@6.0.1': {} '@csstools/css-calc@3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -8462,6 +9078,10 @@ snapshots: dependencies: '@hapi/hoek': 11.0.7 + '@html-validate/stylish@4.3.0': + dependencies: + kleur: 4.1.5 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -10117,6 +10737,11 @@ snapshots: - encoding - supports-color + '@sidvind/better-ajv-errors@4.0.1(ajv@8.17.1)': + dependencies: + ajv: 8.17.1 + kleur: 4.1.5 + '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.1.0': {} @@ -10876,6 +11501,8 @@ snapshots: is-string: 1.1.1 math-intrinsics: 1.1.0 + array-timsort@1.0.3: {} + array-union@1.0.2: dependencies: array-uniq: 1.0.3 @@ -11020,6 +11647,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.3: {} + bare-events@2.8.2: {} bare-fs@4.5.3: @@ -11113,6 +11742,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.2: + dependencies: + balanced-match: 4.0.3 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -11176,6 +11809,10 @@ snapshots: chai@6.2.2: {} + chalk-template@1.1.2: + dependencies: + chalk: 5.6.2 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -11307,6 +11944,11 @@ snapshots: clean-stack@2.2.0: {} + clear-module@4.1.2: + dependencies: + parent-module: 2.0.0 + resolve-from: 5.0.0 + cli-cursor@2.1.0: dependencies: restore-cursor: 2.0.0 @@ -11381,6 +12023,12 @@ snapshots: commander@7.2.0: {} + comment-json@4.5.1: + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + commondir@1.0.1: {} compare-func@2.0.0: @@ -11462,6 +12110,8 @@ snapshots: core-js@3.48.0: {} + core-util-is@1.0.3: {} + cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -11507,6 +12157,97 @@ snapshots: csp_evaluator@1.1.5: {} + cspell-config-lib@9.6.4: + dependencies: + '@cspell/cspell-types': 9.6.4 + comment-json: 4.5.1 + smol-toml: 1.6.0 + yaml: 2.8.2 + + cspell-dictionary@9.6.4: + dependencies: + '@cspell/cspell-performance-monitor': 9.6.4 + '@cspell/cspell-pipe': 9.6.4 + '@cspell/cspell-types': 9.6.4 + cspell-trie-lib: 9.6.4(@cspell/cspell-types@9.6.4) + fast-equals: 6.0.0 + + cspell-gitignore@9.6.4: + dependencies: + '@cspell/url': 9.6.4 + cspell-glob: 9.6.4 + cspell-io: 9.6.4 + + cspell-glob@9.6.4: + dependencies: + '@cspell/url': 9.6.4 + picomatch: 4.0.3 + + cspell-grammar@9.6.4: + dependencies: + '@cspell/cspell-pipe': 9.6.4 + '@cspell/cspell-types': 9.6.4 + + cspell-io@9.6.4: + dependencies: + '@cspell/cspell-service-bus': 9.6.4 + '@cspell/url': 9.6.4 + + cspell-lib@9.6.4: + dependencies: + '@cspell/cspell-bundled-dicts': 9.6.4 + '@cspell/cspell-performance-monitor': 9.6.4 + '@cspell/cspell-pipe': 9.6.4 + '@cspell/cspell-resolver': 9.6.4 + '@cspell/cspell-types': 9.6.4 + '@cspell/dynamic-import': 9.6.4 + '@cspell/filetypes': 9.6.4 + '@cspell/rpc': 9.6.4 + '@cspell/strong-weak-map': 9.6.4 + '@cspell/url': 9.6.4 + clear-module: 4.1.2 + cspell-config-lib: 9.6.4 + cspell-dictionary: 9.6.4 + cspell-glob: 9.6.4 + cspell-grammar: 9.6.4 + cspell-io: 9.6.4 + cspell-trie-lib: 9.6.4(@cspell/cspell-types@9.6.4) + env-paths: 4.0.0 + gensequence: 8.0.8 + import-fresh: 3.3.1 + resolve-from: 5.0.0 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + xdg-basedir: 5.1.0 + + cspell-trie-lib@9.6.4(@cspell/cspell-types@9.6.4): + dependencies: + '@cspell/cspell-types': 9.6.4 + + cspell@9.6.4: + dependencies: + '@cspell/cspell-json-reporter': 9.6.4 + '@cspell/cspell-performance-monitor': 9.6.4 + '@cspell/cspell-pipe': 9.6.4 + '@cspell/cspell-types': 9.6.4 + '@cspell/cspell-worker': 9.6.4 + '@cspell/dynamic-import': 9.6.4 + '@cspell/url': 9.6.4 + ansi-regex: 6.2.2 + chalk: 5.6.2 + chalk-template: 1.1.2 + commander: 14.0.3 + cspell-config-lib: 9.6.4 + cspell-dictionary: 9.6.4 + cspell-gitignore: 9.6.4 + cspell-glob: 9.6.4 + cspell-io: 9.6.4 + cspell-lib: 9.6.4 + fast-json-stable-stringify: 2.1.0 + flatted: 3.3.3 + semver: 7.7.4 + tinyglobby: 0.2.15 + css-line-break@2.1.0: dependencies: utrie: 1.0.2 @@ -11823,6 +12564,10 @@ snapshots: env-paths@3.0.0: {} + env-paths@4.0.0: + dependencies: + is-safe-filename: 0.1.1 + envinfo@7.21.0: {} environment@1.1.0: {} @@ -12403,6 +13148,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-equals@6.0.0: {} + fast-fifo@1.3.2: {} fast-glob@3.3.1: @@ -12572,6 +13319,8 @@ snapshots: generator-function@2.0.1: {} + gensequence@8.0.8: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -12654,6 +13403,12 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.1 + glob@13.0.6: + dependencies: + minimatch: 10.2.2 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -12820,6 +13575,19 @@ snapshots: htmlparser2: 8.0.2 selderee: 0.11.0 + html-validate@10.8.0(vitest@4.0.18): + dependencies: + '@html-validate/stylish': 4.3.0 + '@sidvind/better-ajv-errors': 4.0.1(ajv@8.17.1) + ajv: 8.17.1 + glob: 13.0.6 + kleur: 4.1.5 + minimist: 1.2.8 + prompts: 2.4.2 + semver: 7.7.4 + optionalDependencies: + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + html2canvas@1.4.1: dependencies: css-line-break: 2.1.0 @@ -13130,6 +13898,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + is-safe-filename@0.1.1: {} + is-set@2.0.3: {} is-shared-array-buffer@1.0.4: @@ -13975,6 +14745,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.1 + minimatch@10.2.2: + dependencies: + brace-expansion: 5.0.2 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -13993,6 +14767,8 @@ snapshots: minipass@7.1.2: {} + minipass@7.1.3: {} + mitt@3.0.1: {} mkdirp@0.5.6: @@ -14368,6 +15144,10 @@ snapshots: dependencies: callsites: 3.1.0 + parent-module@2.0.0: + dependencies: + callsites: 3.1.0 + parse-cache-control@1.0.1: {} parse-entities@4.0.2: @@ -14431,6 +15211,11 @@ snapshots: lru-cache: 11.2.5 minipass: 7.1.2 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.5 + minipass: 7.1.3 + path-to-regexp@0.1.12: {} path@0.12.7: @@ -15330,6 +16115,8 @@ snapshots: smart-buffer@4.2.0: {} + smol-toml@1.6.0: {} + socket.io-adapter@2.5.6: dependencies: debug: 4.4.3 @@ -16123,6 +16910,10 @@ snapshots: void-elements@3.1.0: {} + vscode-languageserver-textdocument@1.0.12: {} + + vscode-uri@3.1.0: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -16340,6 +17131,8 @@ snapshots: xdg-basedir@4.0.0: {} + xdg-basedir@5.1.0: {} + xlsx@0.18.5: dependencies: adler-32: 1.3.1 diff --git a/scripts/check-html.ts b/scripts/check-html.ts new file mode 100644 index 00000000..edad8959 --- /dev/null +++ b/scripts/check-html.ts @@ -0,0 +1,82 @@ +import axios from 'axios'; +import * as cheerio from 'cheerio'; +import * as fs from 'fs'; +import * as path from 'path'; +import { execSync } from 'child_process'; + +const targetUrl = process.argv[2] || process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'; +const limit = process.env.PAGESPEED_LIMIT ? parseInt(process.env.PAGESPEED_LIMIT) : 20; +const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026'; + +async function main() { + console.log(`\n🚀 Starting HTML Validation for: ${targetUrl}`); + console.log(`📊 Limit: ${limit} pages\n`); + + try { + const sitemapUrl = `${targetUrl.replace(/\/$/, '')}/sitemap.xml`; + console.log(`📥 Fetching sitemap from ${sitemapUrl}...`); + + const response = await axios.get(sitemapUrl, { + headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, + validateStatus: (status) => status < 400, + }); + + const $ = cheerio.load(response.data, { xmlMode: true }); + let urls = $('url loc') + .map((i, el) => $(el).text()) + .get(); + + const urlPattern = /https?:\/\/[^\/]+/; + urls = [...new Set(urls)] + .filter((u) => u.startsWith('http')) + .map((u) => u.replace(urlPattern, targetUrl.replace(/\/$/, ''))) + .sort(); + + console.log(`✅ Found ${urls.length} URLs in sitemap.`); + + if (urls.length === 0) { + console.error('❌ No URLs found in sitemap. Is the site up?'); + process.exit(1); + } + + if (urls.length > limit) { + console.log( + `⚠️ Too many pages (${urls.length}). Limiting to ${limit} representative pages.`, + ); + const home = urls.filter((u) => u.endsWith('/de') || u.endsWith('/en') || u === targetUrl); + const others = urls.filter((u) => !home.includes(u)); + urls = [...home, ...others.slice(0, limit - home.length)]; + } + + const outputDir = path.join(process.cwd(), '.htmlvalidate-tmp'); + if (fs.existsSync(outputDir)) fs.rmSync(outputDir, { recursive: true, force: true }); + fs.mkdirSync(outputDir, { recursive: true }); + + console.log(`📥 Fetching HTML for ${urls.length} pages...`); + for (let i = 0; i < urls.length; i++) { + const u = urls[i]; + const res = await axios.get(u, { + headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, + }); + const filename = `page-${i}.html`; + fs.writeFileSync(path.join(outputDir, filename), res.data); + } + + console.log(`\n💻 Executing html-validate...`); + try { + execSync(`npx html-validate .htmlvalidate-tmp/*.html`, { stdio: 'inherit' }); + console.log(`✅ HTML Validation passed perfectly!`); + } catch (e) { + console.error(`❌ HTML Validation found issues.`); + process.exit(1); + } + } catch (error: any) { + console.error(`\n❌ Error during HTML Validation:`, error.message); + process.exit(1); + } finally { + const outputDir = path.join(process.cwd(), '.htmlvalidate-tmp'); + if (fs.existsSync(outputDir)) fs.rmSync(outputDir, { recursive: true, force: true }); + } +} + +main(); diff --git a/scripts/check-links.sh b/scripts/check-links.sh new file mode 100644 index 00000000..eec7c708 --- /dev/null +++ b/scripts/check-links.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +# Auto-provision Lychee Rust Binary if missing +if [ ! -f ./lychee ]; then + echo "📥 Downloading Lychee Link Checker (v0.15.1)..." + curl -sSLo lychee.tar.gz https://github.com/lycheeverse/lychee/releases/download/v0.15.1/lychee-v0.15.1-x86_64-unknown-linux-gnu.tar.gz + tar -xzf lychee.tar.gz lychee + rm lychee.tar.gz + chmod +x ./lychee +fi + +echo "🚀 Starting Deep Link Assessment (Lychee)..." + +# Exclude localhost, mintel.me (internal infrastructure), and common placeholder domains +# Scan markdown files and component files for hardcoded dead links +./lychee \ + --exclude "localhost" \ + --exclude "127.0.0.1" \ + --exclude "mintel\.me" \ + --exclude "example\.com" \ + --exclude-mail \ + --accept 200,204,401,403 \ + "./content/**/*.mdx" "./content/**/*.md" "./app/**/*.tsx" "./components/**/*.tsx" + +echo "✅ All project source links are alive and healthy!" diff --git a/scripts/check-security.ts b/scripts/check-security.ts new file mode 100644 index 00000000..c84569e6 --- /dev/null +++ b/scripts/check-security.ts @@ -0,0 +1,54 @@ +import axios from 'axios'; + +const targetUrl = process.argv[2] || process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'; +const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026'; + +const requiredHeaders = [ + 'strict-transport-security', + 'x-frame-options', + 'x-content-type-options', + 'referrer-policy', + 'content-security-policy', +]; + +async function main() { + console.log(`\n🛡️ Starting Security Headers Scan for: ${targetUrl}\n`); + try { + const response = await axios.head(targetUrl, { + headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, + validateStatus: () => true, + }); + + const headers = response.headers; + let allPassed = true; + + const results = requiredHeaders.map((header) => { + const present = !!headers[header]; + if (!present) allPassed = false; + return { + Header: header, + Status: present ? '✅ Present' : '❌ Missing', + Value: present + ? headers[header].length > 50 + ? headers[header].substring(0, 47) + '...' + : headers[header] + : 'N/A', + }; + }); + + console.table(results); + + if (allPassed) { + console.log(`\n✅ All required security headers are correctly configured!\n`); + process.exit(0); + } else { + console.log(`\n❌ Missing critical security headers. Please update next.config.mjs!\n`); + process.exit(process.env.CI ? 1 : 0); // Don't crash local dev hard if missing, but crash CI + } + } catch (error: any) { + console.error(`❌ Failed to scan headers: ${error.message}`); + process.exit(1); + } +} + +main();