diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index c9ce872fd..71b06b93c 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -6,23 +6,42 @@ on: types: [completed] branches: [master] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest - if: ${{ github.event.repository.fork == false && github.event.workflow_run.conclusion == 'success' }} + timeout-minutes: 10 + if: >- + vars.ASTRO_PUBLISH_ENABLED != 'true' && + github.repository == 'LOLBAS-Project/LOLBAS' && + github.event.repository.fork == false && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'master' && + github.event.workflow_run.event == 'push' + concurrency: + group: lolbas-external-site-publish + cancel-in-progress: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + persist-credentials: false - name: Change .yml to .md run: | - for x in $(find yml/ -name '*.yml'); do echo "---" >> "$x"; mv "$x" "${x/%\.yml/.md}"; done + while IFS= read -r -d '' x; do + echo "---" >> "$x" + mv "$x" "${x/%.yml/.md}" + done < <(find yml/ -name '*.yml' -print0) mv yml/OSBinaries yml/Binaries mv yml/OSLibraries yml/Libraries mv yml/OSScripts yml/Scripts rm -r yml/HonorableMentions - name: Deploy to LOLBAS-Project.github.io repo - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} external_repository: LOLBAS-Project/LOLBAS-Project.github.io diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml new file mode 100644 index 000000000..93d4c1056 --- /dev/null +++ b/.github/workflows/site.yml @@ -0,0 +1,129 @@ +name: Validate and publish Astro site + +on: + push: + branches: ['**'] + pull_request: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +# PR validation cannot cancel a production build or access the deploy key. +concurrency: + group: astro-site-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: false + +jobs: + build: + # Fork branches can produce review artifacts; upstream validates master/PRs. + if: >- + github.event_name != 'push' || + github.repository != 'LOLBAS-Project/LOLBAS' || + github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + working-directory: website + env: + ASTRO_TELEMETRY_DISABLED: '1' + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install canonical catalog validators + working-directory: . + run: python -m pip install yamllint==1.37.1 pydantic==2.11.9 PyYAML==6.0.3 + - name: Lint catalog YAML + working-directory: . + run: yamllint -c .github/.yamllint yml/**/ + - name: Validate catalog schemas + working-directory: . + run: python .github/workflows/validation.py + - uses: actions/setup-node@v4 + with: + node-version: '22.22.0' + cache: npm + cache-dependency-path: website/package-lock.json + - run: npm ci --no-audit --no-fund + - name: Check source formatting + run: npm run format:check + - name: Check source and generated data + run: npm run check + - name: Test data compatibility + run: npm run test:data + - name: Build and verify static output + run: npm run build + - name: Record artifact source + run: printf '%s\n' "$GITHUB_SHA" > dist/source-revision.txt + - run: npx playwright install --with-deps chromium + - name: Test the production artifact + run: npm test + - name: Upload browser evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: browser-evidence-${{ github.run_id }}-${{ github.run_attempt }} + path: | + website/playwright-report/ + website/test-results/ + if-no-files-found: ignore + retention-days: 14 + - name: Upload verified site + uses: actions/upload-artifact@v4 + with: + name: verified-site-${{ github.sha }}-${{ github.run_attempt }} + path: website/dist/ + if-no-files-found: error + retention-days: 14 + - name: Summarize review artifacts + run: | + { + printf '### Verified LOLBAS site\n\n' + printf 'Source: **%s**\n\n' "$GITHUB_SHA" + printf 'Download **verified-site-%s-%s** from this run to preview the tested site locally.\n\n' "$GITHUB_SHA" "$GITHUB_RUN_ATTEMPT" + printf 'Inside the extracted artifact, run:\n\n python3 -m http.server 8000\n\nThen open http://localhost:8000/.\n\n' + printf 'Browser screenshots and test results are in **browser-evidence-%s-%s**.\n\n' "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT" + printf 'Fork and pull-request runs build review artifacts only. Production publication requires a separate upstream cutover.\n' + } >> "$GITHUB_STEP_SUMMARY" + + deploy: + needs: build + if: >- + vars.ASTRO_PUBLISH_ENABLED == 'true' && + github.repository == 'LOLBAS-Project/LOLBAS' && + github.ref == 'refs/heads/master' && + (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: + name: github-pages + url: https://lolbas-project.github.io + concurrency: + group: lolbas-external-site-publish + cancel-in-progress: false + steps: + # No checkout or rebuild: publish only this run's tested artifact. + - uses: actions/download-artifact@v4 + with: + name: verified-site-${{ github.sha }}-${{ github.run_attempt }} + path: verified-site + - name: Verify artifact source + run: test "$(cat verified-site/source-revision.txt)" = "$GITHUB_SHA" + - name: Publish verified Astro site to external repository + uses: peaceiris/actions-gh-pages@v4 + with: + deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} + external_repository: LOLBAS-Project/LOLBAS-Project.github.io + publish_branch: master + publish_dir: verified-site + enable_jekyll: false + keep_files: false + commit_message: 'Publish verified Astro site from ${{ github.sha }}' + user_name: 'github-actions[bot]' + user_email: 'github-actions[bot]@users.noreply.github.com' diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b37cf5cad --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.context/ +website/node_modules/ +website/dist/ +website/.astro/ +website/src/data/ +website/public/api/ +website/public/assets/lolbas-count.svg +website/public/mitre_attack_navigator_layer.json +website/test-results/ +website/playwright-report/ diff --git a/README.md b/README.md index 813867675..1f8059ac5 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,23 @@ If you have found a new LOLBin or LOLScript that you would like to contribute, p A template for the required format has been provided here: https://github.com/LOLBAS-Project/LOLBAS/blob/master/YML-Template.yml +## Build the website locally + +The Astro website builds from this repository's YAML catalog. With Node.js +22.22.0 installed, run: + +```sh +cd website +npm ci +npm run check +npm run test:data +npm run build +npm run preview -- --port 4387 +``` + +See [the website guide](website/README.md) for browser tests, development commands, +and the maintainer-controlled deployment cutover and rollback procedure. + ## The History of the LOLBin The phrase "Living off the land" was coined by Christopher Campbell (@obscuresec) & Matt Graeber (@mattifestation) at [DerbyCon 3](https://www.youtube.com/watch?v=j-r6UonEkUw). diff --git a/docs/ASTRO_CHANGE_CONTRACT.md b/docs/ASTRO_CHANGE_CONTRACT.md new file mode 100644 index 000000000..b04e1718a --- /dev/null +++ b/docs/ASTRO_CHANGE_CONTRACT.md @@ -0,0 +1,27 @@ +# Astro site migration contract + +Implementation, fork push, fork CI build, isolated fork-hosted preview and upstream +pull request are authorized. Fork preview deployment belongs to a separate +fork-owned workflow outside the upstream PR. +Production publication and merging remain separate maintainer actions. +Canonical content remains `yml/`; source records must not change. Branding is +LOLBAS only. No analytics, external runtime data dependencies, or backend required. + +| Behavior | Acceptance evidence | +| --- | --- | +| LOL-SITE-001 | Static catalog includes every legacy-published record; links retain `/lolbas/{type}/{filename}/` and category anchors. | +| LOL-SITE-002 | Search matches name, descriptions, ATT&CK IDs, command metadata and legacy `/function` / `#type` syntax. Combined category, type, technique, detection filters, column sorting, clear, pagination and URL reload work. | +| LOL-SITE-003 | Details expose all commands, paths, detections/IOCs, aliases, tags, resources, code samples, author and acknowledgements; copy controls provide honest feedback. | +| LOL-SITE-004 | Timeline aggregates YAML `Created` dates by year, explains methodology and has accessible chart data and catalog filter links. | +| LOL-API-001 | JSON retains legacy fields and URLs; CSV retains exact 17-column header and one row per command; Navigator and count badge remain available. Source-derived tests and escaping fixtures. | +| LOL-CI-001 | Fork branch and PR builds are read-only, cached and tested against production output, producing review artifacts. External publication is opt-in, restricted to upstream master, uses the same tested artifact, and cannot be triggered by forks or PRs. Legacy publication remains until deliberate cutover. | + +Assurance: A2 site/API with A3 publication boundary review. Independent review +required. No authentication boundary exists. Evidence: data tests, Astro check, +production build verification, desktop/mobile/no-JS/URL/API/console browser checks, +visual screenshots and independent review. Remote CI/publication not run locally; +repository settings and external branch backup precede future cutover. + +Stop at a fork-hosted branch, verified fork CI build, clickable fork preview and +upstream PR with screenshots and recorded evidence. Do not merge or publish the +official LOLBAS site. diff --git a/docs/screenshots/lolbas-about.png b/docs/screenshots/lolbas-about.png new file mode 100644 index 000000000..de18a4b64 Binary files /dev/null and b/docs/screenshots/lolbas-about.png differ diff --git a/docs/screenshots/lolbas-catalog.png b/docs/screenshots/lolbas-catalog.png new file mode 100644 index 000000000..f7999c492 Binary files /dev/null and b/docs/screenshots/lolbas-catalog.png differ diff --git a/docs/screenshots/lolbas-detail.png b/docs/screenshots/lolbas-detail.png new file mode 100644 index 000000000..771676c9d Binary files /dev/null and b/docs/screenshots/lolbas-detail.png differ diff --git a/docs/screenshots/lolbas-home.png b/docs/screenshots/lolbas-home.png new file mode 100644 index 000000000..90580edda Binary files /dev/null and b/docs/screenshots/lolbas-home.png differ diff --git a/docs/screenshots/lolbas-mobile.png b/docs/screenshots/lolbas-mobile.png new file mode 100644 index 000000000..a72df3366 Binary files /dev/null and b/docs/screenshots/lolbas-mobile.png differ diff --git a/website/.prettierignore b/website/.prettierignore new file mode 100644 index 000000000..7fbb8bb9f --- /dev/null +++ b/website/.prettierignore @@ -0,0 +1,2 @@ +src/data/ +package-lock.json diff --git a/website/.prettierrc.json b/website/.prettierrc.json new file mode 100644 index 000000000..e0087b4ac --- /dev/null +++ b/website/.prettierrc.json @@ -0,0 +1 @@ +{ "plugins": ["prettier-plugin-astro"], "singleQuote": true } diff --git a/website/README.md b/website/README.md new file mode 100644 index 000000000..92fef9cdf --- /dev/null +++ b/website/README.md @@ -0,0 +1,121 @@ +# LOLBAS website + +The Astro site builds directly from the repository's `yml/` catalog. Edit those +YAML records to update the catalog; generated data and static output are build +products. The existing YAML lint and schema workflow remains a separate gate. + +## Local development and verification + +Use Node.js **22.22.0** and the committed npm lockfile. From the repository root: + +```sh +cd website +npm ci +npm run check +npm run test:data +npm run build +npx playwright install chromium +npm test +npm run preview -- --port 4387 +``` + +`check` regenerates catalog data and runs Astro's checks. `test:data` runs the +data and publication regression tests. `build` regenerates data, produces `dist/`, and verifies +the generated output. Playwright tests the production build through the preview +server on port 4388; run `build` before `test`. The live preview uses port 4387 +so other workspaces can keep using the default Astro port. On Linux CI, install browser system dependencies +with `npx playwright install --with-deps chromium`. + +For interactive development, run `npm run dev`. Run `npm run prepare:data` after +changing catalog YAML during an existing development session. Do not execute the +catalog's example commands: they are documentation rendered by the site. + +## Charts and compatibility + +The growth chart groups YAML `Created` dates by year and shows cumulative entries; +it is catalog growth, not observed threat activity. Function counts count entries +with a matching command; an entry can have multiple functions. Detection counts +include published links and exclude IOC-only records. + +Legacy JSON fields, CSV columns, detail URLs/category anchors, the Navigator layer +and count badge remain available. Generated API files do not include UI metadata. +Search also accepts the legacy `/function` and `#type` syntax. Recently added +sorting uses the recorded `Created` date; it does not imply last-modified dates. + +Every catalog column header toggles ascending/descending order. Function lists +and ATT&CK lists sort alphabetically and numerically respectively; detections +sort by published resource count. The dropdown, header indicators and URL share +one sort state. Older browsers retain native filter menus through progressive CSS. + +## CI and publication boundary + +`.github/workflows/site.yml` validates all fork branch pushes, upstream `master` +pushes, pull requests targeting `master`, and manual runs. Fork builds require +Actions to be enabled in the fork. They upload review artifacts and browser +evidence without publishing a site or needing deployment secrets. Download the +verified site from the run and serve the extracted directory with +`python3 -m http.server 8000` to preview it. It caches npm downloads, installs with `npm ci`, runs +checks and data tests, builds and verifies the site, and runs Chromium against +the production output. Only a successful run uploads the `verified-site` artifact +for publication. `source-revision.txt` records its source SHA. Browser evidence is +uploaded separately, including on test failure when evidence exists. + +The UI also supports an optional Astro `SITE_BASE` (for example `/LOLBAS`) for +project-site previews. A separate fork-owned workflow can build with that value, +run the preview smoke test via `SITE_BASE=/LOLBAS npm test`, and publish to the +fork's Pages site. That preview publisher is intentionally outside this upstream +change. Canonical API export URLs remain unchanged in prefixed previews. + +Publication defaults to **disabled**. The repository Actions variable +`ASTRO_PUBLISH_ENABLED` must equal the literal string `true`. The publisher also +requires the canonical `LOLBAS-Project/LOLBAS` repository, the `master` branch, +and either a push or manual dispatch. Pull requests and `workflow_run` events +cannot publish Astro output. The `github-pages` environment can require human +approval. Use a **repository variable**, not an environment variable, because +both workflow job conditions must see the switch before entering an environment. + +The publisher downloads the tested artifact from the same workflow run and +verifies its recorded SHA. It does not rebuild. Using the existing +`ACTIONS_DEPLOY_KEY`, it replaces the root of the external repository +`LOLBAS-Project/LOLBAS-Project.github.io` on branch `master`, with Jekyll disabled. +This is an external-repository publication, not an `actions/deploy-pages` upload. + +While the switch is unset or differs from `true`, the legacy Jekyll catalog +publisher remains enabled. It accepts only successful YAML-validation runs +originating from a push, checks out that validated run's exact SHA, and updates +the external repository's `_lolbas` directory. Both publishers share a deployment +concurrency group. + +## Maintainer cutover and rollback + +The following is a human-operated release procedure; local implementation and +review do not authorize a live publication. + +1. Review the migration diff and independent release review, including catalog + compatibility, URLs, production browser results, and the generated artifact. + Confirm the master revision has passed both site and YAML/schema checks. +2. Back up the external repository's current `master` Jekyll source to a retained + branch or tag and record its commit SHA. Preserve any custom domain, redirects, + and other site assets needed after replacing the root. Confirm the external + repository's Pages settings serve `master` from the root. +3. Configure the source repository's `github-pages` environment and desired + required reviewers. Confirm the existing deploy key can write only to the + intended external repository. Wait for pending legacy publications to finish. +4. Set the **repository Actions variable** `ASTRO_PUBLISH_ENABLED=true`, then + manually run the site workflow on `master`. Review and approve the environment + deployment if protection requires it. This publishes that run's tested artifact. +5. Check the live homepage, a legacy detail URL, search/filter behavior, API + exports, and `source-revision.txt`. Retain the tested artifact and deployment + evidence with the release record. + +To roll back, unset `ASTRO_PUBLISH_ENABLED` (or set it to `false`) and cancel any +queued or running Astro deployment before restoring the external `master` branch +to the backed-up Jekyll source. Restore Pages/custom-domain settings if changed. +Verify the live legacy site, then allow future successful push-triggered YAML +validation runs to update `_lolbas` again. Merely unsetting the variable does not +restore the Jekyll files removed by the Astro root publication. + +Required release review can be performed locally against workflows and build +evidence. Live deployment, repository-variable changes, environment changes, and +rollback operations are skipped during local validation and require a maintainer +to execute this release procedure. diff --git a/website/astro.config.mjs b/website/astro.config.mjs new file mode 100644 index 000000000..c74353d5f --- /dev/null +++ b/website/astro.config.mjs @@ -0,0 +1,8 @@ +import { defineConfig } from 'astro/config'; +export default defineConfig({ + site: 'https://lolbas-project.github.io', + base: process.env.SITE_BASE || '/', + output: 'static', + trailingSlash: 'always', + devToolbar: { enabled: false }, +}); diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 000000000..22b42bc45 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,5457 @@ +{ + "name": "lolbas-website", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lolbas-website", + "version": "1.0.0", + "dependencies": { + "astro": "7.3.5", + "yaml": "2.9.1" + }, + "devDependencies": { + "@astrojs/check": "0.9.10", + "@playwright/test": "1.63.0", + "@types/node": "22.20.3", + "prettier": "3.9.7", + "prettier-plugin-astro": "1.0.0", + "typescript": "6.0.3" + }, + "engines": { + "node": ">=22.22.0" + } + }, + "node_modules/@astrojs/astro2tsx": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@astrojs/astro2tsx/-/astro2tsx-0.1.2.tgz", + "integrity": "sha512-Uj/M9zbrhu5cIZEvWAGTbQJRKSxXWJ/E59BZwt7F+chrKhbfttW+9rkS/l+1QxoJP1KU3UnpM62M7yszfAI0WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/core": "1.11.3", + "@napi-rs/wasm-runtime": "1.2.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/check": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.10.tgz", + "integrity": "sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/language-server": "^2.16.7", + "chokidar": "^4.0.3", + "kleur": "^4.1.5", + "yargs": "^18.0.0" + }, + "bin": { + "astro-check": "bin/astro-check.js" + }, + "peerDependencies": { + "typescript": "^5.0.0 || ^6.0.0" + } + }, + "node_modules/@astrojs/compiler-binding": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.5.1.tgz", + "integrity": "sha512-oczyolLUl9mNrtuBuNjuvn5thpHrFLXAHAWGTRg0JDHQ8cGVsVeFekOu2uSoQGgainJIi+A3/9m0DyYwz3Nlnw==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@astrojs/compiler-binding-android-arm64": "0.5.1", + "@astrojs/compiler-binding-darwin-arm64": "0.5.1", + "@astrojs/compiler-binding-darwin-x64": "0.5.1", + "@astrojs/compiler-binding-linux-arm64-gnu": "0.5.1", + "@astrojs/compiler-binding-linux-arm64-musl": "0.5.1", + "@astrojs/compiler-binding-linux-x64-gnu": "0.5.1", + "@astrojs/compiler-binding-linux-x64-musl": "0.5.1", + "@astrojs/compiler-binding-wasm32-wasi": "0.5.1", + "@astrojs/compiler-binding-win32-arm64-msvc": "0.5.1", + "@astrojs/compiler-binding-win32-x64-msvc": "0.5.1" + } + }, + "node_modules/@astrojs/compiler-binding-android-arm64": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-android-arm64/-/compiler-binding-android-arm64-0.5.1.tgz", + "integrity": "sha512-nMb2nMIJtcWLNH+fV75Enmi+jp4tIOYOfsv4XtpgdOWkqc9P4uSSdK5u1xb340AzrXo5V18SxLl/nc3vcqtamQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-darwin-arm64": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.5.1.tgz", + "integrity": "sha512-5k4+nZ/WRt+dcVow4z7rBuI7iv9A58h/uQ91YJe1PrTivbcCkq3u2Ynx/AZJwII4CsZawBCeGpojkFjutZBhBQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-darwin-x64": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.5.1.tgz", + "integrity": "sha512-0Buw+YST9Ff2EqQyhRMQujuFq/dijLw3zXWhsvI0dJ2pGrT38iVoxTEca2eBX/PfhlwkVWSRJEKO5gMqKHyeHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-arm64-gnu": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.5.1.tgz", + "integrity": "sha512-x5LRpyCoMKuu3INr18cn42YhwSOaRpp9b5Ny3eJJ/sWVeejC16Zeshu1JlvlSmEZACLeWxU5JM0RX6T6nI+YyA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-arm64-musl": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.5.1.tgz", + "integrity": "sha512-mL7tQEuDk3rGfaKmrO63nw6WBy1TPVioQGmVyjcAr+C+a/k3SVMYBytNSMqH9mZPT/Swj30JaZ2N842Z1NnOEQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-x64-gnu": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.5.1.tgz", + "integrity": "sha512-uLGc9jtxwiDe4cQyhNPQim4ECiB/YzbWlXssfLAv8r374kArfTwBHjwR2+j+XVyF71yCx5mF9YNRclRqdNBgeg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-x64-musl": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.5.1.tgz", + "integrity": "sha512-G0cywinaQUc8v1MrytvYKUX7svTDMmYtOCZfxjwKPbJi59zIqM+Ll0rWgO0GQWLZH3XoPYo19ZwlP3Xt9ep9YA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-wasm32-wasi": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.5.1.tgz", + "integrity": "sha512-yMhLL8asmLydoAdnEZxAw6eLfNDs3YJl6jqn0pjPt6zrb2U5qIDLnii4/twThpIFODKkQ6YDpBRTOz/vSfZCxw==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.3", + "@emnapi/runtime": "1.11.3", + "@napi-rs/wasm-runtime": "^1.2.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-arm64-msvc": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.5.1.tgz", + "integrity": "sha512-aClIG6poFt3Wf/bZykrEAX2nAnKvBHW6RXiKUa69QjBtWH0/FOlOAjnFcxRNlhej2AMWNYFx0o+LGjwMcH8P7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-x64-msvc": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.5.1.tgz", + "integrity": "sha512-/E6uBCagY70YpWqssBC+/FuniNSloeQPRgj7l3LDWiqjxIyTLnIgX1sTt18FU7jrigetpSnC9cHAM6b6IxSgPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-rs": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.5.1.tgz", + "integrity": "sha512-1k1acnX7DepIiea9U9/Y+d1hdAFfylu1ZQQkkYgRSwBOBAsXInolyZuh/2tax+83vu/A+hOYP6P3+265RvMTvw==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler-binding": "0.5.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.11.0.tgz", + "integrity": "sha512-3rzxJ+xbo0+8YyqOzLziIN32wmsHdCjEVz2sGOpRxJ+Ben/KiLph4ItxBy1abEL+E8fkRzqjg0rfXmaHJGw9JA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.3.0", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" + } + }, + "node_modules/@astrojs/language-server": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.17.1.tgz", + "integrity": "sha512-gG7bvMVNIXgoSe1pM9dI1HsSDLU1EIc/dSAYQ0DuReLyLkOBjsiqvaRYgQltWaldLxh0bNEqjgreLfjd9OnUxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/astro2tsx": "^0.1.0", + "@astrojs/yaml2ts": "^0.2.4", + "@volar/kit": "~2.4.28", + "@volar/language-core": "~2.4.28", + "@volar/language-server": "~2.4.28", + "@volar/language-service": "~2.4.28", + "muggle-string": "^0.4.1", + "tinyglobby": "^0.2.17", + "volar-service-css": "0.0.71", + "volar-service-emmet": "0.0.71", + "volar-service-html": "0.0.71", + "volar-service-prettier": "0.0.71", + "volar-service-typescript": "0.0.71", + "volar-service-typescript-twoslash-queries": "0.0.71", + "volar-service-yaml": "0.0.71", + "vscode-html-languageservice": "^5.6.2", + "vscode-uri": "^3.2.0" + }, + "bin": { + "astro-ls": "bin/nodeServer.js" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "prettier-plugin-astro": ">=0.11.0" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + } + } + }, + "node_modules/@astrojs/markdown-satteri": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.4.2.tgz", + "integrity": "sha512-g4QehXnB/MzxQWnSf17NciFInMqEJy9948p8cc7zGg/r7995m0Ln5PMr3Xlnj24iey1LchaoZ3SzTOa3D4P/3w==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "satteri": "^0.10.3" + } + }, + "node_modules/@astrojs/prism": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.3.tgz", + "integrity": "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.4.0", + "dset": "^3.1.4", + "is-docker": "^4.0.0", + "package-manager-detector": "^1.6.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/yaml2ts": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.4.tgz", + "integrity": "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^2.8.3" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bruits/satteri-darwin-arm64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.10.5.tgz", + "integrity": "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@bruits/satteri-darwin-x64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.10.5.tgz", + "integrity": "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@bruits/satteri-linux-arm64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.10.5.tgz", + "integrity": "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-arm64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.10.5.tgz", + "integrity": "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-x64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.10.5.tgz", + "integrity": "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-x64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.10.5.tgz", + "integrity": "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-wasm32-wasi": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.10.5.tgz", + "integrity": "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-win32-arm64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.10.5.tgz", + "integrity": "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@bruits/satteri-win32-x64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.10.5.tgz", + "integrity": "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@clack/core": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", + "integrity": "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.1.tgz", + "integrity": "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.5.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@emmetio/abbreviation": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", + "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-abbreviation": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", + "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-parser": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.1.tgz", + "integrity": "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emmetio/stream-reader": "^2.2.0", + "@emmetio/stream-reader-utils": "^0.1.0" + } + }, + "node_modules/@emmetio/html-matcher": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", + "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@emmetio/scanner": "^1.0.0" + } + }, + "node_modules/@emmetio/scanner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz", + "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz", + "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz", + "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@prettier/parse-srcset": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@prettier/parse-srcset/-/parse-srcset-3.1.0.tgz", + "integrity": "sha512-FIRv2rZotO9NP/r66taYqD1zICpPiBbrECqjKV/xqNotqDxF7fLzzUeK+1RSRx9Tenk0DajR/GcRmTJ2qtHaKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@shikijs/core": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.5.0.tgz", + "integrity": "sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.5.0", + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.5.0.tgz", + "integrity": "sha512-Ed6UdEj84LyYkwnxM5CvSMWAD+id9ExTYri0/+x41U9zrgNifPSS9lZm0JqBP0VfYI8yr8tY5nbNq9+p9R0kFA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.5.0.tgz", + "integrity": "sha512-dKVmbJecVdB9recl8a/CU4jdpNXyjJQrIcesIkpS70s5HS02ohpnCSpMUbotPnEUzbAcLQ+S4hwZYbkBBiTcJw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.5.0.tgz", + "integrity": "sha512-wwBTqwduXSz0X4v9Qa2EWeFaYJEw1clCncWjoRNQRbVDu3SPRTRWucnouYapKj3b8Q+sk/41Vbc4B5c8nuYzLA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.5.0.tgz", + "integrity": "sha512-UM7ShlA8oz0OWxLLO8BmO9Ip6fVqdkt8yF0EH7nOCqHfrx1eEAzF0ZUVK7L+I8NEgeu1PEWSRlxaiPPeCb4iDg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.5.0.tgz", + "integrity": "sha512-zGb87H3UBbpuI4yoh5UguB1XbU+2BDU3kflM7UfZ+Cy2hWo9lBtqjSVK20gmLY6ukYl040hAypGXMgud9vul8Q==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.5.0.tgz", + "integrity": "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "22.20.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.3.tgz", + "integrity": "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "license": "ISC" + }, + "node_modules/@volar/kit": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz", + "integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-service": "2.4.28", + "@volar/typescript": "2.4.28", + "typesafe-path": "^0.2.2", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/language-server": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz", + "integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@volar/language-service": "2.4.28", + "@volar/typescript": "2.4.28", + "path-browserify": "^1.0.1", + "request-light": "^0.7.0", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/language-service": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.28.tgz", + "integrity": "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/emmet-helper": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", + "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "emmet": "^2.4.3", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/emmet-helper/node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-i18n": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz", + "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.0-beta.0" + } + }, + "node_modules/am-i-vibing": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/am-i-vibing/-/am-i-vibing-0.4.0.tgz", + "integrity": "sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==", + "license": "MIT", + "dependencies": { + "process-ancestry": "^0.1.0" + }, + "bin": { + "am-i-vibing": "dist/cli.mjs" + } + }, + "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/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/astro": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/astro/-/astro-7.3.5.tgz", + "integrity": "sha512-1p/ernaq/RuESbM5NMjWzbO6SiebMGrGcYlGjMfBhzzgM+n/X7AUmVyw8BqW2QYCdivpsHwJBcD1fqmlTZjZHg==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler-rs": "^0.5.0", + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/markdown-satteri": "0.4.2", + "@astrojs/telemetry": "3.3.3", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0", + "am-i-vibing": "^0.4.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^2.0.1", + "devalue": "^5.8.1", + "diff": "^9.0.0", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.28.0", + "find-proc": "0.2.0", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.3.2", + "jsonc-parser": "^3.3.1", + "magic-string": "^1.0.0", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^1.0.1", + "obug": "^3.0.0", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.4", + "shiki": "^4.0.2", + "smol-toml": "^1.8.0", + "svgo": "^4.1.0", + "tinyclip": "^1.0.1", + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.5", + "unstorage": "^1.17.5", + "verkit": "^0.4.0", + "vite": "^8.0.13", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.5.4" + }, + "bin": { + "astro": "bin/astro.mjs" + }, + "engines": { + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.35.4" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.4.tgz", + "integrity": "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/emmet": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", + "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "./packages/scanner", + "./packages/abbreviation", + "./packages/css-abbreviation", + "./" + ], + "dependencies": { + "@emmetio/abbreviation": "^2.3.3", + "@emmetio/css-abbreviation": "^2.1.8" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-proc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/find-proc/-/find-proc-0.2.0.tgz", + "integrity": "sha512-a6h2nTrgB3g5Wrn4au9Ux4kMk7IJtyF6oA8uGn+mk3weLZssUtc1cp6meHIzzuW2o1srrBAxzkmXLbqjRkgB6w==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "license": "BSD-2-Clause" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/magicast": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.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==", + "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-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==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "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==", + "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-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "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==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz", + "integrity": "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz", + "integrity": "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.3.tgz", + "integrity": "sha512-SKgVvDU5TNIxQ4r30U75BNLC6a+6GOEjngrk/ysM5+Zu3oJuk1SC5ApzIzY/7PjCEjoVPMWJ0zho/4QUiEB7TQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.9.0.tgz", + "integrity": "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==", + "license": "MIT" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.7.tgz", + "integrity": "sha512-T3mF5P7HFzZVLDQuCUNtJj6WK1pcpLMweMNUVwGb01bLNkH9AkKxtZvmInw8K6bnn2O3d6/5RHl6zSHiBAD0Og==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-astro": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-1.0.0.tgz", + "integrity": "sha512-xOlwKNSaPkPPiJeOhiFk8/MA2eH5A+in99dID+weTJhPcq+FBdIaB/3UNFdy9ckmc8/bT7QNyxSb78cnopgElQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/compiler-rs": "^0.4.0", + "@prettier/parse-srcset": "^3.1.0", + "sass-formatter": "^0.8.0" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "prettier": "^3.5.3" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.4.1.tgz", + "integrity": "sha512-ZYVDW1P58OXyxqZbOMp+/2U9KA7y2esIx9r4pWdpZ/l6fCZhaw4ul+D7EgpRVxYLkyTWqzANbTTUFGPFP8zDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@astrojs/compiler-binding-android-arm64": "0.4.1", + "@astrojs/compiler-binding-darwin-arm64": "0.4.1", + "@astrojs/compiler-binding-darwin-x64": "0.4.1", + "@astrojs/compiler-binding-linux-arm64-gnu": "0.4.1", + "@astrojs/compiler-binding-linux-arm64-musl": "0.4.1", + "@astrojs/compiler-binding-linux-x64-gnu": "0.4.1", + "@astrojs/compiler-binding-linux-x64-musl": "0.4.1", + "@astrojs/compiler-binding-wasm32-wasi": "0.4.1", + "@astrojs/compiler-binding-win32-arm64-msvc": "0.4.1", + "@astrojs/compiler-binding-win32-x64-msvc": "0.4.1" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-android-arm64": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-android-arm64/-/compiler-binding-android-arm64-0.4.1.tgz", + "integrity": "sha512-Wtx2DZORuNTLASLLZ8hlrjFaJe1LUloatcCn9EPq7knBhRpjMGXeJANIJMeBZG4igvz7x+3nyAJM2iwCSW9xog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-darwin-arm64": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.4.1.tgz", + "integrity": "sha512-7iwXcU+hB60Y1SvOfnWHNioT72gDsDQKaDipXh2Onwp/TvQMozhbvhy13wJR8Af940a0h6PbddCeJFpj791UDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-darwin-x64": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.4.1.tgz", + "integrity": "sha512-pifjICos49RXhSVKSDu5gDB0/8j0O7DmxSp1DaWW2PyLG5ekwSkcrfOZto5JqhdI7QYMZB3XOiXpsdOXpS4npA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-linux-arm64-gnu": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.4.1.tgz", + "integrity": "sha512-hsZMlNWc6LqWHh9LlSwTq1/XCY7pxA5rkkKIdZRV8mgbOz9OwLvjEoe5Jry13eIZjMUQO8XnQyzk8o/Pjw2YFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-linux-arm64-musl": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.4.1.tgz", + "integrity": "sha512-a3EW27f+Z9uf57vYmpLgUriA1y9wDhKz57PT8D8XlFZt2HZEWhUxKuxI1jvKVI9p8okYee11WXEm/FHY1G9yoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-linux-x64-gnu": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.4.1.tgz", + "integrity": "sha512-XruPTKB/SS4DFzeAjQ1ZyM0ieHDjapHkOSnnzRApZ4FXY2YAI7UY3mETjfJiIS/acyZyrnPH5jObv1gATTdUJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-linux-x64-musl": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.4.1.tgz", + "integrity": "sha512-E8j7lGanqNpudtkUbsFaOLJhmwFe4Ecqux7K9hkj4wULIPNZRM2bYZNcMSj8r1oWvaUiwZl8LaaNwv3+14jqhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-wasm32-wasi": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.4.1.tgz", + "integrity": "sha512-vNLxIv41ZE+nmPFtw24cRW5ZXG+ZPcAGtgigtjCP8lvcxKI7u2GrXoMsCgEH5fMw8WCEJpwLvsVBg15rJKAkyA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.2.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-win32-arm64-msvc": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.4.1.tgz", + "integrity": "sha512-/xTDgRsFDoTh0/+KFXJ74DY4Jqn8CoSHwa8UP5RAblHfnRHkenwfSl4zYDET61O9vXKLk09UzHjjq09yP20/AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-binding-win32-x64-msvc": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.4.1.tgz", + "integrity": "sha512-0x5J6iHZO0Fjo/CVzoIbXKzWnGESftDbEUQ/oXQ61HhUAFoR7+T1jfToZSNAS+gYyBXHSTpMWYC7lzarbhClcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler-rs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.4.1.tgz", + "integrity": "sha512-//NmAuRhy7eU9iP0ceI5Pymy55M+nUZ//kS6XeOyhBWdDib2KbJOyFaVJ52k7sDkJwTKVox7oPu3SpYiVnGm5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/compiler-binding": "0.4.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-ancestry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/process-ancestry/-/process-ancestry-0.1.0.tgz", + "integrity": "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/request-light": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz", + "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rolldown": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.152.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" + } + }, + "node_modules/s.color": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz", + "integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass-formatter": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.8.0.tgz", + "integrity": "sha512-Nf4eMGE2O5vnJG7KPCRyksWS9gVKSYoUWtPeMXRRxwZSPVQhSGtL602xVYS65LvMkHmkvKa7xpauF0+/5pPwrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "suf-log": "^2.5.3" + }, + "bin": { + "sass-formatter": "dist/cli.js" + } + }, + "node_modules/satteri": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.10.5.tgz", + "integrity": "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.5", + "@types/hast": "^3.0.5", + "@types/mdast": "^4.0.4", + "@types/unist": "^3.0.3" + }, + "optionalDependencies": { + "@bruits/satteri-darwin-arm64": "0.10.5", + "@bruits/satteri-darwin-x64": "0.10.5", + "@bruits/satteri-linux-arm64-gnu": "0.10.5", + "@bruits/satteri-linux-arm64-musl": "0.10.5", + "@bruits/satteri-linux-x64-gnu": "0.10.5", + "@bruits/satteri-linux-x64-musl": "0.10.5", + "@bruits/satteri-wasm32-wasi": "0.10.5", + "@bruits/satteri-win32-arm64-msvc": "0.10.5", + "@bruits/satteri-win32-x64-msvc": "0.10.5" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shiki": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.5.0.tgz", + "integrity": "sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.5.0", + "@shikijs/engine-javascript": "4.5.0", + "@shikijs/engine-oniguruma": "4.5.0", + "@shikijs/langs": "4.5.0", + "@shikijs/themes": "4.5.0", + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz", + "integrity": "sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/suf-log": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz", + "integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==", + "dev": true, + "license": "MIT", + "dependencies": { + "s.color": "0.0.15" + } + }, + "node_modules/svgo": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz", + "integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^6.0.0", + "css-tree": "^3.0.1", + "css-what": "^7.0.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "1.6.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-1.0.3.tgz", + "integrity": "sha512-nTv2raKFMzGG6JJp0lpNDsRPdfFE6UHlu2rVEjXo8vkx1ZS3LW2CVXqIaLu8xZlu+6tbFs9V58Qc/Jmk/Ii7wA==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "devOptional": true, + "license": "0BSD" + }, + "node_modules/typesafe-path": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", + "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-auto-import-cache": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz", + "integrity": "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.8" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz", + "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.5.tgz", + "integrity": "sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ohash": "^2.0.11", + "undici": "^8.0.0" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/unstorage/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/unstorage/node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/verkit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.4.1.tgz", + "integrity": "sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/volar-service-css": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.71.tgz", + "integrity": "sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-css-languageservice": "^6.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-emmet": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.71.tgz", + "integrity": "sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emmetio/css-parser": "^0.4.1", + "@emmetio/html-matcher": "^1.3.0", + "@vscode/emmet-helper": "^2.9.3", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-html": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.71.tgz", + "integrity": "sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-html-languageservice": "^5.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-prettier": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.71.tgz", + "integrity": "sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0", + "prettier": "^2.2 || ^3.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + }, + "prettier": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.71.tgz", + "integrity": "sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-browserify": "^1.0.1", + "semver": "^7.6.2", + "typescript-auto-import-cache": "^0.3.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-nls": "^5.2.0", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript-twoslash-queries": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.71.tgz", + "integrity": "sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-yaml": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.71.tgz", + "integrity": "sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8", + "yaml-language-server": "~1.23.0" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/vscode-css-languageservice": { + "version": "6.3.10", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz", + "integrity": "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "^3.1.0" + } + }, + "node_modules/vscode-css-languageservice/node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-html-languageservice": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.6.2.tgz", + "integrity": "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.1.0" + } + }, + "node_modules/vscode-json-languageservice": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz", + "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + }, + "engines": { + "npm": ">=7.0.0" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.3.tgz", + "integrity": "sha512-qETYf9PCl1lVfwR+oBmMfc4vP2nqLNA35ABxorw3j06+xlKRgDVxbLgKm4Y/xPL0YWdeDjLoI4ImICIT3nB0zQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.4.tgz", + "integrity": "sha512-CCXbyr99dafRJzJH/0Ryk7ly5T8XPmSR0//OuF2w4m+9JiOin7zcAT5iET8GRE7Z9taFBejb3GbM0a0mbjdbrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "9.0.3", + "vscode-languageserver-types": "3.18.4" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.15.tgz", + "integrity": "sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.4.tgz", + "integrity": "sha512-nQsacoiijvl0fRf2q28uFDrAVwRhdCa3QveiF7/CX0r18mLR5FU/1WskSsCKOjmEbtteKUnY8x8Yzxop+k0zwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-languageserver/node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver/node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver/node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-nls": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", + "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yaml-language-server": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.23.0.tgz", + "integrity": "sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "ajv-i18n": "^4.2.0", + "prettier": "^3.8.1", + "request-light": "^0.5.7", + "vscode-json-languageservice": "4.1.8", + "vscode-languageserver": "^9.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-uri": "^3.0.2", + "yaml": "2.8.3" + }, + "bin": { + "yaml-language-server": "bin/yaml-language-server" + } + }, + "node_modules/yaml-language-server/node_modules/request-light": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", + "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 000000000..645485e6c --- /dev/null +++ b/website/package.json @@ -0,0 +1,32 @@ +{ + "name": "lolbas-website", + "private": true, + "version": "1.0.0", + "type": "module", + "engines": { + "node": ">=22.22.0" + }, + "scripts": { + "prepare:data": "node scripts/prepare-data.mjs", + "dev": "npm run prepare:data && astro dev --host 127.0.0.1", + "check": "npm run prepare:data && astro check", + "build": "npm run prepare:data && astro build && node scripts/verify-build.mjs", + "preview": "astro preview --host 127.0.0.1", + "test:data": "node --test tests/data*.test.mjs tests/release.test.mjs", + "test": "playwright test", + "format": "prettier --write src scripts tests *.mjs *.ts *.json", + "format:check": "prettier --check src scripts tests *.mjs *.ts *.json" + }, + "dependencies": { + "astro": "7.3.5", + "yaml": "2.9.1" + }, + "devDependencies": { + "@astrojs/check": "0.9.10", + "@playwright/test": "1.63.0", + "@types/node": "22.20.3", + "prettier": "3.9.7", + "prettier-plugin-astro": "1.0.0", + "typescript": "6.0.3" + } +} diff --git a/website/playwright.config.ts b/website/playwright.config.ts new file mode 100644 index 000000000..7eddac255 --- /dev/null +++ b/website/playwright.config.ts @@ -0,0 +1,15 @@ +import { defineConfig, devices } from '@playwright/test'; +export default defineConfig({ + testDir: './tests', + testMatch: process.env.SITE_BASE ? '**/preview.spec.ts' : '**/site.spec.ts', + fullyParallel: true, + retries: process.env.CI ? 1 : 0, + reporter: [['list'], ['html', { open: 'never' }]], + use: { baseURL: 'http://127.0.0.1:4388', trace: 'retain-on-failure' }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], + webServer: { + command: 'npm run preview -- --port 4388 --ignore-lock', + url: `http://127.0.0.1:4388${process.env.SITE_BASE || ''}/`, + reuseExistingServer: false, + }, +}); diff --git a/website/public/THIRD_PARTY_NOTICES.txt b/website/public/THIRD_PARTY_NOTICES.txt new file mode 100644 index 000000000..5c03b2f20 --- /dev/null +++ b/website/public/THIRD_PARTY_NOTICES.txt @@ -0,0 +1,26 @@ +Third-party notices + +The GitHub mark comes from GitHub Octicons: +https://github.com/primer/octicons/blob/main/icons/mark-github-16.svg + +MIT License + +Copyright (c) 2026 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/website/public/favicon.png b/website/public/favicon.png new file mode 100644 index 000000000..c42cc7a65 Binary files /dev/null and b/website/public/favicon.png differ diff --git a/website/public/logo.png b/website/public/logo.png new file mode 100644 index 000000000..a2e8321a6 Binary files /dev/null and b/website/public/logo.png differ diff --git a/website/scripts/prepare-data.mjs b/website/scripts/prepare-data.mjs new file mode 100644 index 000000000..cf2b0658c --- /dev/null +++ b/website/scripts/prepare-data.mjs @@ -0,0 +1,322 @@ +import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve, join } from 'node:path'; +import { parseDocument } from 'yaml'; + +export const websiteRoot = resolve( + dirname(fileURLToPath(import.meta.url)), + '..', +); +export const sourceRoot = resolve(websiteRoot, '../yml'); +export const canonicalOrigin = 'https://lolbas-project.github.io'; +export const collections = { + OSBinaries: 'Binaries', + OSLibraries: 'Libraries', + OSScripts: 'Scripts', + OtherMSBinaries: 'OtherMSBinaries', +}; +export const apiKeys = [ + 'Name', + 'Description', + 'Author', + 'Created', + 'Commands', + 'Full_Path', + 'Detection', + 'Resources', + 'url', +]; +export const csvHeader = [ + 'Filename', + 'Description', + 'Author', + 'Date', + 'Command', + 'Command Description', + 'Command Usecase', + 'Command Category', + 'Command Privileges', + 'MITRE ATT&CK technique', + 'Operating System', + 'Paths', + 'Detections', + 'Resources', + 'Acknowledgements', + 'URL', + 'Tags', +]; +const object = (value) => + value !== null && typeof value === 'object' && !Array.isArray(value); +const fail = (source, message) => { + throw new Error(`${source}: ${message}`); +}; + +export function parseEntry(text, source, type, id) { + const document = parseDocument(text, { uniqueKeys: true, schema: 'core' }); + if (document.errors.length) + fail(source, document.errors.map((error) => error.message).join('; ')); + const entry = document.toJS({ maxAliasCount: 100 }); + if (!object(entry)) fail(source, 'expected a YAML mapping'); + for (const key of ['Name', 'Description', 'Author', 'Created']) { + if (typeof entry[key] !== 'string' || !entry[key].trim()) + fail(source, `${key} must be a non-empty string`); + } + const createdTime = Date.parse(`${entry.Created}T00:00:00Z`); + if ( + !/^\d{4}-\d{2}-\d{2}$/.test(entry.Created) || + !Number.isFinite(createdTime) || + new Date(createdTime).toISOString().slice(0, 10) !== entry.Created + ) + fail(source, 'Created must be a valid calendar date in YYYY-MM-DD format'); + if (!Array.isArray(entry.Commands) || !entry.Commands.length) + fail(source, 'Commands must be a non-empty array'); + entry.Commands.forEach((command, i) => { + if (!object(command)) fail(source, `Commands[${i}] must be a mapping`); + for (const key of [ + 'Command', + 'Description', + 'Usecase', + 'Category', + 'Privileges', + 'MitreID', + 'OperatingSystem', + ]) { + if (typeof command[key] !== 'string' || !command[key].trim()) + fail(source, `Commands[${i}].${key} must be a non-empty string`); + } + if (!/^T\d{4}(\.\d{3})?$/.test(command.MitreID)) + fail(source, `Commands[${i}].MitreID must be an ATT&CK technique ID`); + if ( + command.Tags != null && + (!Array.isArray(command.Tags) || + command.Tags.some( + (tag) => + !object(tag) || + Object.values(tag).some((value) => typeof value !== 'string'), + )) + ) + fail(source, `Commands[${i}].Tags must be an array of string mappings`); + }); + for (const key of [ + 'Full_Path', + 'Detection', + 'Resources', + 'Acknowledgement', + ]) { + if ( + entry[key] != null && + (!Array.isArray(entry[key]) || + entry[key].some( + (item) => + !object(item) || + Object.values(item).some( + (value) => value != null && typeof value !== 'string', + ), + )) + ) + fail(source, `${key} must be an array of string mappings`); + } + if (!/^[A-Za-z0-9_.-]+$/.test(id)) + fail(source, 'filename must be safe for a legacy URL'); + return { ...entry, id, type, url: `/lolbas/${type}/${id}/`, source }; +} + +export async function loadCatalog(root = sourceRoot) { + const records = []; + const routes = new Set(); + const binaryNames = new Map(); + const folders = new Map(); + // Match the canonical filename gates, including unpublished collections. + // Inspect actual directories only; do not follow symlinks outside the source. + const checkExtension = (source) => { + if (!source.endsWith('.yml')) + fail(source, "unexpected extension; use '.yml' (all lower case)"); + }; + const checkTree = async (directory, relative) => { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const source = `${relative}/${entry.name}`; + if (entry.isFile()) checkExtension(source); + if (entry.isDirectory()) + await checkTree(join(directory, entry.name), source); + } + }; + await checkTree(root, 'yml'); + for (const entry of await readdir(root, { withFileTypes: true })) { + if (entry.isFile()) checkExtension(`yml/${entry.name}`); + if (!entry.isDirectory()) continue; + const children = await readdir(join(root, entry.name), { + withFileTypes: true, + }); + if (Object.hasOwn(collections, entry.name)) { + const nested = children.find((child) => child.isDirectory()); + if (nested) + fail( + `yml/${entry.name}/${nested.name}`, + 'nested directories are not supported in published collections; keep entries directly in the collection', + ); + } + const files = children + .filter((file) => file.isFile()) + .map((file) => file.name) + .sort(); + folders.set(entry.name, files); + for (const file of files) { + const source = `yml/${entry.name}/${file}`; + checkExtension(source); + if (entry.name === 'OSBinaries' || entry.name === 'OtherMSBinaries') { + const key = file.toLowerCase(); + if (binaryNames.has(key)) + fail( + source, + `duplicate binary filename (case insensitive): ${binaryNames.get(key)}`, + ); + binaryNames.set(key, source); + } + } + } + for (const [folder, type] of Object.entries(collections)) { + const files = folders.get(folder); + if (!files) fail(`yml/${folder}`, 'missing canonical collection directory'); + for (const file of files) { + const record = parseEntry( + await readFile(join(root, folder, file), 'utf8'), + `yml/${folder}/${file}`, + type, + file.slice(0, -4), + ); + const key = record.url.toLowerCase(); + if (routes.has(key)) fail(record.source, 'duplicate legacy route'); + routes.add(key); + records.push(record); + } + } + return records.sort( + (a, b) => + a.Name.localeCompare(b.Name, 'en') || a.url.localeCompare(b.url, 'en'), + ); +} + +export const toApi = (records) => + records.map((record) => + Object.fromEntries( + apiKeys.map((key) => [ + key, + key === 'url' ? canonicalOrigin + record.url : (record[key] ?? null), + ]), + ), + ); +const csvCell = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`; +const joinField = (items, key) => + (items ?? []).map((item) => item[key] ?? '').join(', '); +export function toCsv(records) { + const rows = records.flatMap((record) => + record.Commands.map((command) => + [ + record.Name, + record.Description, + record.Author, + record.Created, + command.Command, + command.Description, + command.Usecase, + command.Category, + command.Privileges, + command.MitreID, + command.OperatingSystem, + joinField(record.Full_Path, 'Path'), + (record.Detection ?? []) + .map((item) => { + const [key, value] = Object.entries(item)[0] ?? ['', '']; + return `${key}: ${value ?? ''}`; + }) + .join(', '), + joinField(record.Resources, 'Link'), + (record.Acknowledgement ?? []) + .map( + (item) => + `${item.Person ?? ''}${item.Handle ? ` (${item.Handle})` : ''}`, + ) + .join(', '), + canonicalOrigin + record.url, + (command.Tags ?? []) + .flatMap((tag) => + Object.entries(tag).map(([key, value]) => `${key}:${value}`), + ) + .join(','), + ] + .map(csvCell) + .join(','), + ), + ); + return `${csvHeader.join(',')}\n${rows.join('\n')}\n`; +} + +export function toNavigator(records) { + const counts = new Map(); + for (const record of records) + for (const command of record.Commands) + counts.set(command.MitreID, (counts.get(command.MitreID) ?? 0) + 1); + const parents = new Set( + [...counts.keys()] + .filter((id) => id.includes('.')) + .map((id) => id.split('.')[0]), + ); + return { + name: 'The LOLBAS Project', + versions: { attack: '19', navigator: '5.3.2', layer: '4.5' }, + domain: 'enterprise-attack', + description: + 'ATT&CK alignment of the LOLBAS project, see https://lolbas-project.github.io/.', + filters: { platforms: ['Windows'] }, + layout: { + layout: 'flat', + aggregateFunction: 'average', + showID: false, + showName: true, + showAggregateScores: false, + countUnscored: false, + expandedSubtechniques: 'none', + }, + techniques: [...counts] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([techniqueID, score]) => ({ + techniqueID, + score, + ...(parents.has(techniqueID) ? { showSubtechniques: true } : {}), + })), + gradient: { + colors: ['#cdf0fc', '#0d89b3'], + minValue: 0, + maxValue: Math.max(0, ...counts.values()), + }, + selectTechniquesAcrossTactics: true, + selectSubtechniquesWithParent: false, + selectVisibleTechniques: false, + }; +} +export const toBadge = (count) => + `LOLBAS: ${count}LOLBAS${count}\n`; + +export async function prepareData() { + const records = await loadCatalog(); + const json = (value) => `${JSON.stringify(value, null, 2)}\n`; + const outputs = { + 'src/data/catalog.json': json(records), + 'public/api/lolbas.json': json(toApi(records)), + 'public/api/lolbas.csv': toCsv(records), + 'public/mitre_attack_navigator_layer.json': json(toNavigator(records)), + 'public/assets/lolbas-count.svg': toBadge(records.length), + }; + for (const [path, content] of Object.entries(outputs)) { + await mkdir(dirname(join(websiteRoot, path)), { recursive: true }); + await writeFile(join(websiteRoot, path), content); + } + console.log( + `Prepared ${records.length} LOLBAS entries and ${records.reduce((n, entry) => n + entry.Commands.length, 0)} commands from local YAML.`, + ); +} +if ( + process.argv[1] && + resolve(process.argv[1]) === fileURLToPath(import.meta.url) +) + await prepareData(); diff --git a/website/scripts/verify-build.mjs b/website/scripts/verify-build.mjs new file mode 100644 index 000000000..452e587d4 --- /dev/null +++ b/website/scripts/verify-build.mjs @@ -0,0 +1,47 @@ +import assert from 'node:assert/strict'; +import { readFile, access } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { + loadCatalog, + toApi, + toCsv, + toNavigator, + toBadge, + websiteRoot, +} from './prepare-data.mjs'; + +const dist = resolve(websiteRoot, process.argv[2] ?? 'dist'); +const catalog = await loadCatalog(); +const json = async (path) => + JSON.parse(await readFile(join(dist, path), 'utf8')); +assert.deepEqual( + await json('api/lolbas.json'), + toApi(catalog), + 'JSON API must preserve legacy fields and all canonical entries', +); +assert.equal( + await readFile(join(dist, 'api/lolbas.csv'), 'utf8'), + toCsv(catalog), + 'CSV must include each command with the legacy 17-column representation', +); +assert.deepEqual( + await json('mitre_attack_navigator_layer.json'), + toNavigator(catalog), + 'Navigator must aggregate all command techniques', +); +assert.equal( + await readFile(join(dist, 'assets/lolbas-count.svg'), 'utf8'), + toBadge(catalog.length), +); +await access(join(dist, 'index.html')); +for (const entry of catalog) { + const html = await readFile(join(dist, entry.url, 'index.html'), 'utf8'); + assert.ok(html.includes(entry.Name), `${entry.url} must show ${entry.Name}`); + assert.ok( + html.includes(entry.source), + `${entry.url} must link its canonical source`, + ); +} +console.log( + `Verified ${catalog.length} legacy detail routes, JSON API, CSV command rows, Navigator and count badge in ${dist}.`, +); diff --git a/website/src/components/Explorer.astro b/website/src/components/Explorer.astro new file mode 100644 index 000000000..1b284316d --- /dev/null +++ b/website/src/components/Explorer.astro @@ -0,0 +1,344 @@ +--- +import { sitePath } from '../lib/site'; +import '../styles/filters.css'; +import { catalog, typeLabels, categories, techniques } from '../lib/catalog'; +const sorted = [...catalog].sort((a, b) => a.Name.localeCompare(b.Name)); +--- + +
+
+
+

Catalog

+

Trusted Windows tools. Unexpected possibilities.

+
+ + Use the API ↗ + +
+
+ +
+ + {catalog.length} entries to explore + + + Try{' '} + {' '} + or{' '} + + + +
+
+ + + + + + + + + + + + + {sorted.map((e, i) => ( + c.Category))] + .sort() + .join(', ')} + data-attack={[...new Set(e.Commands.map((c) => c.MitreID))] + .sort((a, b) => a.localeCompare(b, 'en', { numeric: true })) + .join(', ')} + data-created={e.Created} + data-commands={e.Commands.length} + data-detections={ + e.Detection?.filter((d) => + Object.keys(d).some((k) => k !== 'IOC'), + ).length || 0 + } + data-categories={JSON.stringify([ + ...new Set(e.Commands.map((c) => c.Category)), + ])} + data-techniques={JSON.stringify([ + ...new Set(e.Commands.map((c) => c.MitreID)), + ])} + data-detection={ + e.Detection?.some((d) => + Object.keys(d).some((k) => k !== 'IOC'), + ) + ? 'yes' + : 'no' + } + data-search={[ + e.Name, + e.Description, + e.Author, + ...(e.Aliases ?? []).map((a) => a.Alias), + ...e.Commands.flatMap((c) => [ + c.Command, + c.Description, + c.Usecase, + c.Category, + c.Privileges, + c.MitreID, + c.OperatingSystem, + ...(c.Tags ?? []).flatMap((t) => Object.values(t)), + ]), + ...(e.Full_Path ?? []).map((p) => p.Path), + ] + .join(' ') + .toLowerCase()} + > + + + + + + + + ))} + +
+ + + + + + + + + + + Open +
+ + + + {e.Name} + {e.Description} + + + +
+ {[...new Set(e.Commands.map((c) => c.Category))].map((c) => ( + + {c} + + ))} +
+
+ {typeLabels[e.type]} + +
+ {[...new Set(e.Commands.map((c) => c.MitreID))].map((t) => ( + {t} + ))} +
+
+ + Object.keys(d).some((k) => k !== 'IOC'), + ), + }, + ]} + > + + {e.Detection?.filter((d) => + Object.keys(d).some((k) => k !== 'IOC'), + ).length || '—'} + + + + ↗ + +
+
+ + + +
+
+ diff --git a/website/src/components/GitHubIcon.astro b/website/src/components/GitHubIcon.astro new file mode 100644 index 000000000..5ae4e3244 --- /dev/null +++ b/website/src/components/GitHubIcon.astro @@ -0,0 +1,11 @@ + diff --git a/website/src/components/Growth.astro b/website/src/components/Growth.astro new file mode 100644 index 000000000..52df71d2a --- /dev/null +++ b/website/src/components/Growth.astro @@ -0,0 +1,121 @@ +--- +import { sitePath } from '../lib/site'; +import { catalog } from '../lib/catalog'; +const years = [...new Set(catalog.map((e) => e.Created.slice(0, 4)))].sort(); +let total = 0; +const points = years.map((year) => { + const added = catalog.filter((e) => e.Created.startsWith(year)).length; + total += added; + return { year, added, total }; +}); +const coords = points.map( + (p, i) => + `${35 + (i * 550) / Math.max(points.length - 1, 1)},${112 - (p.total / catalog.length) * 90}`, +); +const path = 'M' + coords.join(' L'); +--- + +
+
+
+ THE KNOWLEDGE KEEPS GROWING +

A growing body of research.

+
+ + ↗ + +
+
+ {catalog.length} + + documented entries +
+ since {years[0]} +
+ + Cumulative entries + +
+ + + + + + + + {[22, 67, 112].map((y) => ( + + ))} + + + {points.map((p, i) => ( + + + + {p.year}: {p.total} total, {p.added} added + + + {p.year} + + + ))} + +

+ Based on entry creation dates. Catalog growth, not threat activity. +

+
+ View chart data + + + + + + + + + + {points.map((p) => ( + + + + + + ))} + +
YearAddedTotal
+ {p.year} + {p.added}{p.total}
+
+
diff --git a/website/src/layouts/Base.astro b/website/src/layouts/Base.astro new file mode 100644 index 000000000..5b6305e8a --- /dev/null +++ b/website/src/layouts/Base.astro @@ -0,0 +1,107 @@ +--- +import { sitePath } from '../lib/site'; +import GitHubIcon from '../components/GitHubIcon.astro'; +import '../styles/global.css'; +interface Props { + title?: string; + description?: string; +} +const { + title = 'LOLBAS · Living Off The Land Binaries, Scripts and Libraries', + description = 'Explore Windows binaries, scripts and libraries used in living off the land techniques. Search commands, ATT&CK mappings and detection resources.', +} = Astro.props; +--- + + + + + + + + + {title} + + + + + + + +
+ + + LOLBAS. + + + + GitHub + +
+
+ +
+ + + + diff --git a/website/src/lib/catalog.ts b/website/src/lib/catalog.ts new file mode 100644 index 000000000..ff67acb07 --- /dev/null +++ b/website/src/lib/catalog.ts @@ -0,0 +1,51 @@ +import data from '../data/catalog.json'; + +export interface Command { + Command: string; + Description: string; + Usecase: string; + Category: string; + Privileges: string; + MitreID: string; + OperatingSystem: string; + Tags?: Record[]; +} +export type CatalogType = + 'Binaries' | 'Libraries' | 'Scripts' | 'OtherMSBinaries'; +export interface CatalogEntry { + id: string; + type: CatalogType; + url: string; + source: string; + Name: string; + Description: string; + Aliases?: { Alias: string }[] | null; + Author: string; + Created: string; + Commands: Command[]; + Full_Path?: { Path: string }[] | null; + Detection?: Record[] | null; + Resources?: { Link: string }[] | null; + Acknowledgement?: { Person: string; Handle?: string | null }[] | null; +} +export const catalog = data as CatalogEntry[]; +export const typeLabels: Record = { + Binaries: 'OS Binaries', + Libraries: 'OS Libraries', + Scripts: 'OS Scripts', + OtherMSBinaries: 'Other Microsoft Binaries', +}; +export const categories = [ + ...new Set( + catalog.flatMap((entry) => + entry.Commands.map((command) => command.Category), + ), + ), +].sort(); +export const techniques = [ + ...new Set( + catalog.flatMap((entry) => + entry.Commands.map((command) => command.MitreID), + ), + ), +].sort(); diff --git a/website/src/lib/site.ts b/website/src/lib/site.ts new file mode 100644 index 000000000..6dd035fa1 --- /dev/null +++ b/website/src/lib/site.ts @@ -0,0 +1,3 @@ +// UI links support a fork's project Pages path; exported API URLs stay canonical. +export const sitePath = (path: string) => + `${import.meta.env.BASE_URL.replace(/\/$/, '')}${path}`; diff --git a/website/src/pages/404.astro b/website/src/pages/404.astro new file mode 100644 index 000000000..44fafd15f --- /dev/null +++ b/website/src/pages/404.astro @@ -0,0 +1,20 @@ +--- +import { sitePath } from '../lib/site'; +import Base from '../layouts/Base.astro'; +--- + + +
+

404 / NO LEAD HERE

+

+ Back to the hunt. +

+

+ This page doesn’t exist. Search the catalog to find a binary, script, or + library. +

+ + Explore the catalog → + +
+ diff --git a/website/src/pages/about.astro b/website/src/pages/about.astro new file mode 100644 index 000000000..9c921112d --- /dev/null +++ b/website/src/pages/about.astro @@ -0,0 +1,106 @@ +--- +import Base from '../layouts/Base.astro'; +// Maintainer names and handles are published in the project's README. +const maintainers = [ + { name: 'Oddvar Moe', handle: 'oddvarmoe' }, + { name: 'Jimmy Bayne', handle: 'bohops' }, + { name: 'Conor Richard', handle: 'xenosCR' }, + { name: 'Chris “Lopi” Spehn', handle: 'ConsciousHacker' }, + { name: 'Liam', handle: 'liamsomerville' }, + { name: 'Wietze', handle: 'Wietze' }, + { name: 'Jose Hernandez', handle: '_josehelps' }, +]; +--- + + +
+

+ Nothing to LOL about. +

+

+ Living Off The Land Binaries, Scripts and Libraries. A community effort to + document the unexpected capabilities of trusted Windows tools. +

+
+
+
+

Project maintainers

+

The people who keep LOLBAS moving. Find them on X.

+ +
+

The goal

+

+ Document every Microsoft-signed binary, script, and library that can be + used for living off the land techniques. Legitimate tools can execute + code, transfer files, bypass application allowlisting, access credentials, + and perform other actions beyond their expected use. +

+

What belongs in LOLBAS?

+
    +
  • + A Microsoft-signed file, native to Windows or available from Microsoft. +
  • +
  • + Unexpected functionality, with an exception for application allowlisting + bypasses. +
  • +
  • + Functionality useful in adversary simulation or to an advanced threat + actor. +
  • +
+

+ Read the{' '} + + full criteria + {' '} + before submitting a finding. +

+

Built by the community

+

+ The phrase “living off the land” was coined by Christopher Campbell and + Matt Graeber at DerbyCon 3. LOLBAS grew from community research into a + shared catalog maintained by volunteers. +

+

The original project logos were created by Adam Nadrowski.

+

Add your finding

+

+ Start with the{' '} + + contribution guide + {' '} + and{' '} + + YAML template + + . The original source format remains unchanged. +

+ + Explore the repository ↗ + +

Keep exploring

+

+ For Unix binaries, visit{' '} + GTFOBins. For Windows drivers, + visit LOLDrivers. +

+

+ + License & attribution + {' '} + ·{' '} + + Report an issue + +

+
+ diff --git a/website/src/pages/api.astro b/website/src/pages/api.astro new file mode 100644 index 000000000..e6e8231f2 --- /dev/null +++ b/website/src/pages/api.astro @@ -0,0 +1,109 @@ +--- +import { sitePath } from '../lib/site'; +import Base from '../layouts/Base.astro'; +import { catalog } from '../lib/catalog'; +--- + + +
+

OPEN DATA. NO KEYS REQUIRED.

+

+ Built for your workflow. +

+

+ The same LOLBAS data, ready for your tools. Access all {catalog.length}{' '} + entries as JSON, CSV, or the original YAML. +

+
+
+
+ 01 / JSON +

Every entry. One file.

+

+ The JSON API keeps the existing URL and field structure. Each record + includes its name, description, author, creation date, commands, full + paths, detections, resources, and canonical page URL. Optional top-level + fields retain null values when absent. +

+ + /api/lolbas.json ↗ + +
+
curl https://lolbas-project.github.io/api/lolbas.json
+ +
+
+
+ 02 / CSV +

Ready for a spreadsheet.

+

+ One row per command example, with the existing 17 columns: filename, + entry metadata, command metadata, ATT&CK technique, operating + systems, paths, detections, resources, acknowledgements, URL, and tags. +

+ + /api/lolbas.csv ↗ + +
+
curl -o lolbas.csv https://lolbas-project.github.io/api/lolbas.csv
+ +
+
+
+ 03 / SOURCE YAML +

Straight from the source.

+

+ YAML files in the project repository remain the source of truth. Browse + the four published collections, review changes, and contribute through + GitHub. +

+ + Browse source files ↗ + +

+ + Example: Certutil.yml ↗ + +

+
+

ATT&CK Navigator

+

+ Download the{' '} + + Navigator layer + {' '} + to explore command mappings. Scores represent the number of command + examples mapped to each technique. +

+

+ Data is generated at build time from the repository. No API key, cookies, + or account needed. See the project{' '} + + licensing notice + {' '} + for attribution and reuse. +

+
+ + diff --git a/website/src/pages/functions.astro b/website/src/pages/functions.astro new file mode 100644 index 000000000..07ee92c02 --- /dev/null +++ b/website/src/pages/functions.astro @@ -0,0 +1,26 @@ +--- +import { sitePath } from '../lib/site'; +import Base from '../layouts/Base.astro'; +import { categories } from '../lib/catalog'; +--- + + +
+

+ Unexpected capabilities. +

+

Explore the functions documented in the catalog.

+
+ + diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro new file mode 100644 index 000000000..885ebc965 --- /dev/null +++ b/website/src/pages/index.astro @@ -0,0 +1,139 @@ +--- +import { sitePath } from '../lib/site'; +import Base from '../layouts/Base.astro'; +import Growth from '../components/Growth.astro'; +import Explorer from '../components/Explorer.astro'; +import { catalog, techniques, categories } from '../lib/catalog'; +const commands = catalog.reduce((sum, e) => sum + e.Commands.length, 0); +const recent = [...catalog] + .sort((a, b) => b.Created.localeCompare(a.Created)) + .slice(0, 3); +--- + + +
+
+

+ Living Off The Land +
+ Binaries, Scripts and Libraries +

+

+ The tools are legitimate. The intent isn’t always. +
+ Explore the Windows binaries, scripts, and libraries that turn +
everyday functionality into unexpected + capabilities. +

+ +
+ +
+
+
+ DOCUMENTED ENTRIES + + {catalog.length} + binaries, scripts & libraries + +
+
+ COMMAND EXAMPLES + + {commands} + unexpected capabilities + +
+
+ ATT&CK TECHNIQUES + + {techniques.length} + mapped to the framework + +
+
+
+ +
+
+
+ FRESH FROM THE COMMUNITY +

Latest additions.

+
+ + ↗ + +
+ {recent.map((e) => ( + + +
+

{e.Name}

+

{e.Description}

+
+ + ↗ +
+ ))} +

+ By recorded creation date · {categories.length} documented functions +

+
+
+ + + diff --git a/website/src/pages/insights.astro b/website/src/pages/insights.astro new file mode 100644 index 000000000..be8c04465 --- /dev/null +++ b/website/src/pages/insights.astro @@ -0,0 +1,128 @@ +--- +import { sitePath } from '../lib/site'; +import Base from '../layouts/Base.astro'; +import Growth from '../components/Growth.astro'; +import { catalog, categories, typeLabels, techniques } from '../lib/catalog'; +const distribution = categories + .map((category) => ({ + category, + count: catalog.filter((e) => + e.Commands.some((c) => c.Category === category), + ).length, + })) + .sort((a, b) => b.count - a.count); +const coverage = catalog.filter((e) => + e.Detection?.some((d) => Object.keys(d).some((k) => k !== 'IOC')), +).length; +--- + + +
+

FOLLOW THE RESEARCH

+

+ The bigger picture. +

+

+ How the catalog grows. What the tools can do. Where detection resources + exist. +
A view of the community’s research, grounded in the source data. +

+
+
+ +
+

WHAT’S IN THE CATALOG

+

By file type.

+ {Object.entries(typeLabels).map(([type, label]) => { + const count = catalog.filter((e) => e.type === type).length; + return ( + + {label} + + + + {count} + + ); + })} +

+ {catalog.length} entries from the four published YAML collections. + Honorable mentions remain outside the published catalog. +

+
+
+

UNEXPECTED CAPABILITIES

+

Functions across the catalog.

+ {distribution.map(({ category, count }) => ( + + {category} + + + + {count} + + ))} +

+ Count of entries with at least one command in each category. An entry + can have multiple functions, so these counts overlap. +

+
+
+

TURN RESEARCH INTO DEFENSE

+

Detection resources.

+
+ {coverage} + / {catalog.length} +
+

+ Entries with at least one published detection link, such as Sigma, + Elastic, Splunk, analysis, or block rules. IOC-only entries are + excluded. Links are research leads; coverage does not guarantee that a + technique will be detected. +

+ + Explore detection resources ↗ + +
+

{techniques.length} ATT&CK technique IDs.

+

+ Distinct technique and sub-technique IDs mapped to command examples. +

+ + Download Navigator layer ↓ + +
+
+ + diff --git a/website/src/pages/lolbas/[...entry].astro b/website/src/pages/lolbas/[...entry].astro new file mode 100644 index 000000000..7a71db9eb --- /dev/null +++ b/website/src/pages/lolbas/[...entry].astro @@ -0,0 +1,437 @@ +--- +import { sitePath } from '../../lib/site'; +import Base from '../../layouts/Base.astro'; +import { catalog, typeLabels, type CatalogEntry } from '../../lib/catalog'; +import '../../styles/detail.css'; + +export function getStaticPaths() { + return catalog.map((entry) => ({ + params: { entry: `${entry.type}/${entry.id}` }, + props: { entry }, + })); +} +interface ExtendedEntry extends CatalogEntry { + Aliases?: { Alias: string }[] | null; + Code_Sample?: { Code: string }[] | null; +} +const { entry } = Astro.props as { entry: ExtendedEntry }; +const groups = [...new Set(entry.Commands.map((command) => command.Category))]; +const techniques = [ + ...new Set(entry.Commands.map((command) => command.MitreID).filter(Boolean)), +]; +const detections = (entry.Detection ?? []) + .flatMap((item) => Object.entries(item)) + .filter((item): item is [string, string] => Boolean(item[1])); +const detectionLinks = detections.filter( + ([kind, value]) => kind !== 'IOC' && /^https?:\/\//i.test(value), +); +const indicators = detections.filter( + ([kind, value]) => kind === 'IOC' || !/^https?:\/\//i.test(value), +); +const isLink = (value: string) => /^https?:\/\//i.test(value); +const attackURL = (id: string) => + `https://attack.mitre.org/techniques/${id.replaceAll('.', '/')}/`; +function linkLabel(value: string) { + try { + const url = new URL(value); + const pieces = url.pathname.split('/').filter(Boolean); + const last = decodeURIComponent(pieces.at(-1) ?? ''); + if (/^(twitter\.com|x\.com)$/.test(url.hostname)) + return `Research by @${pieces[0]}`; + if (url.hostname === 'gist.github.com') + return `Code & research by ${pieces[0]}`; + if (!last || /^[a-f0-9]{16,}$/i.test(last)) return url.hostname; + return last.replace(/\.html?$/, '').replace(/-/g, ' '); + } catch { + return value; + } +} +function host(value: string) { + try { + return new URL(value).hostname; + } catch { + return ''; + } +} +const sourceURL = `https://github.com/LOLBAS-Project/LOLBAS/blob/master/${entry.source}`; +const editURL = `https://github.com/LOLBAS-Project/LOLBAS/edit/master/${entry.source}`; +const related = catalog + .filter( + (other) => + other.id !== entry.id && + other.Commands.some((command) => techniques.includes(command.MitreID)), + ) + .slice(0, 3); +--- + + +
+ +
+
+ +
+ + LIVING OFF THE LAND /{' '} + {typeLabels[entry.type].toUpperCase()} + +

{entry.Name}

+
+
+ + View source + +
+

{entry.Description}

+
+ {groups.map((category) => ( + + {category} + + ))} + {entry.Aliases?.map((alias) => ( + Alias: {alias.Alias} + ))} +
+
+
+ {String(entry.Commands.length).padStart(2, '0')} + COMMAND EXAMPLES +
+
+ {String(techniques.length).padStart(2, '0')} + ATT&CK TECHNIQUES +
+
+ {String(detectionLinks.length).padStart(2, '0')} + DETECTION RESOURCES +
+
+ {String(groups.length).padStart(2, '0')} + CAPABILITIES +
+
+
+
+
+
+
+
+ 01 / TRADECRAFT +

Command reference

+
+ {entry.Commands.length} examples +
+ {groups.map((category) => ( +
+

+ + {category} + + { + entry.Commands.filter( + (command) => command.Category === category, + ).length + } + +

+ {entry.Commands.filter( + (command) => command.Category === category, + ).map((command, index) => ( +
+
+ + EXAMPLE {String(index + 1).padStart(2, '0')} + + {command.MitreID && ( + + {command.MitreID} + + )} +
+

{command.Description}

+
+
+ COMMAND +
+
{command.Command}
+ +
+
+
+
USE CASE
+
{command.Usecase}
+
+
+
PRIVILEGES
+
{command.Privileges || 'Not specified'}
+
+
+
OPERATING SYSTEM
+
{command.OperatingSystem || 'Not specified'}
+
+
+ {(command.Tags?.length ?? 0) > 0 && ( +
+ {command.Tags?.flatMap((tag) => Object.entries(tag)).map( + ([key, value]) => ( + + {key} · {value} + + ), + )} +
+ )} +
+ ))} +
+ ))} +
+
+
+
+ 02 / ON DISK +

Known paths

+
+
+
+ {entry.Full_Path?.length ? ( + entry.Full_Path.map((path) => ( +
+ + {path.Path} +
+ )) + ) : ( +

No paths documented for this entry.

+ )} +
+
+
+
+
+ 03 / DEFENSIVE INTELLIGENCE +

Detection & indicators

+
+ {detectionLinks.length} resources +
+ {detectionLinks.length > 0 && ( + + )} + {indicators.length > 0 && ( +
+

+ Indicators & detection notes +

+
    + {indicators.map(([kind, value]) => ( +
  • + {kind} +

    + {isLink(value) ? ( + {linkLabel(value)} ↗ + ) : ( + value + )} +

    +
  • + ))} +
+
+ )} + {detections.length === 0 && ( +
+

No detection resources documented yet.

+ + Contribute detection coverage + +
+ )} +
+ {(entry.Code_Sample?.length ?? 0) > 0 && ( +
+
+
+ REFERENCE IMPLEMENTATIONS +

Code samples

+
+
+ +
+ )} + {(entry.Resources?.length ?? 0) > 0 && ( +
+
+
+ FURTHER READING +

Research & resources

+
+
+ +
+ )} + {(entry.Acknowledgement?.length ?? 0) > 0 && ( +
+
+
+ COMMUNITY RESEARCH +

Acknowledgements

+
+
+
+ {entry.Acknowledgement?.map((person) => ( +
+ +
+ {person.Person} + {person.Handle && {person.Handle}} +
+
+ ))} +
+
+ )} +
+ +
+ diff --git a/website/src/scripts/explorer.ts b/website/src/scripts/explorer.ts new file mode 100644 index 000000000..e5b86ca6c --- /dev/null +++ b/website/src/scripts/explorer.ts @@ -0,0 +1,228 @@ +// LOL-SITE-002: filters apply to the same record; URL state is reloadable. +const form = document.querySelector('#catalog-form'); +if (form) { + const controls = [ + 'q', + 'type', + 'category', + 'technique', + 'detection', + 'sort', + 'year', + ]; + const field = (name: string) => + form.elements.namedItem(name) as HTMLInputElement | HTMLSelectElement; + const rows = Array.from( + document.querySelectorAll('[data-row]'), + ); + const count = document.querySelector('#result-count')!; + const body = document.querySelector('#catalog-rows')!; + const previous = document.querySelector('#previous')!; + const next = document.querySelector('#next')!; + const size = 25; + let page = 1; + function load() { + const params = new URLSearchParams(location.search); + controls.forEach((name) => { + field(name).value = params.get(name) || (name === 'sort' ? 'name' : ''); + }); + page = Math.max(1, Number.parseInt(params.get('page') || '1') || 1); + } + function update(sync = true) { + const q = field('q').value.trim().toLowerCase(); + const tokens = + q.match(/\/(?:awl bypass|uac bypass|[a-z]+)|#[\w]+|[^\s]+/g) || []; + const filtered = rows.filter((row) => { + const d = row.dataset; + const cats = JSON.parse(d.categories || '[]') as string[]; + const techs = JSON.parse(d.techniques || '[]') as string[]; + return ( + (!field('type').value || field('type').value === d.type) && + (!field('category').value || cats.includes(field('category').value)) && + (!field('technique').value || + techs.includes(field('technique').value)) && + (!field('detection').value || + field('detection').value === d.detection) && + (!field('year').value || d.created?.startsWith(field('year').value)) && + tokens.every((token) => + token.startsWith('/') + ? cats.some((c) => c.toLowerCase() === token.slice(1).trim()) + : token.startsWith('#') + ? (d.type || '') + .toLowerCase() + .includes(token.slice(1).replace(/y$/, 'ie')) + : d.search?.includes(token), + ) + ); + }); + const sort = field('sort').value; + const sortSpecs: Record< + string, + { key: string; descending: boolean; numeric?: boolean } + > = { + name: { key: 'name', descending: false }, + reverse: { key: 'name', descending: true }, + newest: { key: 'created', descending: true }, + oldest: { key: 'created', descending: false }, + commands: { key: 'commands', descending: true, numeric: true }, + detections: { key: 'detections', descending: true, numeric: true }, + 'detections-asc': { key: 'detections', descending: false, numeric: true }, + functions: { key: 'functions', descending: false }, + 'functions-desc': { key: 'functions', descending: true }, + type: { key: 'typeLabel', descending: false }, + 'type-desc': { key: 'typeLabel', descending: true }, + attack: { key: 'attack', descending: false }, + 'attack-desc': { key: 'attack', descending: true }, + }; + const spec = sortSpecs[sort] || sortSpecs.name; + filtered.sort((a, b) => { + const left = a.dataset[spec.key] || '', + right = b.dataset[spec.key] || ''; + const primary = spec.numeric + ? Number(left) - Number(right) + : left.localeCompare(right, 'en', { numeric: spec.key === 'attack' }); + return ( + primary * (spec.descending ? -1 : 1) || + (a.dataset.name || '').localeCompare(b.dataset.name || '', 'en') + ); + }); + document + .querySelectorAll('[data-sort]') + .forEach((button) => { + const key = button.dataset.sort!; + const active = + key === 'name' + ? spec.key === 'name' + : key === 'type' + ? spec.key === 'typeLabel' + : spec.key === key; + button + .closest('th')! + .setAttribute( + 'aria-sort', + active ? (spec.descending ? 'descending' : 'ascending') : 'none', + ); + button.querySelector('.sort-indicator')!.textContent = active + ? spec.descending + ? '↓' + : '↑' + : '↕'; + }); + const pages = Math.max(1, Math.ceil(filtered.length / size)); + page = Math.min(page, pages); + rows.forEach((row) => { + row.hidden = true; + }); + filtered.forEach((row, i) => { + row.hidden = i < (page - 1) * size || i >= page * size; + body.append(row); + }); + count.textContent = `${filtered.length} ${filtered.length === 1 ? 'entry' : 'entries'} to explore`; + document.querySelector('#empty-state')!.hidden = + filtered.length !== 0; + document.querySelector('#pagination')!.hidden = + filtered.length === 0; + document.querySelector('#page-info')!.textContent = + `Showing ${filtered.length ? (page - 1) * size + 1 : 0}–${Math.min(page * size, filtered.length)} of ${filtered.length} · Page ${page} of ${pages}`; + document.querySelector('#year-filter')!.textContent = field( + 'year', + ).value + ? `Created in ${field('year').value}` + : ''; + previous.disabled = page === 1; + next.disabled = page === pages; + if (sync) { + const params = new URLSearchParams(); + controls.forEach((name) => { + const value = field(name).value; + if (value && !(name === 'sort' && value === 'name')) + params.set(name, value); + }); + if (page > 1) params.set('page', String(page)); + history.replaceState( + null, + '', + `${location.pathname}${params.size ? '?' + params : ''}${location.hash}`, + ); + } + } + form.addEventListener('submit', (e) => e.preventDefault()); + form.addEventListener('input', () => { + page = 1; + update(); + }); + form.addEventListener('change', (event) => { + // Search already updates on input. Reordering rows again on blur can cancel + // a link click between pointer-down and pointer-up. + if (event.target instanceof HTMLInputElement) return; + page = 1; + update(); + }); + function reset() { + controls.forEach((name) => { + field(name).value = name === 'sort' ? 'name' : ''; + }); + page = 1; + update(); + } + form.addEventListener('reset', (e) => { + e.preventDefault(); + reset(); + }); + document.querySelector('#clear-empty')!.addEventListener('click', reset); + document + .querySelectorAll('[data-query]') + .forEach((button) => + button.addEventListener('click', () => { + field('q').value = button.dataset.query!; + page = 1; + update(); + }), + ); + previous.addEventListener('click', () => { + page--; + update(); + }); + next.addEventListener('click', () => { + page++; + update(); + }); + window.addEventListener('popstate', () => { + load(); + update(false); + }); + document.addEventListener('keydown', (e) => { + if ( + e.key === '/' && + !e.ctrlKey && + !e.metaKey && + !( + e.target instanceof HTMLInputElement || + e.target instanceof HTMLTextAreaElement || + e.target instanceof HTMLSelectElement + ) + ) { + e.preventDefault(); + field('q').focus(); + } + }); + document + .querySelectorAll('[data-sort]') + .forEach((button) => + button.addEventListener('click', () => { + const key = button.dataset.sort!; + const choices = + key === 'name' + ? ['name', 'reverse'] + : key === 'detections' + ? ['detections', 'detections-asc'] + : [key, key + '-desc']; + field('sort').value = + field('sort').value === choices[0] ? choices[1] : choices[0]; + page = 1; + update(); + }), + ); + load(); + update(false); +} diff --git a/website/src/styles/catalog.css b/website/src/styles/catalog.css new file mode 100644 index 000000000..3fb79f4c5 --- /dev/null +++ b/website/src/styles/catalog.css @@ -0,0 +1,261 @@ +.search-box { + display: flex; + align-items: center; + padding: 15px 19px; + gap: 12px; + border-bottom: 1px solid var(--line); + background: var(--panel-deep); +} +.search-box > span { + color: var(--accent); + font-size: 28px; + line-height: 20px; +} +.search-box input { + width: 100%; + background: transparent; + color: white; + border: 0; + outline-offset: 3px; + font-size: 13px; + padding: 5px; +} +.search-box input::placeholder { + color: #92a5b4; +} +.search-box kbd { + border: 1px solid var(--line); + padding: 2px 7px; + font: 11px var(--mono); + color: var(--muted); + border-radius: 3px; +} +.filters { + display: flex; + gap: 13px; + align-items: end; + padding: 16px 19px; + border-bottom: 1px solid var(--line); +} +.filters label { + flex: 1; + min-width: 0; + color: var(--muted); + font: 8px var(--mono); + letter-spacing: 1px; +} +.filters select { + width: 100%; + display: block; + margin-top: 7px; + background: var(--bg); + color: var(--text); + border: 1px solid var(--line); + border-radius: 5px; + font: + 11px -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; + height: 34px; + padding: 0 9px; +} +.reset { + background: transparent; + border: 0; + color: var(--muted); + height: 34px; + font-size: 11px; + padding: 0 5px; +} +.results-toolbar { + padding: 16px 19px; + display: flex; + align-items: center; + gap: 15px; + font-size: 11px; +} +.results-toolbar b { + color: white; +} +.hint { + margin-left: auto; + color: var(--muted); + font-size: 10px; +} +.hint button { + padding: 0; + border: 0; + background: none; + color: var(--accent); + font: 10px var(--mono); +} +.table-scroll { + overflow: auto; +} +.catalog-table { + width: 100%; + border-collapse: collapse; + text-align: left; +} +.catalog-table th { + background: #29343f; + color: var(--muted); + font: 8px var(--mono); + letter-spacing: 1px; + padding: 12px 14px; + white-space: nowrap; +} +.catalog-table td { + border-bottom: 1px solid #35434e80; + padding: 16px 14px; + font-size: 11px; +} +.catalog-table th:first-child, +.catalog-table td:first-child { + padding-left: 20px; + width: 33%; +} +.catalog-table td:nth-child(2) { + width: 25%; +} +.catalog-table tr:hover td { + background: #2b374280; +} +.entry-name { + display: flex; + gap: 13px; + align-items: center; +} +.entry-name strong { + font: 600 12px var(--mono); + color: var(--accent); +} +.entry-name small { + display: block; + margin-top: 6px; + max-width: 250px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 10px; + color: var(--muted); +} +.badges { + display: flex; + flex-wrap: wrap; + gap: 5px; +} +.badge { + border: 1px solid #486170; + padding: 4px 6px; + line-height: 1; + color: #a4c4d6; + background: #31455260; + border-radius: 3px; + font-size: 9px; + white-space: nowrap; +} +.badge.lime { + color: #5ad5f3; + border-color: #28697b; + background: #22c8ef0c; +} +.badge.blue { + color: #8dbcf6; + border-color: #435d7e; + background: #8dbcf60c; +} +.badge.purple { + color: #c2acf2; + border-color: #635879; + background: #c2acf20c; +} +.type-label { + font-size: 10px; + color: var(--muted); +} +.techniques { + display: flex; + flex-wrap: wrap; + gap: 5px; + max-width: 160px; + color: #a6bfce; + font: 10px var(--mono); +} +.coverage { + display: flex; + align-items: center; + gap: 6px; + color: var(--muted); + font: 10px var(--mono); +} +.coverage i { + width: 5px; + height: 5px; + background: #627482; + border-radius: 50%; +} +.coverage.available { + color: #59cee8; +} +.coverage.available i { + background: #59cee8; +} +.row-arrow { + color: #7793a7; +} +.pagination { + padding: 18px 20px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 10px; + color: var(--muted); +} +.pagination > div { + display: flex; + gap: 8px; +} +.pagination button { + background: var(--bg); + border: 1px solid var(--line); + padding: 7px 12px; + border-radius: 5px; + color: var(--text); + font-size: 10px; +} +.empty { + text-align: center; + padding: 50px 20px; +} +.empty p { + color: var(--muted); + font-size: 13px; +} +.catalog-table th button { + display: flex; + align-items: center; + gap: 10px; + padding: 0; + border: 0; + background: transparent; + color: inherit; + font: inherit; + letter-spacing: inherit; + text-align: left; +} +.catalog-table th button:hover { + color: var(--text); +} +.sort-indicator { + color: #7895a8; + font-size: 11px; + letter-spacing: 0; +} +.catalog-table th[aria-sort='ascending'] button, +.catalog-table th[aria-sort='descending'] button, +.catalog-table th[aria-sort='ascending'] .sort-indicator, +.catalog-table th[aria-sort='descending'] .sort-indicator { + color: var(--accent); +} diff --git a/website/src/styles/detail.css b/website/src/styles/detail.css new file mode 100644 index 000000000..c43179e8c --- /dev/null +++ b/website/src/styles/detail.css @@ -0,0 +1,698 @@ +.detail-hero { + padding: 35px 0 0; +} +.detail-hero .breadcrumb { + display: flex; + flex-wrap: wrap; + gap: 12px; + font-size: 10px; + line-height: 1.6; + margin: 0 0 36px; +} +.detail-hero-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; +} +.detail-identity { + display: flex; + align-items: center; + gap: 22px; + min-width: 0; +} +.detail-file-mark { + flex-shrink: 0; + width: 72px; + height: 76px; + border: 1px solid #3a6778; + border-radius: 13px; + display: grid; + place-items: center; + color: var(--accent); + font: 30px var(--mono); + background: linear-gradient(135deg, #22c8ef18, #22c8ef03); + box-shadow: inset 0 0 25px #22c8ef06; +} +.detail-identity h1 { + font: 650 clamp(28px, 3.6vw, 48px)/1.15 var(--mono); + letter-spacing: -2px; + margin: 12px 0 0; + overflow-wrap: anywhere; +} +.detail-identity .eyebrow { + font-size: 9px; + letter-spacing: 1.3px; +} +.detail-source { + flex-shrink: 0; + color: var(--muted); +} +.detail-description { + max-width: 850px; + color: #b6c6d1; + line-height: 1.85; + font-size: 15px; + margin: 25px 0 17px; + white-space: pre-line; +} +.detail-capabilities { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.detail-capabilities .badge { + font-size: 10px; + padding: 7px 10px; +} +.detail-stats { + display: grid; + grid-template-columns: repeat(4, 1fr); + margin: 32px 0 38px; + border-block: 1px solid var(--line); + background: linear-gradient(90deg, #22c8ef06, transparent); +} +.detail-stats > div { + display: flex; + align-items: center; + gap: 13px; + padding: 22px 20px; + border-right: 1px solid var(--line); +} +.detail-stats > div:first-child { + padding-left: 0; +} +.detail-stats > div:last-child { + border: 0; +} +.detail-stats strong { + font: 500 27px var(--mono); + letter-spacing: -1.5px; + color: var(--accent); +} +.detail-stats span { + font: 8px/1.8 var(--mono); + letter-spacing: 1px; + color: var(--muted); + max-width: 110px; +} +.entry-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 270px; + gap: 32px; + align-items: start; + margin-bottom: 65px; +} +.entry-content { + min-width: 0; +} +.entry-section { + margin: 0 0 36px; + scroll-margin-top: 25px; +} +.entry-section-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + margin-bottom: 20px; +} +.entry-section-heading h2 { + font-size: 24px; + letter-spacing: -0.8px; + margin: 8px 0 0; +} +.entry-section-heading .eyebrow { + font-size: 8px; + letter-spacing: 1.2px; +} +.entry-count { + color: var(--muted); + font: 10px var(--mono); + white-space: nowrap; +} +.command-group { + scroll-margin-top: 25px; +} +.category-heading { + font-size: 13px; + font-weight: 600; + display: flex; + align-items: center; + gap: 10px; + margin: 25px 0 13px; +} +.category-heading > span:last-child { + font: 9px var(--mono); + color: var(--muted); + border: 1px solid var(--line); + padding: 2px 5px; + border-radius: 3px; +} +.category-line { + width: 3px; + height: 15px; + background: var(--accent); + border-radius: 2px; +} +.entry-command { + padding: 22px; + margin-bottom: 16px; + box-shadow: 0 4px 14px #11182013; +} +.command-topline { + display: flex; + align-items: center; + justify-content: space-between; + gap: 15px; +} +.command-topline .eyebrow { + font-size: 8px; +} +.attack-pill { + font: 10px var(--mono); + color: var(--accent); + display: inline-flex; + gap: 15px; + border: 1px solid #22c8ef35; + border-radius: 4px; + background: #22c8ef08; + padding: 5px 7px; +} +.entry-command h4 { + font-size: 14px; + font-weight: 450; + line-height: 1.8; + margin: 17px 0; + color: #d6e0e6; + white-space: pre-line; +} +.entry-command .code-wrap { + border-color: #354855; + background: #182129; +} +.command-terminal-label { + font: 8px var(--mono); + letter-spacing: 1.5px; + color: #839dab; + padding: 13px 15px 0; +} +.command-terminal-label span { + color: var(--accent); + margin-right: 7px; +} +.entry-command pre { + font-size: 12px; + line-height: 1.9; + color: #d4edf4; + padding: 14px 17px 18px; + tab-size: 2; +} +.entry-command .code-wrap button { + padding: 5px 9px; +} +.entry-command-meta { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18px; + margin: 20px 0 0; +} +.entry-command-meta dt, +.entry-metadata dt { + font: 8px/1.5 var(--mono); + letter-spacing: 1px; + color: #90a5b4; + margin-bottom: 6px; +} +.entry-command-meta dd { + font-size: 11px; + line-height: 1.8; + margin: 0; + color: #d3dde4; + white-space: pre-line; +} +.entry-command-meta .command-usecase { + grid-column: 1/-1; +} +.command-tags { + display: flex; + flex-wrap: wrap; + gap: 6px; + border-top: 1px solid var(--line); + margin-top: 18px; + padding-top: 15px; +} +.command-tags .badge { + font-size: 9px; + line-height: 1.5; + white-space: normal; +} +.command-tags .badge > span { + color: #d3e5ee; +} +.entry-paths { + padding: 7px 20px; +} +.entry-path { + display: flex; + gap: 14px; + padding: 16px 0; + border-bottom: 1px solid var(--line); +} +.entry-path:last-child { + border: 0; +} +.entry-path > span { + color: var(--accent); +} +.entry-path code { + font-size: 11px; + line-height: 1.9; + overflow-wrap: anywhere; + color: #bfd0dc; +} +.entry-resource-list { + list-style: none; + margin: 0; + padding: 0 20px; +} +.entry-resource-list li + li { + border-top: 1px solid var(--line); +} +.entry-resource-list li > a { + display: flex; + align-items: center; + gap: 15px; + padding: 18px 0; + min-width: 0; +} +.resource-kind { + font: 8px var(--mono); + letter-spacing: 0.4px; + color: var(--accent); + background: #22c8ef0a; + border: 1px solid #22c8ef2b; + border-radius: 4px; + padding: 6px 7px; + min-width: 56px; + text-align: center; + flex-shrink: 0; +} +.resource-text { + min-width: 0; +} +.resource-text strong { + display: block; + font-size: 12px; + font-weight: 500; + line-height: 1.7; + overflow-wrap: anywhere; +} +.resource-text small { + font: 9px var(--mono); + color: var(--muted); + display: block; + margin-top: 5px; +} +.resource-arrow { + margin-left: auto; + color: var(--accent); + flex-shrink: 0; +} +.indicator-panel { + margin-top: 15px; + padding: 20px; + border-left: 2px solid #22c8ef80; + background: linear-gradient(120deg, #22c8ef07, var(--panel)); +} +.indicator-panel h3 { + font-size: 13px; + font-weight: 600; + margin: 0 0 20px; +} +.indicator-panel ul { + padding: 0; + margin: 0; + list-style: none; +} +.indicator-panel li + li { + border-top: 1px solid var(--line); + padding-top: 15px; + margin-top: 15px; +} +.indicator-panel .eyebrow { + font-size: 8px; +} +.indicator-panel p { + font-size: 12px; + line-height: 1.9; + white-space: pre-line; + overflow-wrap: anywhere; + margin: 6px 0 0; + color: #bfced8; +} +.indicator-panel a, +.entry-empty a { + color: var(--accent); +} +.entry-empty { + padding: 20px; + color: var(--muted); + font-size: 12px; + line-height: 1.8; +} +.entry-empty p { + margin-top: 0; +} +.entry-credits { + padding: 22px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 23px; +} +.entry-credits > div { + display: flex; + gap: 12px; + align-items: center; + min-width: 0; +} +.credit-avatar { + width: 32px; + height: 32px; + flex-shrink: 0; + display: grid; + place-items: center; + border: 1px solid #456171; + background: #22c8ef08; + border-radius: 50%; + color: var(--accent); + font: 12px var(--mono); +} +.entry-credits strong { + font-size: 11px; + font-weight: 550; + overflow-wrap: anywhere; +} +.entry-credits div > div > span { + display: block; + color: var(--muted); + font: 10px var(--mono); + margin-top: 5px; + overflow-wrap: anywhere; +} +.entry-aside { + position: sticky; + top: 24px; +} +.entry-toc { + padding: 23px; +} +.entry-toc > .eyebrow { + font-size: 9px; + margin-bottom: 20px; +} +.entry-toc > a { + display: block; + font-size: 11px; + color: #b2c1cd; + line-height: 1.7; + padding: 8px 0; +} +.entry-toc > a:hover { + color: var(--accent); +} +.entry-toc .toc-main { + display: flex; + justify-content: space-between; + color: var(--text); +} +.toc-main > span { + font: 9px var(--mono); + color: var(--muted); +} +.entry-toc .toc-category { + padding: 6px 0 6px 15px; + border-left: 1px solid #22c8ef60; + margin-left: 2px; + color: #92aebf; + font-size: 10px; +} +.entry-toc .toc-category:last-of-type { + margin-bottom: 6px; +} +.entry-metadata { + margin-top: 18px; + padding: 23px; +} +.entry-metadata > .eyebrow { + font-size: 9px; +} +.entry-metadata dl { + margin: 23px 0; +} +.entry-metadata dl > div + div { + margin-top: 18px; +} +.entry-metadata dd { + margin: 0; + font-size: 12px; + line-height: 1.6; + overflow-wrap: anywhere; +} +.entry-mitre { + border-top: 1px solid var(--line); + padding-top: 18px; +} +.entry-mitre > .eyebrow { + font-size: 8px; +} +.entry-mitre > div { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 12px; +} +.entry-edit { + display: flex; + justify-content: space-between; + border-top: 1px solid var(--line); + margin-top: 22px; + padding-top: 18px; + font-size: 11px; + color: var(--accent); +} +.entry-related { + padding: 25px 4px; +} +.entry-related > .eyebrow { + font-size: 8px; + letter-spacing: 0.8px; +} +.entry-related h2 { + font-size: 17px; + letter-spacing: -0.4px; + margin: 9px 0 13px; +} +.entry-related > a { + display: flex; + justify-content: space-between; + gap: 12px; + padding: 13px 0; + border-bottom: 1px solid var(--line); + font: 11px var(--mono); + color: var(--muted); + overflow-wrap: anywhere; +} +.entry-related > a:hover, +.entry-related > a > span:last-child { + color: var(--accent); +} +@media (max-width: 1050px) { + .entry-layout { + grid-template-columns: minmax(0, 1fr) 230px; + gap: 22px; + } + .detail-stats > div { + padding: 18px 13px; + gap: 10px; + } + .detail-stats span { + font-size: 7px; + } + .entry-toc, + .entry-metadata { + padding: 19px; + } + .detail-file-mark { + width: 60px; + height: 64px; + } + .detail-identity h1 { + font-size: 35px; + } + .detail-identity .eyebrow { + font-size: 8px; + } +} +@media (max-width: 760px) { + .detail-hero { + padding-top: 24px; + } + .detail-hero .breadcrumb { + font-size: 9px; + margin-bottom: 26px; + gap: 8px; + } + .detail-hero-row { + align-items: start; + flex-direction: column; + gap: 20px; + } + .detail-identity { + gap: 14px; + width: 100%; + } + .detail-identity h1 { + font-size: clamp(23px, 6vw, 35px); + letter-spacing: -1px; + } + .detail-file-mark { + width: 46px; + height: 50px; + font-size: 21px; + border-radius: 8px; + } + .detail-identity .eyebrow { + font-size: 7px; + letter-spacing: 0.65px; + } + .detail-source { + padding: 9px 12px; + font-size: 10px; + } + .detail-description { + font-size: 13px; + margin-top: 20px; + } + .detail-stats { + grid-template-columns: 1fr 1fr; + margin: 25px 0; + } + .detail-stats > div, + .detail-stats > div:first-child { + padding: 16px 10px; + } + .detail-stats > div:nth-child(2) { + border-right: 0; + } + .detail-stats > div:nth-child(-n + 2) { + border-bottom: 1px solid var(--line); + } + .detail-stats strong { + font-size: 25px; + } + .detail-stats span { + font-size: 7px; + } + .entry-layout { + display: flex; + flex-direction: column; + gap: 25px; + margin-bottom: 35px; + } + .entry-aside { + position: static; + display: contents; + } + .entry-toc { + order: -1; + display: flex; + flex-wrap: wrap; + gap: 4px 16px; + width: 100%; + padding: 17px; + } + .entry-toc > .eyebrow { + width: 100%; + margin: 0 0 8px; + } + .entry-toc > a, + .entry-toc .toc-category { + padding: 4px 0; + border: 0; + margin: 0; + font-size: 10px; + } + .entry-toc .toc-main > span { + margin-left: 8px; + } + .entry-metadata { + width: 100%; + margin: 0; + } + .entry-metadata dl { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + } + .entry-metadata dl > div + div { + margin: 0; + } + .entry-related { + width: 100%; + padding: 0 4px; + } + .entry-content { + width: 100%; + } + .entry-section-heading h2 { + font-size: 22px; + } + .entry-section-heading .eyebrow { + font-size: 7px; + } + .entry-count { + font-size: 9px; + } + .entry-command { + padding: 16px; + } + .entry-command h4 { + font-size: 12px; + } + .entry-command pre { + font-size: 11px; + padding: 15px 13px; + } + .entry-command-meta { + gap: 16px; + } + .entry-paths { + padding: 5px 14px; + } + .entry-path code { + font-size: 10px; + } + .entry-resource-list { + padding: 0 15px; + } + .entry-resource-list li > a { + gap: 10px; + } + .resource-kind { + min-width: 46px; + font-size: 7px; + padding: 5px; + } + .resource-text strong { + font-size: 11px; + } + .entry-credits { + padding: 18px; + gap: 20px; + grid-template-columns: 1fr; + } + .entry-section { + margin-bottom: 28px; + } +} diff --git a/website/src/styles/filters.css b/website/src/styles/filters.css new file mode 100644 index 000000000..23e2b4bdc --- /dev/null +++ b/website/src/styles/filters.css @@ -0,0 +1,102 @@ +/* Progressive native select styling keeps OS controls on older browsers. */ +.filters select { + transition: + border-color 0.15s, + background-color 0.15s; +} +.filters select:hover { + border-color: #5c7b8c; + background-color: #24323d; +} +@supports (appearance: base-select) { + .filters select, + .filters select::picker(select) { + appearance: base-select; + } + .filters select { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + height: 36px; + padding: 0 11px; + border-radius: 6px; + font-size: 11px; + overflow: hidden; + white-space: nowrap; + } + .filters select:open { + border-color: var(--accent); + box-shadow: 0 0 0 3px #22c8ef12; + background: #253741; + } + .filters select::picker-icon { + content: '⌄'; + color: #8fa8b8; + font-size: 15px; + margin-left: auto; + transition: rotate 0.15s; + } + .filters select:open::picker-icon { + rotate: 180deg; + color: var(--accent); + } + .filters select::picker(select) { + margin: 8px 0 0; + padding: 6px; + color: var(--text); + background: #222e38; + border: 1px solid #466272; + border-radius: 9px; + box-shadow: + 0 16px 45px #10161dcc, + 0 0 0 1px #22c8ef09; + min-width: anchor-size(width); + max-height: min(340px, 65vh); + scrollbar-width: thin; + scrollbar-color: #4a6677 transparent; + font: + 12px/1.4 -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; + } + .filters option { + display: flex; + align-items: center; + gap: 10px; + min-height: 34px; + padding: 8px 10px; + border-radius: 5px; + color: #c2d0da; + cursor: pointer; + } + .filters option:first-child { + color: #8fa8b8; + } + .filters option:hover, + .filters option:focus { + background: #2e4350; + color: #effaff; + outline: none; + } + .filters option:checked { + background: #22c8ef14; + color: #63d9f3; + font-weight: 600; + } + .filters option::checkmark { + content: '✓'; + color: var(--accent); + font-size: 11px; + } + .filters option + option { + margin-top: 2px; + } +} +@media (prefers-reduced-motion: reduce) { + .filters select, + .filters select::picker-icon { + transition: none; + } +} diff --git a/website/src/styles/global.css b/website/src/styles/global.css new file mode 100644 index 000000000..0ffed990a --- /dev/null +++ b/website/src/styles/global.css @@ -0,0 +1,823 @@ +@import './catalog.css'; +@import './pages.css'; +:root { + color-scheme: dark; + --bg: #1e262e; + --panel: #222c35; + --panel-deep: #1a222a; + --line: #35434e; + --text: #f3f6f9; + --muted: #9baeba; + --accent: #22c8ef; + --mono: ui-monospace, SFMono-Regular, Consolas, monospace; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; + background: var(--bg); + color: var(--text); +} +* { + box-sizing: border-box; +} +body { + margin: 0; +} +a { + color: inherit; + text-decoration: none; +} +button, +input, +select { + font: inherit; +} +button, +a, +input, +select, +summary { + outline-offset: 5px; +} +a:hover { + color: var(--accent); +} +button { + cursor: pointer; +} +button:disabled { + cursor: default; + opacity: 0.4; +} +button:focus-visible, +a:focus-visible, +input:focus-visible, +select:focus-visible, +summary:focus-visible { + outline: 2px solid var(--accent); +} +[hidden] { + display: none !important; +} +::selection { + background: #22c8ef55; +} +html { + scroll-behavior: smooth; + scroll-padding-top: 20px; +} +.header, +main, +footer { + max-width: 1360px; + margin: auto; + padding-left: 48px; + padding-right: 48px; +} +.header { + height: 88px; + display: flex; + align-items: center; + gap: 64px; + border-bottom: 1px solid var(--line); +} +.brand { + font-weight: 850; + font-size: 25px; + letter-spacing: -1px; + display: inline-flex; + align-items: center; + gap: 10px; +} +.brand-logo { + object-fit: contain; + /* Keep the original badge legible on a dark surface without enlarging it. */ + filter: invert(1) hue-rotate(180deg); +} +.brand-dot { + color: var(--accent); +} +.header nav { + display: flex; + gap: 32px; + font-size: 14px; + color: var(--muted); +} +.header nav a:hover { + color: white; +} +.repo-link { + display: inline-flex; + align-items: center; + gap: 9px; + margin-left: auto; + border: 1px solid var(--line); + padding: 9px 16px; + border-radius: 6px; + font-size: 13px; +} +.repo-link span { + color: var(--accent); + margin-left: 6px; +} +.eyebrow { + display: block; + font-family: var(--mono); + font-size: 10px; + letter-spacing: 1.6px; + color: var(--muted); + font-weight: 500; + line-height: 1.5; +} +.status-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 10px #22c8ef55; + vertical-align: middle; + margin-right: 8px; +} +.hero { + display: grid; + grid-template-columns: 1.3fr 1fr; + gap: 20px; + padding: 55px 0 42px; + align-items: center; +} +.hero h1 { + font-size: clamp(34px, 3.65vw, 52px); + line-height: 1.1; + letter-spacing: -2.8px; + margin: 21px 0; +} +.hero h1 span { + color: var(--accent); +} +.intro { + color: var(--muted); + font-size: 15px; + line-height: 1.85; +} +.hero-actions { + display: flex; + align-items: center; + gap: 25px; + margin-top: 28px; +} +.button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 28px; + padding: 13px 18px; + border: 1px solid var(--line); + border-radius: 6px; + background: transparent; + color: var(--text); + font-size: 12px; + font-weight: 650; +} +.primary { + background: var(--accent); + color: #15222b; + border-color: var(--accent); +} +.primary:hover { + background: #6addf7; + color: #15222b; +} +.text-link { + font-size: 12px; + color: var(--muted); +} +.text-link span { + color: var(--accent); + margin-left: 10px; +} +.hero-art { + height: 290px; + position: relative; + display: flex; + justify-content: center; +} +.radar { + position: relative; + height: 300px; + width: 360px; + background: radial-gradient(ellipse, #22c8ef0c, transparent 70%); +} +.orbit { + border: 1px solid #22c8ef20; + border-radius: 50%; + position: absolute; + inset: 0 30px; +} +.orbit.two { + inset: 30px 60px; +} +.orbit.three { + inset: 60px 90px; +} +.crosshair { + position: absolute; + background: #22c8ef15; +} +.horizontal { + width: 100%; + height: 1px; + top: 50%; +} +.vertical { + height: 100%; + width: 1px; + left: 50%; +} +.scan { + position: absolute; + inset: 0 30px; + border-radius: 50%; + background: conic-gradient( + from 40deg, + transparent 0deg, + #22c8ef0c 65deg, + transparent 66deg + ); +} +.terminal { + position: absolute; + top: 65px; + left: 15px; + width: 330px; + background: linear-gradient(130deg, #2b3742, #19232c); + border: 1px solid #466274; + border-radius: 9px; + box-shadow: 0 25px 55px #10182070; + transform: rotate(-3deg); +} +.terminal-top { + height: 31px; + display: flex; + align-items: center; + gap: 5px; + border-bottom: 1px solid var(--line); + padding: 0 13px; +} +.terminal-top i { + width: 5px; + height: 5px; + border-radius: 50%; + background: #657887; +} +.terminal-top i:first-child { + background: var(--accent); +} +.terminal-top span { + margin-left: auto; + font: 8px var(--mono); + letter-spacing: 1px; + color: #829caf; +} +.terminal p { + font: 10px var(--mono); + margin: 12px 15px; + color: var(--muted); +} +.terminal p span { + color: var(--accent); +} +.terminal-mark { + display: flex; + justify-content: center; + align-items: center; +} +.terminal-mark img { + display: block; + object-fit: contain; + filter: invert(1) hue-rotate(180deg) drop-shadow(0 0 14px #22c8ef20); +} +.terminal .terminal-bottom { + font-size: 7px; + letter-spacing: 0.4px; + text-align: center; + margin: 12px 10px; +} +.radar-label { + position: absolute; + font: 8px var(--mono); + color: #6b8799; + letter-spacing: 2px; +} +.label-top { + top: 12px; + left: 28px; +} +.label-bottom { + bottom: 5px; + right: 0; +} +.radar-point { + position: absolute; + height: 5px; + width: 5px; + background: var(--accent); + border-radius: 50%; + box-shadow: 0 0 0 5px #22c8ef12; +} +.p1 { + left: 65px; + top: 32px; +} +.p2 { + right: 24px; + top: 112px; +} +.p3 { + left: 115px; + bottom: 9px; +} +.metrics { + display: grid; + grid-template-columns: repeat(3, 1fr); + border: 1px solid var(--line); + border-radius: 8px; + background: linear-gradient(120deg, #27323c, #222c35); +} +.metrics > div { + padding: 21px 23px; + border-right: 1px solid var(--line); +} +.metrics > div:last-child { + border: 0; +} +.metric-label { + font: 9px var(--mono); + letter-spacing: 1.2px; + color: var(--muted); +} +.metrics strong { + display: block; + font-size: 30px; + letter-spacing: -1px; + margin-top: 9px; +} +.metrics small { + display: block; + font-size: 11px; + color: var(--muted); + font-weight: 400; + letter-spacing: 0; + margin-top: 5px; +} +.metrics > div:first-child strong { + color: var(--accent); +} +.intelligence-grid { + display: grid; + grid-template-columns: 1.16fr 1fr; + gap: 18px; + margin-top: 23px; +} +.panel { + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + overflow: hidden; +} +.growth, +.recent { + padding: 22px 25px; +} +.panel-heading { + display: flex; + justify-content: space-between; + align-items: center; +} +.panel-heading h2 { + font-size: 18px; + letter-spacing: -0.4px; + margin: 8px 0 0; +} +.panel-heading > a { + border: 1px solid var(--line); + padding: 5px 8px; + border-radius: 5px; + color: var(--muted); +} +.chart-summary { + display: flex; + align-items: center; + gap: 12px; + margin: 14px 0 0; +} +.chart-summary > strong { + font-size: 29px; + letter-spacing: -1px; +} +.chart-summary > span { + font-size: 9px; + line-height: 1.6; + color: var(--muted); +} +.chart-summary b { + font-weight: 400; +} +.chart-key { + margin-left: auto; +} +.chart-key i { + display: inline-block; + width: 12px; + height: 2px; + background: var(--accent); + vertical-align: middle; + margin-right: 5px; +} +.growth-chart { + display: block; + width: 100%; + height: 120px; + margin-top: 0; +} +.chart-note { + color: var(--muted); + font-size: 9px; + margin: 10px 0 0; +} +.chart-data { + font-size: 10px; + margin-top: 9px; + color: var(--muted); +} +.chart-data table { + width: 100%; + text-align: left; + line-height: 2; + margin-top: 10px; +} +.recent-entry { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 0; + border-bottom: 1px solid var(--line); +} +.recent-entry h3 { + font: 600 12px var(--mono); + margin: 0 0 5px; +} +.recent-entry p { + margin: 0; + font-size: 10px; + color: var(--muted); + max-width: 230px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.recent-entry time { + font: 9px var(--mono); + color: var(--muted); + margin-left: auto; + white-space: nowrap; +} +.file-icon { + font: 15px var(--mono); + color: var(--muted); + border: 1px solid var(--line); + border-radius: 5px; + min-width: 30px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + background: #23303b; +} +.section-heading { + display: flex; + align-items: center; + justify-content: space-between; + padding: 40px 0 20px; +} +.section-heading h2 { + font-size: 30px; + letter-spacing: -1.3px; + margin: 8px 0; +} +.section-heading p { + font-size: 12px; + color: var(--muted); + margin: 0; +} +.catalog-section { + scroll-margin-top: 20px; +} +.contribute-link { + margin: 30px 0 48px; + text-align: center; +} +footer { + border-top: 1px solid var(--line); + padding-top: 32px; + padding-bottom: 40px; + display: flex; + justify-content: space-between; +} +footer p { + font-size: 11px; + color: var(--muted); +} +footer > div:last-child { + text-align: right; +} +footer a:not(.brand) { + font-size: 11px; + margin-left: 20px; +} +footer small { + display: block; + color: var(--muted); + font-size: 9px; + line-height: 1.7; + margin-top: 17px; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); +} +.skip { + position: absolute; + top: -80px; + left: 20px; + background: var(--accent); + color: #18252e; + padding: 10px; + z-index: 10; +} +.skip:focus { + top: 10px; +} +@media (min-width: 1450px) { + .hero { + padding-top: 65px; + padding-bottom: 55px; + } + .hero-art { + transform: scale(1.08); + } +} +@media (max-width: 1000px) { + .header, + main, + footer { + padding-left: 25px; + padding-right: 25px; + } + .hero h1 { + font-size: 44px; + } + .desktop-br { + display: none; + } + .hero-art { + transform: scale(0.85); + } + .metrics > div { + padding: 18px 15px; + } + .filters { + flex-wrap: wrap; + } + .filters label { + flex: 1 1 25%; + } + .recent-entry time { + display: none; + } + .entry-name small { + max-width: 200px; + } + .header { + gap: 40px; + } +} +@media (max-width: 700px) { + .header, + main, + footer { + padding-left: 20px; + padding-right: 20px; + } + .header { + height: 75px; + gap: 15px; + flex-wrap: wrap; + } + .brand { + font-size: 20px; + } + .brand-logo { + width: 28px; + height: 28px; + } + .header nav { + margin-left: auto; + gap: 17px; + font-size: 11px; + } + .repo-link { + display: none; + } + .hero { + display: block; + padding: 35px 0 30px; + } + .hero h1 { + font-size: 38px; + letter-spacing: -1.8px; + } + .hero .eyebrow { + font-size: 8px; + letter-spacing: 1px; + } + .hero-art { + display: none; + } + .intro { + font-size: 13px; + } + .hero-actions { + gap: 17px; + } + .hero-actions .text-link { + font-size: 10px; + } + .metrics { + grid-template-columns: 1fr 1fr; + } + .metrics > div { + padding: 18px; + } + .metrics > div:nth-child(2) { + border-right: 0; + } + .metrics > div:nth-child(-n + 2) { + border-bottom: 1px solid var(--line); + } + .metrics > div:last-child { + grid-column: 1 / -1; + } + .metric-label { + font-size: 8px; + } + .metrics strong { + font-size: 26px; + } + .metrics small { + font-size: 9px; + } + .intelligence-grid, + .insights-grid { + grid-template-columns: 1fr; + } + .growth, + .recent { + padding: 20px; + } + .recent-entry time { + display: block; + font-size: 8px; + } + .recent-entry p { + max-width: 185px; + } + .chart-key { + display: none; + } + .section-heading { + padding-top: 30px; + } + .section-heading h2 { + font-size: 25px; + } + .section-heading .text-link { + font-size: 10px; + } + .section-heading .eyebrow { + font-size: 8px; + } + .filters { + padding: 14px; + gap: 10px; + } + .filters label { + flex: 1 1 40%; + } + .filters label:nth-child(5) { + flex: 1 1 65%; + } + .hint { + display: none; + } + .results-toolbar { + padding: 14px; + } + .catalog-table { + min-width: 690px; + } + .catalog-table td { + padding: 14px 12px; + } + .entry-name small { + max-width: 185px; + } + .catalog-table th { + font-size: 7px; + } + .pagination { + padding: 15px 12px; + gap: 10px; + } + .pagination button { + padding: 7px; + } + footer { + gap: 20px; + flex-direction: column; + } + footer > div:last-child { + text-align: left; + } + footer a:not(.brand) { + margin: 0 15px 0 0; + } + .page-head { + padding: 35px 0 20px; + } + .page-head h1 { + font-size: 34px; + } + .page-head p { + font-size: 13px; + } + .detail-grid { + display: flex; + flex-direction: column-reverse; + gap: 20px; + } + .detail-grid aside { + position: static; + } + .detail-sidebar ul { + display: flex; + flex-wrap: wrap; + gap: 12px; + } + .command-card { + padding: 16px; + } + .command-meta { + grid-template-columns: 1fr; + } + .bar-row { + grid-template-columns: 110px 1fr 25px; + gap: 8px; + } +} +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} + +/* Keep grid children within the viewport and chart links easy to target. */ +.panel { + min-width: 0; +} +.intelligence-grid > *, +.insights-grid > * { + min-width: 0; +} +.recent-entry > div { + min-width: 0; +} +.growth-chart path, +.growth-chart line { + pointer-events: none; +} +.growth-chart a:hover circle { + r: 6px; + fill: #a2ecff; +} +.chart-data a { + color: var(--accent); +} +.catalog-table th { + position: relative; +} diff --git a/website/src/styles/pages.css b/website/src/styles/pages.css new file mode 100644 index 000000000..65a359722 --- /dev/null +++ b/website/src/styles/pages.css @@ -0,0 +1,230 @@ +.page-head { + padding: 50px 0 30px; +} +.breadcrumb { + color: var(--muted); + font: 11px var(--mono); + margin-bottom: 28px; +} +.breadcrumb a { + color: var(--accent); +} +.page-head h1 { + font-size: 44px; + letter-spacing: -2px; + margin: 12px 0; +} +.page-head p { + color: var(--muted); + font-size: 15px; + line-height: 1.8; + max-width: 780px; +} +.detail-grid { + display: grid; + grid-template-columns: 1fr 280px; + gap: 26px; + margin-bottom: 60px; +} +.detail-grid aside { + align-self: start; + position: sticky; + top: 20px; +} +.detail-sidebar { + padding: 22px; + font-size: 12px; +} +.detail-sidebar h2 { + font-size: 13px; +} +.detail-sidebar ul { + padding: 0; + list-style: none; + line-height: 2.3; +} +.detail-sidebar dl { + font-size: 11px; + line-height: 1.9; +} +.detail-sidebar dt { + color: var(--muted); +} +.detail-sidebar dd { + margin: 0 0 10px; +} +.content-section { + margin-bottom: 30px; +} +.content-section h2 { + font-size: 23px; + letter-spacing: -0.5px; +} +.command-card { + padding: 22px; + margin-bottom: 15px; +} +.command-card h3 { + font-size: 14px; + font-weight: 550; + line-height: 1.7; + margin: 0 0 14px; +} +.code-wrap { + position: relative; + border: 1px solid var(--line); + border-radius: 5px; + background: var(--panel-deep); +} +pre { + font: 12px/1.8 var(--mono); + white-space: pre-wrap; + overflow-wrap: anywhere; + padding: 17px; + margin: 0; +} +code { + font-family: var(--mono); +} +.code-wrap pre { + padding-right: 70px; +} +.code-wrap button { + position: absolute; + right: 9px; + top: 10px; + background: var(--panel); + color: var(--muted); + font-size: 10px; + border: 1px solid var(--line); + border-radius: 3px; + padding: 5px 8px; +} +.command-meta { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 14px; + font-size: 11px; + line-height: 1.7; + margin-bottom: 0; +} +.command-meta dt { + color: var(--muted); + font: 8px var(--mono); + letter-spacing: 1px; + margin-bottom: 5px; +} +.command-meta dd { + margin: 0; +} +.command-meta a, +.resource-list a { + color: var(--accent); +} +.resource-list { + padding: 20px 25px; + list-style: none; + margin: 0; +} +.resource-list li { + padding: 10px 0; + border-bottom: 1px solid var(--line); + font-size: 12px; + line-height: 1.8; + overflow-wrap: anywhere; +} +.resource-list li:last-child { + border: 0; +} +.resource-list strong { + color: var(--muted); + font: 10px var(--mono); + margin-right: 12px; +} +.paths { + font: 12px/2 var(--mono); + overflow-wrap: anywhere; +} +.article { + max-width: 820px; + margin: 0 auto 70px; + font-size: 14px; + line-height: 1.9; +} +.article h2 { + margin-top: 35px; + font-size: 24px; +} +.article a:not(.button) { + color: var(--accent); +} +.maintainers h2 { + margin-top: 0; +} +.maintainer-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); + gap: 0 24px; + padding: 0; + list-style: none; + margin: 20px 0 38px; +} +.maintainer-list a { + display: flex; + flex-direction: column; + padding: 12px 0; + border-bottom: 1px solid var(--line); +} +.maintainer-list a span { + color: var(--text); +} +.maintainer-list small { + font-size: 12px; +} +.maintainer-list a:hover span { + color: var(--accent); +} +.article .panel { + padding: 20px; +} +.endpoint { + font: 14px var(--mono); +} +.insights-grid { + display: grid; + grid-template-columns: 1.15fr 1fr; + gap: 20px; + margin-bottom: 50px; +} +.distribution { + padding: 24px; +} +.distribution h2 { + font-size: 20px; + margin-top: 0; +} +.bar-row { + display: grid; + grid-template-columns: 125px 1fr 30px; + gap: 15px; + align-items: center; + font-size: 11px; + margin: 16px 0; +} +.bar-track { + height: 6px; + background: var(--line); + border-radius: 5px; + overflow: hidden; +} +.bar-track i { + display: block; + height: 100%; + background: var(--accent); + border-radius: 5px; +} +.insight-note { + color: var(--muted); + font-size: 11px; + line-height: 1.8; +} diff --git a/website/tests/data-contract.test.mjs b/website/tests/data-contract.test.mjs new file mode 100644 index 000000000..7103cac5b --- /dev/null +++ b/website/tests/data-contract.test.mjs @@ -0,0 +1,284 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { + parseEntry, + loadCatalog, + toApi, + toCsv, + toNavigator, + apiKeys, + csvHeader, + collections, +} from '../scripts/prepare-data.mjs'; + +const fixture = `Name: Example.exe +Description: 'A "quoted", description' +Author: Example Author +Created: 2020-01-02 +Commands: + - Command: |- + example.exe "first,second" + next line + Description: A command + Usecase: Example + Category: Execute + Privileges: User + MitreID: T1218.001 + OperatingSystem: Windows 11 + Tags: + - Application: 'GUI, "quoted"' + - Network: Remote +Acknowledgement: + - Person: Example + Handle: '@example' +`; +const record = () => + parseEntry(fixture, 'yml/OSBinaries/Example.yml', 'Binaries', 'Example'); + +// Independent parser makes commas, embedded quotes and multiline cells observable. +export function parseCsv(text) { + const rows = []; + let row = []; + let cell = ''; + let quoted = false; + for (let i = 0; i < text.length; i++) { + const char = text[i]; + if (char === '"') { + if (quoted && text[i + 1] === '"') { + cell += '"'; + i++; + } else quoted = !quoted; + } else if (char === ',' && !quoted) { + row.push(cell); + cell = ''; + } else if (char === '\n' && !quoted) { + row.push(cell); + rows.push(row); + row = []; + cell = ''; + } else cell += char; + } + assert.equal(quoted, false, 'CSV quotes must close'); + if (cell || row.length) { + row.push(cell); + rows.push(row); + } + return rows; +} + +test('legacy JSON has exact keys, null missing fields, original command fields, and canonical URL', () => { + const entry = record(); + const [api] = toApi([entry]); + assert.deepEqual(Object.keys(api), apiKeys); + assert.equal(api.Created, '2020-01-02'); + assert.equal(api.Full_Path, null); + assert.equal(api.Detection, null); + assert.equal(api.Resources, null); + assert.deepEqual(api.Commands, entry.Commands); + assert.equal( + api.url, + 'https://lolbas-project.github.io/lolbas/Binaries/Example/', + ); + assert.ok(!('Acknowledgement' in api)); + assert.ok(!('source' in api)); +}); + +test('CSV preserves 17 legacy columns and round trips multiline commands, quotes, commas and tags', () => { + const [header, row] = parseCsv(toCsv([record()])); + assert.deepEqual(header, csvHeader); + assert.equal(row.length, 17); + assert.equal(row[1], 'A "quoted", description'); + assert.equal(row[4], 'example.exe "first,second"\nnext line'); + assert.equal(row[14], 'Example (@example)'); + assert.equal(row[16], 'Application:GUI, "quoted",Network:Remote'); + assert.equal(row[11], ''); +}); + +test('rejects malformed YAML, duplicate keys, invalid command shape and unsafe route IDs', () => { + for (const text of [ + 'Name: [', + fixture + '\nName: Duplicate', + fixture.replace('Category: Execute', 'Category: [Execute]'), + fixture.replace('MitreID: T1218.001', 'MitreID: invalid'), + ]) { + assert.throws( + () => parseEntry(text, 'bad.yml', 'Binaries', 'Example'), + /bad.yml:/, + ); + } + assert.throws( + () => parseEntry(fixture, 'bad.yml', 'Binaries', '../escape'), + /filename/, + ); +}); + +test('collection mapping excludes HonorableMentions and duplicate binary filenames fail before generation', async () => { + const root = await mkdtemp(join(tmpdir(), 'lolbas-data-')); + try { + for (const folder of [...Object.keys(collections), 'HonorableMentions']) + await mkdir(join(root, folder)); + await writeFile(join(root, 'OSBinaries/Example.yml'), fixture); + await writeFile( + join(root, 'HonorableMentions/Ignored.yml'), + 'malformed: [', + ); + assert.equal((await loadCatalog(root)).length, 1); + await writeFile(join(root, 'OtherMSBinaries/eXample.yml'), fixture); + await assert.rejects( + loadCatalog(root), + /duplicate binary filename.*OSBinaries\/Example.yml/, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('extension gate rejects .yaml, uppercase .YML and unrelated files even in unpublished collections', async () => { + const root = await mkdtemp(join(tmpdir(), 'lolbas-extension-')); + try { + for (const folder of [ + ...Object.keys(collections), + 'HonorableMentions', + 'FutureCollection', + ]) + await mkdir(join(root, folder)); + await writeFile(join(root, 'OSBinaries/Example.yml'), fixture); + for (const path of [ + 'OSBinaries/Bad.yaml', + 'OSLibraries/Bad.YML', + 'HonorableMentions/Bad.txt', + 'FutureCollection/Bad.json', + 'Bad.md', + ]) { + await writeFile(join(root, path), fixture); + await assert.rejects( + loadCatalog(root), + /unexpected extension; use '.yml'/, + ); + await rm(join(root, path)); + } + assert.equal((await loadCatalog(root)).length, 1); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('distinct binary names and matching library stems are valid', async () => { + const root = await mkdtemp(join(tmpdir(), 'lolbas-names-')); + try { + for (const folder of Object.keys(collections)) + await mkdir(join(root, folder)); + for (const path of [ + 'OSBinaries/Example.yml', + 'OtherMSBinaries/Another.yml', + 'OSLibraries/Example.yml', + ]) + await writeFile(join(root, path), fixture); + assert.equal((await loadCatalog(root)).length, 3); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('extension validation is recursive and published collections cannot hide nested entries', async () => { + const root = await mkdtemp(join(tmpdir(), 'lolbas-nested-')); + try { + for (const folder of [ + ...Object.keys(collections), + 'HonorableMentions/nested/deeper', + ]) + await mkdir(join(root, folder), { recursive: true }); + await writeFile(join(root, 'OSBinaries/Example.yml'), fixture); + await writeFile( + join(root, 'HonorableMentions/nested/deeper/Bad.txt'), + fixture, + ); + await assert.rejects( + loadCatalog(root), + /HonorableMentions\/nested\/deeper\/Bad.txt: unexpected extension/, + ); + await rm(join(root, 'HonorableMentions/nested/deeper/Bad.txt')); + await writeFile( + join(root, 'HonorableMentions/nested/deeper/Allowed.yml'), + fixture, + ); + assert.equal((await loadCatalog(root)).length, 1); + for (const folder of Object.keys(collections)) { + await mkdir(join(root, folder, 'nested')); + await writeFile(join(root, folder, 'nested/Hidden.yml'), fixture); + await assert.rejects( + loadCatalog(root), + /nested directories are not supported in published collections/, + ); + await rm(join(root, folder, 'nested'), { recursive: true }); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('Created rejects impossible dates and accepts leap dates', () => { + for (const date of [ + '2023-02-29', + '2020-02-30', + '2020-13-01', + '2020-00-01', + '2020-1-02', + ]) { + assert.throws( + () => + parseEntry( + fixture.replace('2020-01-02', date), + 'bad.yml', + 'Binaries', + 'Example', + ), + /valid calendar date/, + ); + } + assert.equal( + parseEntry( + fixture.replace('2020-01-02', '2020-02-29'), + 'good.yml', + 'Binaries', + 'Example', + ).Created, + '2020-02-29', + ); +}); + +test('Navigator aggregates every command and marks parents with subtechniques', () => { + const entry = record(); + entry.Commands.push( + { ...entry.Commands[0], MitreID: 'T1218' }, + { ...entry.Commands[0] }, + ); + const layer = toNavigator([entry]); + assert.deepEqual(layer.techniques, [ + { techniqueID: 'T1218', score: 1, showSubtechniques: true }, + { techniqueID: 'T1218.001', score: 2 }, + ]); + assert.equal(layer.gradient.maxValue, 2); +}); + +test('canonical source produces a deterministic API and one CSV row per command', async () => { + const catalog = await loadCatalog(); + const rows = parseCsv(toCsv(catalog)); + assert.ok(catalog.length > 200); + assert.equal(new Set(catalog.map((entry) => entry.url)).size, catalog.length); + assert.equal( + rows.length - 1, + catalog.reduce((sum, entry) => sum + entry.Commands.length, 0), + ); + assert.ok(rows.every((row) => row.length === 17)); + assert.deepEqual(catalog, await loadCatalog()); + assert.ok( + catalog.some((entry) => entry.url === '/lolbas/Libraries/comsvcs/'), + ); + assert.ok( + catalog.every((entry) => !entry.source.includes('HonorableMentions')), + ); +}); diff --git a/website/tests/preview.spec.ts b/website/tests/preview.spec.ts new file mode 100644 index 000000000..c5a4e1bb4 --- /dev/null +++ b/website/tests/preview.spec.ts @@ -0,0 +1,62 @@ +import { test, expect } from '@playwright/test'; + +test('LOL-CI-001 hosted fork preview keeps navigation, assets and data under its base path', async ({ + page, + request, + context, +}) => { + const base = process.env.SITE_BASE!; + const errors: string[] = []; + page.on('pageerror', (error) => errors.push(error.message)); + page.on('console', (message) => { + if (message.type() === 'error') errors.push(message.text()); + }); + page.on('response', (response) => { + if (response.status() >= 400) errors.push(response.url()); + }); + await context.grantPermissions(['clipboard-read', 'clipboard-write']); + await page.goto(`${base}/`); + await expect( + page.getByRole('heading', { + name: 'Living Off The Land Binaries, Scripts and Libraries', + }), + ).toBeVisible(); + expect( + await page + .locator('.brand-logo') + .evaluate((img) => (img as HTMLImageElement).naturalWidth), + ).toBeGreaterThan(0); + for (const path of await page + .locator('a[href^="/"], img[src^="/"], link[href^="/"]') + .evaluateAll((nodes) => + nodes.map( + (node) => node.getAttribute('href') || node.getAttribute('src')!, + ), + )) + expect(path.startsWith(`${base}/`)).toBeTruthy(); + await page.getByRole('searchbox').fill('certutil'); + await expect(page.locator('[data-row]:visible')).toHaveCount(1); + await page.locator('.entry-name:visible').click(); + await expect(page).toHaveURL(new RegExp(`${base}/lolbas/Binaries/Certutil/`)); + const copy = page.locator('[data-copy]').first(); + const command = await copy.getAttribute('data-copy'); + await copy.click(); + await expect(copy).toHaveText('Copied ✓'); + expect(await page.evaluate(() => navigator.clipboard.readText())).toBe( + command, + ); + await page.getByRole('link', { name: 'Insights', exact: true }).click(); + await page.locator('.growth-chart a').last().click(); + await expect(page).toHaveURL(new RegExp(`${base}/\\?year=`)); + await expect(page.locator('#year-filter')).toContainText('Created in'); + await page.getByRole('link', { name: 'API', exact: true }).click(); + await page.locator('.endpoint').first().click(); + await expect(page).toHaveURL(new RegExp(`${base}/api/lolbas.json`)); + const response = await request.get(`${base}/api/lolbas.json`); + expect(response.ok()).toBeTruthy(); + const data = await response.json(); + expect(data.find((entry: any) => entry.Name === 'Certutil.exe').url).toBe( + 'https://lolbas-project.github.io/lolbas/Binaries/Certutil/', + ); + expect(errors).toEqual([]); +}); diff --git a/website/tests/release.test.mjs b/website/tests/release.test.mjs new file mode 100644 index 000000000..70c2a8b4d --- /dev/null +++ b/website/tests/release.test.mjs @@ -0,0 +1,242 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { test } from 'node:test'; +import { runInNewContext } from 'node:vm'; +import { parse } from 'yaml'; + +const readWorkflow = (name) => + parse( + readFileSync( + new URL(`../../.github/workflows/${name}`, import.meta.url), + 'utf8', + ), + ); +const site = readWorkflow('site.yml'); +const legacy = readWorkflow('gh-pages.yml'); +const canonical = 'LOLBAS-Project/LOLBAS'; +const publicationContext = (overrides = {}) => ({ + vars: { ASTRO_PUBLISH_ENABLED: 'true' }, + github: { + repository: canonical, + ref: 'refs/heads/master', + event_name: 'push', + event: { + repository: { fork: false }, + workflow_run: { + conclusion: 'success', + event: 'push', + head_branch: 'master', + }, + }, + }, + ...overrides, +}); +// Conditions use the shared JavaScript/GitHub boolean-expression subset. Evaluate +// the parsed workflow expression, so a weakened workflow changes these results. +const enabled = (job, context) => + Boolean( + runInNewContext( + job.if.replace(/^\s*\$\{\{([\s\S]*)\}\}\s*$/, '$1'), + context, + { timeout: 100 }, + ), + ); +const action = (steps, name) => + steps.find((step) => step.uses?.startsWith(`${name}@`)); + +test('fork branch pushes build review artifacts while upstream feature pushes skip', () => { + assert.deepEqual(site.on.push.branches, ['**']); + assert.deepEqual(site.on.pull_request.branches, ['master']); + for (const repository of [canonical, 'contributor/LOLBAS']) { + for (const ref of ['refs/heads/master', 'refs/heads/feature']) { + for (const event_name of ['push', 'pull_request', 'workflow_dispatch']) { + const context = publicationContext(); + Object.assign(context.github, { repository, ref, event_name }); + assert.equal( + enabled(site.jobs.build, context), + event_name !== 'push' || + repository !== canonical || + ref === 'refs/heads/master', + ); + if (repository !== canonical) { + assert.equal(enabled(site.jobs.deploy, context), false); + assert.equal(enabled(legacy.jobs.build, context), false); + } + } + } + } +}); + +test('only explicitly enabled canonical master push/manual runs may publish', () => { + for (const optIn of [undefined, '', 'false', 'TRUE', 'true']) { + for (const repository of [canonical, 'contributor/LOLBAS']) { + for (const ref of [ + 'refs/heads/master', + 'refs/heads/feature', + 'refs/tags/master', + ]) { + for (const event_name of [ + 'push', + 'pull_request', + 'pull_request_target', + 'workflow_run', + 'workflow_dispatch', + ]) { + const context = publicationContext(); + context.vars.ASTRO_PUBLISH_ENABLED = optIn; + Object.assign(context.github, { repository, ref, event_name }); + const allowed = + optIn === 'true' && + repository === canonical && + ref === 'refs/heads/master' && + ['push', 'workflow_dispatch'].includes(event_name); + assert.equal( + enabled(site.jobs.deploy, context), + allowed, + JSON.stringify({ optIn, repository, ref, event_name }), + ); + } + } + } + } +}); + +test('pull requests validate without exposing publication secrets or write permissions', () => { + assert.ok(site.on.pull_request); + assert.equal(site.on.pull_request_target, undefined); + assert.deepEqual(site.permissions, { contents: 'read' }); + for (const [name, job] of Object.entries(site.jobs)) { + if (name === 'deploy') continue; + assert.doesNotMatch( + JSON.stringify(job), + /secrets\.|actions-gh-pages|deploy-pages/, + ); + assert.ok(!Object.values(job.permissions ?? {}).includes('write')); + } + assert.equal(site.jobs.deploy.environment.name, 'github-pages'); +}); + +test('publication depends on a successful fully validated build', () => { + assert.deepEqual([site.jobs.deploy.needs].flat(), ['build']); + assert.doesNotMatch(site.jobs.deploy.if, /always\(|failure\(|cancelled\(/); + assert.equal(site.jobs.build['continue-on-error'], undefined); + const steps = site.jobs.build.steps; + const required = [ + /yamllint -c \.github\/\.yamllint yml\/\*\*\//, + /python(?:3)? \.github\/workflows\/validation\.py/, + /npm run check/, + /npm run test:data/, + /npm run build/, + /^npm test$/, + ]; + const uploadIndex = steps.findIndex( + (step) => step.with?.path === 'website/dist/', + ); + assert.ok(uploadIndex >= 0); + for (const command of required) { + const index = steps.findIndex((step) => command.test(step.run ?? '')); + assert.ok( + index >= 0 && index < uploadIndex, + `${command} must gate the artifact`, + ); + assert.equal(steps[index]['continue-on-error'], undefined); + assert.equal(steps[index].if, undefined); + } + assert.equal(steps[uploadIndex].if, undefined); + assert.equal(steps[uploadIndex]['continue-on-error'], undefined); + for (const step of steps.filter((item) => + /yamllint -c|validation\.py/.test(item.run ?? ''), + )) { + assert.equal(step['working-directory'], '.'); + } +}); + +test('publisher downloads exactly the tested artifact from its own run without rebuilding', () => { + const upload = site.jobs.build.steps.find( + (step) => step.with?.path === 'website/dist/', + ); + const download = action(site.jobs.deploy.steps, 'actions/download-artifact'); + const publish = action(site.jobs.deploy.steps, 'peaceiris/actions-gh-pages'); + assert.ok(upload?.uses.startsWith('actions/upload-artifact@')); + assert.equal(download.with.name, upload.with.name); + assert.match(download.with.name, /github\.sha/); + assert.match(download.with.name, /github\.run_attempt/); + assert.equal(download.with['run-id'], undefined); + assert.equal(download.with.repository, undefined); + assert.equal(publish.with.publish_dir, download.with.path); + assert.equal( + publish.with.external_repository, + 'LOLBAS-Project/LOLBAS-Project.github.io', + ); + assert.equal(publish.with.publish_branch, 'master'); + assert.equal(publish.with.destination_dir, undefined); + assert.equal(publish.with.keep_files, false); + assert.equal(publish.with.enable_jekyll, false); + assert.match(publish.with.deploy_key, /secrets\.ACTIONS_DEPLOY_KEY/); + assert.ok( + site.jobs.deploy.steps.some( + (step) => + /source-revision\.txt/.test(step.run ?? '') && + /GITHUB_SHA/.test(step.run), + ), + ); + assert.doesNotMatch( + JSON.stringify(site.jobs.deploy.steps), + /actions\/checkout|npm (?:ci|run build)/, + ); +}); + +test('legacy publisher is mutually exclusive and accepts only validated upstream pushes', () => { + for (const optIn of [undefined, '', 'false', 'true']) { + for (const conclusion of ['success', 'failure', 'cancelled', 'skipped']) { + for (const event of ['push', 'pull_request', 'workflow_dispatch']) { + for (const head_branch of ['master', 'feature']) { + for (const fork of [false, true]) { + for (const repository of [canonical, 'contributor/LOLBAS']) { + const context = publicationContext(); + context.vars.ASTRO_PUBLISH_ENABLED = optIn; + context.github.repository = repository; + context.github.event.repository.fork = fork; + context.github.event.workflow_run = { + conclusion, + event, + head_branch, + }; + const allowed = + optIn !== 'true' && + conclusion === 'success' && + event === 'push' && + head_branch === 'master' && + !fork && + repository === canonical; + assert.equal( + enabled(legacy.jobs.build, context), + allowed, + JSON.stringify({ + optIn, + conclusion, + event, + head_branch, + fork, + repository, + }), + ); + assert.ok( + !( + enabled(legacy.jobs.build, context) && + enabled(site.jobs.deploy, context) + ), + ); + } + } + } + } + } + } + const checkout = action(legacy.jobs.build.steps, 'actions/checkout'); + assert.equal(checkout.with.ref, '${{ github.event.workflow_run.head_sha }}'); + assert.equal( + legacy.jobs.build.concurrency.group, + site.jobs.deploy.concurrency.group, + ); +}); diff --git a/website/tests/site.spec.ts b/website/tests/site.spec.ts new file mode 100644 index 000000000..6a78d6e23 --- /dev/null +++ b/website/tests/site.spec.ts @@ -0,0 +1,386 @@ +import { test, expect } from '@playwright/test'; +import { loadCatalog } from '../scripts/prepare-data.mjs'; +import type { CatalogEntry } from '../src/lib/catalog'; +const source = (await loadCatalog()) as CatalogEntry[]; +const techniqueCount = new Set( + source.flatMap((e) => e.Commands.map((c) => c.MitreID)), +).size; + +test('LOL-SITE-002 search, legacy syntax, sorting, pagination and URL state', async ({ + page, +}) => { + await page.goto('/'); + const rows = page.locator('[data-row]:visible'); + await expect(rows).toHaveCount(25); + await page.getByRole('button', { name: 'Next →' }).click(); + await expect(page.locator('#page-info')).toContainText('Page 2'); + await page.reload(); + await expect(page.locator('#page-info')).toContainText('Page 2'); + await page.getByRole('searchbox').fill('certutil'); + await expect(rows).toHaveCount(1); + await expect(rows.first()).toContainText('Certutil.exe'); + await page.locator('#category').selectOption('Download'); + await expect(rows).toHaveCount(1); + await page.reload(); + await expect(page.getByRole('searchbox')).toHaveValue('certutil'); + await expect(page.locator('#category')).toHaveValue('Download'); + await expect(rows).toHaveCount(1); + await page.getByRole('button', { name: 'Reset' }).click(); + await page.getByRole('searchbox').fill('/download certutil'); + await expect(rows).toHaveCount(1); + await expect(rows.first()).toContainText('Certutil.exe'); + await page.getByRole('searchbox').fill('/uac bypass'); + await expect(rows).not.toHaveCount(0); + await expect(rows.first()).toContainText('UAC Bypass'); + await page.getByRole('searchbox').fill('#Script'); + await expect(rows).not.toHaveCount(0); + for (const row of await rows.all()) + await expect(row).toHaveAttribute('data-type', 'Scripts'); + await page.getByRole('button', { name: 'Reset' }).click(); + await page.locator('#sort').selectOption('newest'); + const dates = await rows.evaluateAll((rs) => + rs.map((r) => (r as HTMLElement).dataset.created!), + ); + expect(dates).toEqual([...dates].sort().reverse()); + await page.locator('#sort').selectOption('commands'); + const counts = await rows.evaluateAll((rs) => + rs.map((r) => Number((r as HTMLElement).dataset.commands)), + ); + expect(counts).toEqual([...counts].sort((a, b) => b - a)); + await page.getByRole('searchbox').fill('no-such-lolbas-zzzz'); + await expect(page.locator('#empty-state')).toBeVisible(); + await page.getByRole('button', { name: 'Clear all filters' }).click(); + await expect(rows).toHaveCount(25); + await page.getByRole('searchbox').fill('certutil'); + await page.locator('.entry-name:visible').click(); + await expect( + page.getByRole('heading', { name: 'Certutil.exe', exact: true }), + ).toBeVisible(); +}); + +test('LOL-SITE-002 combined technique/type/detection/date filters and chart deep links', async ({ + page, +}) => { + await page.goto( + '/?technique=T1105&type=Binaries&detection=yes&year=2018#catalog', + ); + const rows = page.locator('[data-row]:visible'); + expect(await rows.count()).toBeGreaterThan(0); + for (const row of await rows.all()) { + await expect(row).toHaveAttribute('data-type', 'Binaries'); + await expect(row).toHaveAttribute('data-detection', 'yes'); + await expect(row).toHaveAttribute('data-created', /^2018/); + await expect(row).toHaveAttribute('data-techniques', /T1105/); + } + await page.goto('/insights/'); + await page.locator('.growth-chart a').last().click(); + await expect(page.locator('#year-filter')).toContainText('Created in'); +}); + +test('LOL-SITE-003 legacy detail anchor, content and copy command', async ({ + page, + context, +}) => { + await context.grantPermissions(['clipboard-read', 'clipboard-write']); + await page.goto('/lolbas/Binaries/Certutil/#download'); + await expect( + page.getByRole('heading', { name: 'Certutil.exe', exact: true }), + ).toBeVisible(); + await expect(page.locator('[id="download"]')).toBeVisible(); + await expect(page.locator('main')).toContainText( + 'C:\\Windows\\System32\\certutil.exe', + ); + await expect(page.locator('main')).toContainText('Microsoft-CryptoAPI/10.0'); + const button = page.locator('[data-copy]').first(); + const value = await button.getAttribute('data-copy'); + await button.click(); + await expect(button).toHaveText('Copied ✓'); + expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(value); + await expect( + page.locator('a[href*="yml/OSBinaries/Certutil.yml"]').first(), + ).toBeVisible(); +}); + +test('LOL-API-001 production API and Navigator retain legacy contracts', async ({ + request, +}) => { + const data = await (await request.get('/api/lolbas.json')).json(); + expect(data).toHaveLength(source.length); + expect(Object.keys(data[0])).toEqual([ + 'Name', + 'Description', + 'Author', + 'Created', + 'Commands', + 'Full_Path', + 'Detection', + 'Resources', + 'url', + ]); + const cert = data.find((e: any) => e.Name === 'Certutil.exe'); + expect(cert.url).toBe( + 'https://lolbas-project.github.io/lolbas/Binaries/Certutil/', + ); + expect(cert.Commands).toEqual( + source.find((e) => e.Name === 'Certutil.exe')!.Commands, + ); + const csv = await (await request.get('/api/lolbas.csv')).text(); + expect(csv.split('\n')[0].trim()).toBe( + 'Filename,Description,Author,Date,Command,Command Description,Command Usecase,Command Category,Command Privileges,MITRE ATT&CK technique,Operating System,Paths,Detections,Resources,Acknowledgements,URL,Tags', + ); + const nav = await ( + await request.get('/mitre_attack_navigator_layer.json') + ).json(); + expect(nav.domain).toBe('enterprise-attack'); + expect(nav.techniques.length).toBe(techniqueCount); + expect((await request.get('/assets/lolbas-count.svg')).ok()).toBeTruthy(); +}); + +test('LOL-SITE-001 no-JavaScript catalog and detail navigation', async ({ + browser, +}) => { + const context = await browser.newContext({ javaScriptEnabled: false }); + const page = await context.newPage(); + await page.goto('http://127.0.0.1:4388/'); + await expect(page.locator('[data-row]')).toHaveCount(source.length); + await page + .locator('.entry-name') + .filter({ hasText: 'AddinUtil.exe' }) + .click(); + await expect( + page.getByRole('heading', { name: 'AddinUtil.exe', exact: true }), + ).toBeVisible(); + await context.close(); +}); + +test('LOL-SITE-001 mobile layout, keyboard access and clean runtime', async ({ + page, +}) => { + const errors: string[] = []; + page.on('pageerror', (e) => errors.push(e.message)); + page.on('console', (msg) => { + if (msg.type() === 'error') errors.push(msg.text()); + }); + page.on('response', (res) => { + if (res.status() >= 400) errors.push(`${res.status()} ${res.url()}`); + }); + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto('/'); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= innerWidth, + ), + ).toBeTruthy(); + await page.keyboard.press('/'); + await expect(page.getByRole('searchbox')).toBeFocused(); + await page.getByRole('searchbox').fill('AddinUtil'); + await page.locator('.entry-name:visible').click(); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= innerWidth, + ), + ).toBeTruthy(); + expect(errors).toEqual([]); +}); + +test('LOL-SITE-001 about button stays readable before hover and maintainers are linked first', async ({ + page, +}) => { + await page.goto('/about/'); + const button = page.getByRole('link', { name: 'Explore the repository' }); + await button.scrollIntoViewIfNeeded(); + const contrast = () => + button.evaluate((node) => { + const style = getComputedStyle(node); + const luminance = (color: string) => { + const channels = color + .match(/[\d.]+/g)! + .slice(0, 3) + .map(Number) + .map((n) => { + const channel = n / 255; + return channel <= 0.04045 + ? channel / 12.92 + : ((channel + 0.055) / 1.055) ** 2.4; + }); + return ( + channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722 + ); + }; + const foreground = luminance(style.color), + background = luminance(style.backgroundColor); + return ( + (Math.max(foreground, background) + 0.05) / + (Math.min(foreground, background) + 0.05) + ); + }); + expect(await contrast()).toBeGreaterThanOrEqual(4.5); + await button.hover(); + expect(await contrast()).toBeGreaterThanOrEqual(4.5); + await expect(page.locator('.article h2').first()).toHaveText( + 'Project maintainers', + ); + await expect(page.locator('.maintainer-list a')).toHaveCount(7); + for (const handle of [ + 'oddvarmoe', + 'bohops', + 'xenosCR', + 'ConsciousHacker', + 'liamsomerville', + 'Wietze', + '_josehelps', + ]) { + await expect( + page.getByRole('link', { name: new RegExp(`@${handle}`) }), + ).toHaveAttribute('href', `https://x.com/${handle}`); + } + await page.setViewportSize({ width: 390, height: 844 }); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= innerWidth, + ), + ).toBeTruthy(); +}); + +test('LOL-SITE-001 visual evidence for catalog, detail and mobile', async ({ + page, +}, testInfo) => { + const { mkdir, copyFile } = await import('node:fs/promises'); + const { resolve } = await import('node:path'); + async function capture(name: string) { + const path = testInfo.outputPath(`${name}.png`); + await page.screenshot({ path, fullPage: false }); + await testInfo.attach(name, { path, contentType: 'image/png' }); + if (!process.env.CI) { + await mkdir(resolve('../.context'), { recursive: true }); + await copyFile(path, resolve(`../.context/${name}.png`)); + } + } + await page.setViewportSize({ width: 1440, height: 1100 }); + await page.goto('/'); + for (const width of [1440, 900]) { + await page.setViewportSize({ width, height: 1100 }); + const terminal = await page.locator('.terminal').boundingBox(); + const label = await page.locator('.label-bottom').boundingBox(); + expect(terminal).not.toBeNull(); + expect(label).not.toBeNull(); + expect(terminal!.y + terminal!.height + 8).toBeLessThanOrEqual(label!.y); + } + await page.setViewportSize({ width: 1440, height: 1100 }); + await capture('lolbas-home'); + await page.locator('#catalog').scrollIntoViewIfNeeded(); + await capture('lolbas-catalog'); + await page.goto('/lolbas/Binaries/Addinutil/'); + await expect( + page.getByRole('heading', { name: 'AddinUtil.exe', exact: true }), + ).toBeVisible(); + await expect.poll(() => page.evaluate(() => scrollY)).toBe(0); + await capture('lolbas-detail'); + await page.goto('/about/'); + await expect.poll(() => page.evaluate(() => scrollY)).toBe(0); + await capture('lolbas-about'); + await page + .getByRole('link', { name: 'Explore the repository' }) + .scrollIntoViewIfNeeded(); + await capture('lolbas-about-button'); + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto('/'); + await capture('lolbas-mobile'); + await page.goto('/about/'); + await expect.poll(() => page.evaluate(() => scrollY)).toBe(0); + await capture('lolbas-about-mobile'); +}); + +test('LOL-SITE-002 styled filter menu supports pointer, keyboard and dismissal', async ({ + page, +}, testInfo) => { + await page.setViewportSize({ width: 1440, height: 900 }); + await page.goto('/?type=Binaries#catalog'); + const category = page.locator('#category'); + await category.click(); + const download = page.getByRole('option', { name: 'Download', exact: true }); + await expect(download).toBeVisible(); + const path = testInfo.outputPath('lolbas-dropdown.png'); + await page.screenshot({ path }); + await testInfo.attach('Styled filter menu', { + path, + contentType: 'image/png', + }); + if (!process.env.CI) { + const { copyFile, mkdir } = await import('node:fs/promises'); + const { resolve } = await import('node:path'); + await mkdir(resolve('../.context'), { recursive: true }); + await copyFile(path, resolve('../.context/lolbas-dropdown.png')); + } + await download.click(); + await expect(category).toHaveValue('Download'); + const rows = page.locator('[data-row]:visible'); + expect(await rows.count()).toBeGreaterThan(0); + for (const row of await rows.all()) + await expect(row).toHaveAttribute('data-categories', /Download/); + await page.locator('#sort').focus(); + await page.keyboard.press('Space'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + await expect(page.locator('#sort')).toHaveValue('newest'); + await category.click(); + await expect(download).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(category).toHaveValue('Download'); + await expect(category).toBeFocused(); +}); + +test('LOL-SITE-002 column sorting toggles direction, persists and follows filter/reset state', async ({ + page, +}) => { + await page.goto('/?category=Execute'); + const rows = page.locator('[data-row]:visible'); + const cases = [ + { key: 'name', attr: 'data-name', numeric: false }, + { key: 'functions', attr: 'data-functions', numeric: false }, + { key: 'type', attr: 'data-type-label', numeric: false }, + { key: 'attack', attr: 'data-attack', numeric: false }, + { key: 'detections', attr: 'data-detections', numeric: true }, + ]; + for (const { key, attr, numeric } of cases) { + const button = page.locator(`[data-sort="${key}"]`); + let previousDirection: string | null = null; + for (let click = 0; click < 2; click++) { + await page.getByRole('button', { name: 'Next →' }).click(); + await expect(page.locator('#page-info')).toContainText('Page 2'); + await button.click(); + await expect(page.locator('#page-info')).toContainText('Page 1'); + const direction = await button.locator('..').getAttribute('aria-sort'); + expect(['ascending', 'descending']).toContain(direction); + if (previousDirection) expect(direction).not.toBe(previousDirection); + previousDirection = direction; + const values = await rows.evaluateAll( + (elements, attribute) => + elements.map((el) => el.getAttribute(attribute)!), + attr, + ); + const expected = [...values].sort((a, b) => + numeric + ? Number(a) - Number(b) + : a.localeCompare(b, 'en', { numeric: key === 'attack' }), + ); + if (direction === 'descending') expected.reverse(); + expect(values).toEqual(expected); + const sort = await page.locator('#sort').inputValue(); + await page.reload(); + await expect(page.locator('#sort')).toHaveValue(sort); + await expect(button.locator('..')).toHaveAttribute( + 'aria-sort', + direction!, + ); + await expect(page.locator('#category')).toHaveValue('Execute'); + } + } + await page.getByRole('button', { name: 'Reset' }).click(); + await expect( + page.locator('[data-sort="name"]').locator('..'), + ).toHaveAttribute('aria-sort', 'ascending'); + await page.locator('[data-sort="name"]').focus(); + await page.keyboard.press('Enter'); + await expect(page.locator('#sort')).toHaveValue('reverse'); +}); diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 000000000..a20d33fb5 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { "resolveJsonModule": true }, + "include": [".astro/types.d.ts", "src/**/*", "tests/**/*.ts", "*.ts"], + "exclude": ["dist", "node_modules", "playwright-report", "test-results"] +}