diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5220e58 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2026 The Gatepost authors +# SPDX-License-Identifier: Apache-2.0 +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 100 + +[*.php] +indent_size = 4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..536d78c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: 2026 The Gatepost authors +# SPDX-License-Identifier: Apache-2.0 +name: ci + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The js submodule holds its own spec submodule. + submodules: recursive + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + # The oldest Python that the scripts in the spec repo support. + python-version: '3.11' + - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + with: + # pnpm check starts reuse and zizmor with uvx. No lock file keys a cache. + enable-cache: false + - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + # The newest PHP. The PHP package supports 8.1 and later, and its own CI tests 8.1. + php-version: '8.4' + tools: composer + coverage: none + - run: corepack enable + - run: pnpm install --frozen-lockfile + - run: pnpm sdk + - run: pnpm exec playwright install --with-deps chromium + - run: pnpm check + - if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + # The built site, so a reviewer can open the pages of a pull request. + name: site + path: dist + retention-days: 7 + + commits: + if: github.event_name == 'pull_request' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The checks read each commit from the base to the head. + fetch-depth: 0 + submodules: true + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + - run: corepack enable + - run: pnpm install --frozen-lockfile + - run: pnpm exec commitlint --from "$BASE" --to "$HEAD" + - name: Check each commit message for agent tells + run: | + for sha in $(git rev-list "$BASE..$HEAD"); do + git log -1 --format=%B "$sha" > "$RUNNER_TEMP/message" + python3 spec/scripts/check-tells --commit-msg "$RUNNER_TEMP/message" + done + + secrets: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # gitleaks must read the whole history. + fetch-depth: 0 + persist-credentials: false + - run: | + image=ghcr.io/gitleaks/gitleaks # v8.30.1 + digest=sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f + docker run --rm -v "$PWD:/repo" "$image@$digest" git /repo diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml new file mode 100644 index 0000000..98e7e47 --- /dev/null +++ b/.github/workflows/links.yml @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2026 The Gatepost authors +# SPDX-License-Identifier: Apache-2.0 +name: links + +# The links to other sites change without a commit here, so a weekly job checks them. A pull +# request checks only the links inside the site, which need no network. +on: + schedule: + - cron: '17 6 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + external: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: '8.4' + tools: composer + coverage: none + - run: corepack enable + - run: pnpm install --frozen-lockfile + - run: pnpm sdk + - run: pnpm build + - run: pnpm links:external diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..ac136c1 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2026 The Gatepost authors +# SPDX-License-Identifier: Apache-2.0 +name: pages + +# Each push to main publishes the site on GitHub Pages. The ci workflow checks the same commit. +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + # The newest PHP. The PHP package supports 8.1 and later, and its own CI tests 8.1. + php-version: '8.4' + tools: composer + coverage: none + - run: corepack enable + - run: pnpm install --frozen-lockfile + - run: pnpm sdk + - run: pnpm build + - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: dist + + deploy: + needs: build + # A manual run may start from any branch. Only main may publish. + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..f43adf0 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2026 The Gatepost authors +# SPDX-License-Identifier: Apache-2.0 +name: pull request + +# A squash merge puts the title and the body of the pull request on main as the commit message. +# An edit of the title or the body does not push a commit, so it must start this job again. +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: + contents: read + +concurrency: + group: pull-request-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + squash-message: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: true + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + - run: corepack enable + - run: pnpm install --frozen-lockfile + # The title and the body come through env, so that no text of the pull request ends up + # inside the script. + - name: Check the message that the squash merge puts on main + env: + TITLE: ${{ github.event.pull_request.title }} + NUMBER: ${{ github.event.pull_request.number }} + BODY: ${{ github.event.pull_request.body }} + run: | + printf '%s (#%s)\n\n%s\n' "$TITLE" "$NUMBER" "$BODY" > "$RUNNER_TEMP/message" + python3 spec/scripts/check-tells --squash-msg "$RUNNER_TEMP/message" --no-scope + pnpm exec commitlint --edit "$RUNNER_TEMP/message" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2477554 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2026 The Gatepost authors +# SPDX-License-Identifier: Apache-2.0 +node_modules/ +dist/ +temp/ +coverage/ +.astro/ +test-results/ +playwright-report/ +# What the build writes from the spec, the doc comments and the mock gateway. +src/content/docs/spec/ +src/content/docs/reference/ +src/generated/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1385bc8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "spec"] + path = spec + url = https://github.com/gatepost-dev/spec +[submodule "js"] + path = js + url = https://github.com/gatepost-dev/js +[submodule "php"] + path = php + url = https://github.com/gatepost-dev/php diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..4ac74b1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +pnpm-lock.yaml +spec +js +php +dist +temp +coverage +.astro +src/content/docs/spec +src/content/docs/reference diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..534bd00 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "printWidth": 100, + "plugins": ["prettier-plugin-astro"] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..32ec583 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,31 @@ +# AGENTS.md + +This repo is part of Gatepost, unofficial open-source developer tools for Nigeria's National Digital Postcode. This repo holds the docs site: the guides, the API reference, the spec pages and the playground. It reads the SDKs and the spec from the submodules `js`, `php` and `spec`. + +## Read first + +- `CODING_STANDARDS.md` and the two files it names hold the rules. Cite rule IDs, such as `API-3`, in reviews and commit bodies. +- `CONTEXT.md` is the glossary. Name things with its terms in code, tests, docs and commits. +- `spec/` holds the grammar, with its Interface section, and the vectors. The spec wins over code and over these docs. + +## Rules that no config file shows + +- If the task needs a public symbol that the Interface section of `spec/grammar.md` does not list, add nothing, not even an internal helper. Stop, and say that a spec change must come first (API-1, CS-8). +- Call only the documented NIPOST gateway endpoints. +- Commit only NIPOST's published test codes and synthetic values, such as `FC-01-Z99-ZZ-01`. +- Sign off each commit with `git commit -s`. The person who opens the pull request is the only author. Leave out co-author lines for AI tools. +- Change `main` only through pull requests. Keep the history of `main` and of other people's branches as it is: never force-push to them. +- If a commit lacks its sign-off, run `git rebase --signoff` with the base branch. Then push to your own branch with `--force-with-lease`. +- Send every call to the gateway through the client. The client limits how many calls run at once. +- Write prose in plain British English, with short sentences in the active voice. +- Some agent tools change a typed backslash-u escape, such as the one for an en dash, into the character itself. Build such characters with code, for example `chr(0x2013)` in Python or `String.fromCodePoint(0x2013)` in TypeScript. In data and prose, write `U+2013`. +- Load nothing from another website: no fonts, scripts, styles, images or analytics. The site tests fail on each one. + +## Done means all of these + +1. A failing test came first, and it passes now. +2. `pnpm check` passes. It runs the formatter, linters, tests, every code example, the accessibility checks, `check-tells`, REUSE and zizmor. +3. Each code example on a hand-written page runs in CI and shows the result that it gets. +4. A page that describes the public interface matches the spec and the SDK version in the submodules. +5. Each new domain term is in `CONTEXT.md`. +6. The diff touches only the lines that the task needs. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md new file mode 100644 index 0000000..ead5e31 --- /dev/null +++ b/CODING_STANDARDS.md @@ -0,0 +1,8 @@ +# Coding standards + +This repo follows two files. Both are part of its standard: + +1. `spec/standards/CODING_STANDARDS.md` holds the rules for every Gatepost repo. +2. `spec/standards/languages/typescript.md` holds the rules for TypeScript. + +Rules for this repo only: the site is not a package, so it has no change files, no changelog and no API report. Its commits use no scope, as in the spec repo (GIT-1). diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..fcc8182 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,174 @@ +# Gatepost + +Unofficial, open-source developer tools for Nigeria's National Digital Postcode. This glossary fixes one name for each concept, in every language and every repo. + +## Language + +### The postcode + +**Postcode**: +The 11-character code that NIPOST gives to one addressable building or location. +_Avoid_: digital address, address code, zip code, postal code + +**Legacy postcode**: +An old 6-digit NIPOST postcode. It names an area, not a building. +_Avoid_: old code, zip + +**Segment**: +One of the five parts of a postcode: state, LGA, district, area and unit. +_Avoid_: part, field, component, chunk + +**State**: +The first segment. Two letters that name one of the 36 states or the Federal Capital Territory. +_Avoid_: region, province + +**LGA**: +The second segment. Two digits that name a local government area within a state. +_Avoid_: council, local government, municipality + +**District**: +The third segment. Three characters that name a district within an LGA. +_Avoid_: sorting code, sector + +**Area**: +The fourth segment. Two letters that name the smallest polygon in the system. An area holds at most 99 units. +_Avoid_: zone, block, neighbourhood + +**Unit**: +The fifth segment. Two digits that name one building or delivery point within an area. +_Avoid_: building number, house number, delivery unit + +**Partial postcode**: +A postcode that stops after the state, LGA, district or area segment. +_Avoid_: prefix, incomplete code +Exception: `prefix` is the name of the first parameter of `contains`. + +**Precision**: +The most specific segment that a postcode or a result contains. +_Avoid_: level, granularity, resolution + +### Forms of a postcode + +**Compact form**: +The postcode with no separators, for example `EK01A03FK01`. +_Avoid_: raw, plain + +**Canonical form**: +The postcode with hyphens between segments, for example `EK-01-A03-FK-01`. +_Avoid_: formatted, standard form + +**Display form**: +The postcode with spaces between segments, for example `EK 01 A03 FK 01`. +_Avoid_: pretty, human form + +**Normalise**: +To change input in three steps, in this order: apply NFKC, remove the separators, and change the ASCII letters to upper case. `grammar.md` defines the steps. +_Avoid_: sanitise, canonicalise + +**Code point**: +One Unicode code point. It is not a UTF-16 unit, a byte or a grapheme cluster. For example, U+1D404 is one code point and two UTF-16 units. +_Avoid_: character (when you count input), char + +**Input limit**: +The most code points that `parse` and `isLegacy` read. It is `maxInputCodePoints` in `data/format.json`. Over the limit, `parse` gives `bad_length` and `isLegacy` gives false. Both functions apply the limit before they normalise. +_Avoid_: max length, length limit, size limit + +**Suggestion**: +The canonical form that `parse` offers with `unknown_state` or `bad_segment`, when a fix of look-alike characters makes the code parse. It is a hint, never a success. The word means this hint from `parse`. It does not mean the suggestions that NIPOST's autocomplete API returns. +_Avoid_: correction, autocorrect + +**Parse result**: +What `parse` returns: a postcode, or a parse error. The Interface section of `grammar.md` defines its fields. +_Avoid_: outcome, response + +**Parse error**: +The reason that `parse` rejected its input. It has a code, the failing segment and a suggestion. It is a value that `parse` returns, not an exception. +_Avoid_: validation error, exception + +### NIPOST's API + +**Gateway**: +NIPOST's public API at `api.postcode.gov.ng`. +_Avoid_: server, backend, endpoint (for the whole API) + +**Lookup**: +A request to the gateway for the facts about one postcode. +_Avoid_: query, fetch, resolve + +**Lookup level**: +How much data a lookup returns, from 1 for validity only to 5 for point geometry. +_Avoid_: tier, access level, depth + +**Reverse geocode**: +A request for the nearest unit to a coordinate. +_Avoid_: reverse lookup, locate + +**Accuracy**: +The radius in metres within which a GPS fix is likely correct. +_Avoid_: precision, error margin + +**Secret key**: +An API key for servers. It starts with `nipost_test_` or `nipost_live_`. +_Avoid_: private key, API secret + +**Publishable key**: +An API key that can ship inside an app or a web page. It starts with `nipost_pk_`. +_Avoid_: public key, client key + +**Widget**: +NIPOST's own postcode picker. Gatepost does not build widgets. +_Avoid_: using this word for a Gatepost component + +### Gatepost + +**Spec**: +The `spec` repo. It holds the grammar, the client contract, the data, the vectors, the fixtures, the completed OpenAPI file and the standards. +_Avoid_: schema, contract (for the repo) + +**Contract scenario**: +One shared test case in `spec/contract/`. It names the calls, the mock server's responses and the outcome that every client must reach. +_Avoid_: contract test (for the file), vector, fixture + +**Attempt**: +One request that a client sends for one call. A retry is the second or third attempt. +_Avoid_: try, request (when you count them) + +**Evidence**: +How Gatepost knows the shape of a gateway response: `observed` in its own calls, `documented` by NIPOST only, or `assumed` by the mock server. +_Avoid_: source, confidence, proof + +**Vector**: +One shared test case in `spec/vectors/` that every SDK must pass. +_Avoid_: fixture, example, golden file + +**Fixture**: +Synthetic data that the mock server returns. +_Avoid_: vector, stub, sample + +**Mock server**: +The local server that imitates the gateway with fixtures. +_Avoid_: fake API, stub server, sandbox + +**SDK**: +The core and the client for one language. +_Avoid_: library (for the pair), wrapper, binding + +**Core**: +The part of an SDK that parses and formats postcodes without network access. +_Avoid_: utils, base, common + +**Client**: +The part of an SDK that calls the gateway. +_Avoid_: API wrapper, service, fetcher + +**Field**: +A Gatepost UI component in which a user enters or picks a postcode. +_Avoid_: input, widget, picker + +**Confirm**: +To show the user what a postcode points to before the app stores it. +_Avoid_: verify, validate (for this step) + +**Tell**: +A habit that makes code look machine-written, such as very long lines. +_Avoid_: smell (smells are design problems), anti-pattern diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 0000000..137069b --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..bc94510 --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +Gatepost +Copyright 2026 The Gatepost authors + +This product is unofficial. The Nigerian Postal Service (NIPOST) did not make it and does not endorse it. +Gatepost uses the name NIPOST only to describe compatibility with NIPOST's National Digital Postcode. diff --git a/README.md b/README.md new file mode 100644 index 0000000..acf0b41 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ + + + gatepost + + +# Gatepost docs + +The docs site of Gatepost: guides for each SDK, the API reference, the spec and a playground. + +> Unofficial. Not made or endorsed by NIPOST. + +[![CI](https://github.com/gatepost-dev/docs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/gatepost-dev/docs/actions/workflows/ci.yml) +[![Licence](https://img.shields.io/badge/licence-Apache--2.0-blue)](LICENSE) + +## Install + +```sh +git clone --recurse-submodules https://github.com/gatepost-dev/docs +cd docs +pnpm install +pnpm sdk +``` + +`pnpm sdk` installs and builds the SDKs in the `js` and `php` submodules, because the site documents their source and runs its examples against them. + +## Quickstart + +```sh +pnpm dev +``` + +Astro serves the site at `http://localhost:4321/docs/`. The scripts disable Astro's usage data, so the build sends nothing. + +## What it does + +- Builds a static site with Astro and Starlight, for GitHub Pages. +- Writes the API reference from the TSDoc comments and the PHP doc comments of the submodules. +- Shows the grammar, the client contract and the glossary of the `spec` submodule. +- Runs each code example of each hand-written page against the SDKs and Gatepost's mock gateway. +- Checks each page with axe for WCAG 2.2 AA, in the light and the dark theme. +- Fails when a page loads anything from another website, when a page lacks the line that says that NIPOST did not make the site, or when a link inside the site breaks. The link check serves the site under `/docs/`, as GitHub Pages does. + +## Requirements + +| Requirement | Version | +| ----------------------------------- | ----------------------------------- | +| Node | 24.15 or later | +| pnpm | 12, through Corepack | +| PHP, for the PHP pages | 8.1 or later, with Composer | +| Python, for `check-tells` and REUSE | 3.11 or later, with uv | +| Gatepost spec | the version in the `spec` submodule | + +`pnpm check` runs every check that CI runs. The browser tests need Chromium: run `pnpm exec playwright install chromium` once. + +## Docs + +The site itself is the docs. CI uploads the built site of each pull request as an artifact. + +## Support + +Ask questions and report problems with a page in GitHub Issues. Report security problems privately, as [`SECURITY.md`](https://github.com/gatepost-dev/.github/blob/main/SECURITY.md) describes. + +## Contributing + +Read [`CONTRIBUTING.md`](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md) before you open a pull request. + +## Licence + +Apache-2.0. See `LICENSE` and `NOTICE`. diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..66a6efe --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,16 @@ +version = 1 + +[[annotations]] +path = [ + "**/*.md", "**/*.mdx", "**/*.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", ".prettierignore", + "**/NOTICE", "CLAUDE.md", ".gitmodules", "public/*.png", +] +precedence = "aggregate" +SPDX-FileCopyrightText = "2026 The Gatepost authors" +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = ["src/assets/*.svg", "public/favicon.svg"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2026 The Gatepost authors" +SPDX-License-Identifier = "Apache-2.0" diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..205daad --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { execFileSync } from 'node:child_process'; +import starlight from '@astrojs/starlight'; +import { defineConfig } from 'astro/config'; +import { createStarlightTypeDocPlugin } from 'starlight-typedoc'; +import { writeClientSamples } from './scripts/client-samples.ts'; +import { writeSpecPages } from './scripts/spec-pages.ts'; + +// GitHub Pages serves the org's project site under the repo name. A custom domain changes the +// site and sets the base to '/'. +const site = 'https://gatepost-dev.github.io'; +const base = '/docs'; + +const [coreTypeDoc, coreTypeDocGroup] = createStarlightTypeDocPlugin(); +const [clientTypeDoc, clientTypeDocGroup] = createStarlightTypeDocPlugin(); + +// The API pages of a package, generated from the TSDoc comments of its source. +function typeDocOf(name) { + return { + entryPoints: [`js/packages/${name}/src/index.ts`], + tsconfig: `js/packages/${name}/tsconfig.json`, + output: `reference/js/${name}`, + sidebar: { label: `@gatepost/${name}`, collapsed: true }, + typeDoc: { + excludeInternal: true, + readme: 'none', + disableSources: true, + entryFileName: 'index', + }, + }; +} + +export default defineConfig({ + site, + base, + trailingSlash: 'always', + integrations: [ + { + name: 'gatepost-generated-pages', + hooks: { + 'astro:config:setup': async ({ command }) => { + // The preview serves the built site and reads none of the generated files. + if (command === 'preview') return; + writeSpecPages(import.meta.dirname); + await writeClientSamples(import.meta.dirname); + try { + execFileSync( + 'php', + ['scripts/php-reference.php', 'php', 'src/content/docs/reference/php'], + { cwd: import.meta.dirname, stdio: ['ignore', 'inherit', 'inherit'] }, + ); + } catch (error) { + // The reference script prints its own message. A missing PHP gives only ENOENT. + if (error.code === 'ENOENT') { + throw new Error('PHP 8.1 or later is needed to build the PHP reference.', { + cause: error, + }); + } + throw error; + } + }, + }, + }, + starlight({ + title: 'Gatepost', + description: "Unofficial developer tools for Nigeria's digital postcode.", + // The title stays in the page for screen readers, so the logo needs no alt text of its own. + logo: { + light: './src/assets/gatepost-lockup.svg', + dark: './src/assets/gatepost-lockup-dark.svg', + replacesTitle: true, + }, + favicon: '/favicon.svg', + head: [ + { tag: 'link', attrs: { rel: 'icon', href: `${base}/favicon-32.png`, sizes: '32x32' } }, + { + tag: 'link', + attrs: { rel: 'apple-touch-icon', href: `${base}/apple-touch-icon-180.png` }, + }, + // A shared link shows this card. The address is absolute, because a card reader needs it. + { tag: 'meta', attrs: { property: 'og:image', content: `${site}${base}/og-image.png` } }, + { tag: 'meta', attrs: { name: 'twitter:image', content: `${site}${base}/og-image.png` } }, + ], + locales: { root: { label: 'English', lang: 'en-GB' } }, + social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/gatepost-dev' }], + editLink: { baseUrl: 'https://github.com/gatepost-dev/docs/edit/main/' }, + customCss: [ + '@fontsource/overpass/400.css', + '@fontsource/overpass/600.css', + './src/styles/theme.css', + ], + components: { Footer: './src/components/Footer.astro' }, + credits: false, + // A code block that scrolls sideways needs a focus stop for keyboard users. Wrapped lines + // need none, and they fit a phone screen. + expressiveCode: { defaultProps: { wrap: true } }, + plugins: [coreTypeDoc(typeDocOf('core')), clientTypeDoc(typeDocOf('client'))], + sidebar: [ + { label: 'Start', items: ['start', 'playground'] }, + { label: 'Guides', items: [{ autogenerate: { directory: 'guides' } }] }, + { label: 'Spec', items: [{ autogenerate: { directory: 'spec' } }] }, + { + label: 'API reference', + items: [ + coreTypeDocGroup, + clientTypeDocGroup, + { + label: 'gatepost/postcode', + collapsed: true, + items: [{ autogenerate: { directory: 'reference/php' } }], + }, + ], + }, + { label: 'Project', items: ['readiness', 'privacy'] }, + ], + }), + ], +}); diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..4fca655 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// The site is not a package, so its commits use no scope, as in the spec repo (GIT-1). +export default { + extends: ['@commitlint/config-conventional'], + rules: { + 'header-max-length': [2, 'always', 72], + 'scope-empty': [2, 'always'], + 'signed-off-by': [2, 'always', 'Signed-off-by:'], + }, +}; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..f1456d1 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import eslint from '@eslint/js'; +import comments from '@eslint-community/eslint-plugin-eslint-comments/configs'; +import { defineConfig } from 'eslint/config'; +import tseslint from 'typescript-eslint'; + +export default defineConfig( + { + ignores: [ + 'dist/**', + 'temp/**', + 'coverage/**', + '.astro/**', + 'spec/**', + 'js/**', + 'php/**', + 'test-results/**', + 'playwright-report/**', + ], + }, + eslint.configs.recommended, + tseslint.configs.strictTypeChecked, + tseslint.configs.stylisticTypeChecked, + comments.recommended, + { + languageOptions: { + parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname }, + }, + rules: { + '@eslint-community/eslint-comments/require-description': 'error', + 'max-depth': ['error', 3], + complexity: ['error', 10], + 'max-params': ['error', 4], + 'no-empty': ['error', { allowEmptyCatch: false }], + 'no-nested-ternary': 'error', + '@typescript-eslint/require-await': 'error', + '@typescript-eslint/explicit-module-boundary-types': 'error', + 'no-restricted-syntax': ['error', 'TSEnumDeclaration', 'TSModuleDeclaration'], + 'no-restricted-exports': [ + 'error', + { + restrictDefaultExports: { + direct: true, + named: true, + defaultFrom: true, + namedFrom: true, + namespaceFrom: true, + }, + }, + ], + }, + }, + { + // Code that runs in the reader's browser writes nothing to the console (TS-12). + files: ['src/**/*.ts'], + rules: { 'no-console': 'error' }, + }, + { + files: ['**/*.config.ts', '**/*.js', '**/*.mjs'], + extends: [tseslint.configs.disableTypeChecked], + rules: { 'no-restricted-exports': 'off' }, + }, +); diff --git a/js b/js new file mode 160000 index 0000000..3ba7fc4 --- /dev/null +++ b/js @@ -0,0 +1 @@ +Subproject commit 3ba7fc43c4f1c15685987e88ce20cd190f95065d diff --git a/package.json b/package.json new file mode 100644 index 0000000..a754834 --- /dev/null +++ b/package.json @@ -0,0 +1,70 @@ +{ + "name": "gatepost-docs", + "private": true, + "type": "module", + "license": "Apache-2.0", + "packageManager": "pnpm@12.8.1", + "engines": { + "node": ">=24.15.0" + }, + "scripts": { + "sdk": "run-s sdk:js sdk:js:build sdk:php", + "sdk:js": "pnpm --dir js install --frozen-lockfile", + "sdk:js:build": "pnpm --dir js --filter @gatepost/core --filter @gatepost/client run build", + "sdk:php": "composer --working-dir=php install --no-interaction --no-progress", + "dev": "ASTRO_TELEMETRY_DISABLED=1 astro dev", + "build": "ASTRO_TELEMETRY_DISABLED=1 astro build", + "preview": "ASTRO_TELEMETRY_DISABLED=1 astro preview", + "check": "run-s check:source check:site check:policy", + "check:source": "run-s format:check lint typecheck php:check test", + "check:site": "run-s build test:site links test:browser", + "check:policy": "run-s tells prose context:check licences reuse:check workflows:check", + "format": "prettier --write .", + "format:check": "prettier --check .", + "lint": "eslint . --max-warnings 0", + "typecheck": "ASTRO_TELEMETRY_DISABLED=1 astro check", + "php:check": "php php/vendor/bin/phpstan analyse --no-progress --level max scripts", + "test": "vitest run --project unit --coverage", + "test:site": "vitest run --project site", + "links": "node scripts/check-links.ts", + "links:external": "node scripts/check-links.ts --external", + "tells": "python3 spec/scripts/check-tells", + "prose": "node scripts/check-prose.ts", + "context:check": "cmp CONTEXT.md spec/CONTEXT.md", + "licences": "node scripts/check-licences.ts", + "reuse:check": "uvx --with charset-normalizer==3.5.2 reuse@6.2.0 lint", + "workflows:check": "uvx zizmor@1.30.1 --offline .github/workflows", + "test:browser": "playwright test" + }, + "dependencies": { + "@astrojs/starlight": "^0.42.5", + "@fontsource/overpass": "^5.3.0", + "@gatepost/client": "link:js/packages/client", + "@gatepost/core": "link:js/packages/core", + "astro": "^7.3.5", + "starlight-typedoc": "^0.23.1", + "typedoc": "^0.28.20", + "typedoc-plugin-markdown": "^4.13.1" + }, + "devDependencies": { + "@astrojs/check": "^0.9.10", + "@axe-core/playwright": "^4.13.0", + "@commitlint/cli": "^21.2.3", + "@commitlint/config-conventional": "^21.2.3", + "@eslint-community/eslint-plugin-eslint-comments": "^4.8.1", + "@eslint/js": "^10.0.1", + "@gatepost/mock-server": "link:js/packages/mock-server", + "@playwright/test": "^1.63.0", + "@types/node": "^24.19.1", + "@vitest/coverage-v8": "^5.0.3", + "eslint": "^10.11.0", + "linkinator": "^8.1.0", + "npm-run-all2": "^9.0.3", + "prettier": "^3.9.9", + "prettier-plugin-astro": "^1.1.0", + "typescript": "6.0.3", + "typescript-eslint": "^8.71.0", + "vitest": "^5.0.3", + "yaml": "^2.9.1" + } +} diff --git a/php b/php new file mode 160000 index 0000000..831f332 --- /dev/null +++ b/php @@ -0,0 +1 @@ +Subproject commit 831f332f5f6441cd051094e3a5a09843978c9f51 diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..05fd66a --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { defineConfig, devices } from '@playwright/test'; + +const PORT = 4321; + +// The browser tests read the built site through `astro preview`, which serves it under the base +// path, as GitHub Pages does. Each test runs once in the light theme and once in the dark one. +export default defineConfig({ + testDir: 'test/browser', + fullyParallel: true, + forbidOnly: true, + reporter: 'list', + use: { baseURL: `http://localhost:${String(PORT)}` }, + projects: [ + { name: 'light', use: { ...devices['Desktop Chrome'], colorScheme: 'light' } }, + { name: 'dark', use: { ...devices['Desktop Chrome'], colorScheme: 'dark' } }, + ], + webServer: { + command: `pnpm exec astro preview --port ${String(PORT)} --ignore-lock`, + env: { ASTRO_TELEMETRY_DISABLED: '1' }, + url: `http://localhost:${String(PORT)}/docs/`, + reuseExistingServer: false, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..a54a608 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6350 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.8.1 + version: 12.8.1 + +packages: + + '@pnpm/exe.android-arm64@12.8.1': + resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.8.1': + resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.8.1': + resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.8.1': + resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.8.1': + resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.8.1': + resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.8.1': + resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.8.1': + resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.8.1': + resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.8.1': + resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.8.1': + resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.8.1': + resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.8.1': + resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.8.1': + resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==} + cpu: [x64] + os: [win32] + + pnpm@12.8.1: + resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.8.1': + optional: true + + '@pnpm/exe.android-x64@12.8.1': + optional: true + + '@pnpm/exe.darwin-arm64@12.8.1': + optional: true + + '@pnpm/exe.darwin-x64@12.8.1': + optional: true + + '@pnpm/exe.freebsd-x64@12.8.1': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.8.1': + optional: true + + '@pnpm/exe.linux-arm64@12.8.1': + optional: true + + '@pnpm/exe.linux-ppc64@12.8.1': + optional: true + + '@pnpm/exe.linux-riscv64@12.8.1': + optional: true + + '@pnpm/exe.linux-s390x@12.8.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.8.1': + optional: true + + '@pnpm/exe.linux-x64@12.8.1': + optional: true + + '@pnpm/exe.win32-arm64@12.8.1': + optional: true + + '@pnpm/exe.win32-x64@12.8.1': + optional: true + + pnpm@12.8.1: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.8.1 + '@pnpm/exe.android-x64': 12.8.1 + '@pnpm/exe.darwin-arm64': 12.8.1 + '@pnpm/exe.darwin-x64': 12.8.1 + '@pnpm/exe.freebsd-x64': 12.8.1 + '@pnpm/exe.linux-arm64': 12.8.1 + '@pnpm/exe.linux-arm64-musl': 12.8.1 + '@pnpm/exe.linux-ppc64': 12.8.1 + '@pnpm/exe.linux-riscv64': 12.8.1 + '@pnpm/exe.linux-s390x': 12.8.1 + '@pnpm/exe.linux-x64': 12.8.1 + '@pnpm/exe.linux-x64-musl': 12.8.1 + '@pnpm/exe.win32-arm64': 12.8.1 + '@pnpm/exe.win32-x64': 12.8.1 + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@astrojs/starlight': + specifier: ^0.42.5 + version: 0.42.5(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1))(typescript@6.0.3) + '@fontsource/overpass': + specifier: ^5.3.0 + version: 5.3.0 + '@gatepost/client': + specifier: link:js/packages/client + version: link:js/packages/client + '@gatepost/core': + specifier: link:js/packages/core + version: link:js/packages/core + astro: + specifier: ^7.3.5 + version: 7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1) + starlight-typedoc: + specifier: ^0.23.1 + version: 0.23.1(@astrojs/starlight@0.42.5(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1))(typescript@6.0.3))(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1))(typedoc-plugin-markdown@4.13.1(typedoc@0.28.20(typescript@6.0.3)))(typedoc@0.28.20(typescript@6.0.3)) + typedoc: + specifier: ^0.28.20 + version: 0.28.20(typescript@6.0.3) + typedoc-plugin-markdown: + specifier: ^4.13.1 + version: 4.13.1(typedoc@0.28.20(typescript@6.0.3)) + devDependencies: + '@astrojs/check': + specifier: ^0.9.10 + version: 0.9.10(@emnapi/runtime@1.11.3)(prettier-plugin-astro@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(prettier@3.9.9))(prettier@3.9.9)(typescript@6.0.3) + '@axe-core/playwright': + specifier: ^4.13.0 + version: 4.13.0(playwright-core@1.63.0) + '@commitlint/cli': + specifier: ^21.2.3 + version: 21.2.3(@types/node@24.19.1)(conventional-commits-parser@7.1.2)(typescript@6.0.3) + '@commitlint/config-conventional': + specifier: ^21.2.3 + version: 21.2.3 + '@eslint-community/eslint-plugin-eslint-comments': + specifier: ^4.8.1 + version: 4.8.1(eslint@10.11.0(jiti@2.6.1)) + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.11.0(jiti@2.6.1)) + '@gatepost/mock-server': + specifier: link:js/packages/mock-server + version: link:js/packages/mock-server + '@playwright/test': + specifier: ^1.63.0 + version: 1.63.0 + '@types/node': + specifier: ^24.19.1 + version: 24.19.1 + '@vitest/coverage-v8': + specifier: ^5.0.3 + version: 5.0.3(vitest@5.0.3) + eslint: + specifier: ^10.11.0 + version: 10.11.0(jiti@2.6.1) + linkinator: + specifier: ^8.1.0 + version: 8.1.0 + npm-run-all2: + specifier: ^9.0.3 + version: 9.0.3 + prettier: + specifier: ^3.9.9 + version: 3.9.9 + prettier-plugin-astro: + specifier: ^1.1.0 + version: 1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(prettier@3.9.9) + typescript: + specifier: 6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: ^8.71.0 + version: 8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3) + vitest: + specifier: ^5.0.3 + version: 5.0.3(@types/node@24.19.1)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1)) + yaml: + specifier: ^2.9.1 + version: 2.9.1 + +packages: + + '@astrojs/astro2tsx@0.1.2': + resolution: {integrity: sha512-Uj/M9zbrhu5cIZEvWAGTbQJRKSxXWJ/E59BZwt7F+chrKhbfttW+9rkS/l+1QxoJP1KU3UnpM62M7yszfAI0WA==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@astrojs/check@0.9.10': + resolution: {integrity: sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 || ^6.0.0 + + '@astrojs/compiler-binding-android-arm64@0.4.1': + resolution: {integrity: sha512-Wtx2DZORuNTLASLLZ8hlrjFaJe1LUloatcCn9EPq7knBhRpjMGXeJANIJMeBZG4igvz7x+3nyAJM2iwCSW9xog==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@astrojs/compiler-binding-android-arm64@0.5.1': + resolution: {integrity: sha512-nMb2nMIJtcWLNH+fV75Enmi+jp4tIOYOfsv4XtpgdOWkqc9P4uSSdK5u1xb340AzrXo5V18SxLl/nc3vcqtamQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@astrojs/compiler-binding-darwin-arm64@0.4.1': + resolution: {integrity: sha512-7iwXcU+hB60Y1SvOfnWHNioT72gDsDQKaDipXh2Onwp/TvQMozhbvhy13wJR8Af940a0h6PbddCeJFpj791UDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@astrojs/compiler-binding-darwin-arm64@0.5.1': + resolution: {integrity: sha512-5k4+nZ/WRt+dcVow4z7rBuI7iv9A58h/uQ91YJe1PrTivbcCkq3u2Ynx/AZJwII4CsZawBCeGpojkFjutZBhBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@astrojs/compiler-binding-darwin-x64@0.4.1': + resolution: {integrity: sha512-pifjICos49RXhSVKSDu5gDB0/8j0O7DmxSp1DaWW2PyLG5ekwSkcrfOZto5JqhdI7QYMZB3XOiXpsdOXpS4npA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@astrojs/compiler-binding-darwin-x64@0.5.1': + resolution: {integrity: sha512-0Buw+YST9Ff2EqQyhRMQujuFq/dijLw3zXWhsvI0dJ2pGrT38iVoxTEca2eBX/PfhlwkVWSRJEKO5gMqKHyeHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@astrojs/compiler-binding-linux-arm64-gnu@0.4.1': + resolution: {integrity: sha512-hsZMlNWc6LqWHh9LlSwTq1/XCY7pxA5rkkKIdZRV8mgbOz9OwLvjEoe5Jry13eIZjMUQO8XnQyzk8o/Pjw2YFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@astrojs/compiler-binding-linux-arm64-gnu@0.5.1': + resolution: {integrity: sha512-x5LRpyCoMKuu3INr18cn42YhwSOaRpp9b5Ny3eJJ/sWVeejC16Zeshu1JlvlSmEZACLeWxU5JM0RX6T6nI+YyA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@astrojs/compiler-binding-linux-arm64-musl@0.4.1': + resolution: {integrity: sha512-a3EW27f+Z9uf57vYmpLgUriA1y9wDhKz57PT8D8XlFZt2HZEWhUxKuxI1jvKVI9p8okYee11WXEm/FHY1G9yoQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@astrojs/compiler-binding-linux-arm64-musl@0.5.1': + resolution: {integrity: sha512-mL7tQEuDk3rGfaKmrO63nw6WBy1TPVioQGmVyjcAr+C+a/k3SVMYBytNSMqH9mZPT/Swj30JaZ2N842Z1NnOEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@astrojs/compiler-binding-linux-x64-gnu@0.4.1': + resolution: {integrity: sha512-XruPTKB/SS4DFzeAjQ1ZyM0ieHDjapHkOSnnzRApZ4FXY2YAI7UY3mETjfJiIS/acyZyrnPH5jObv1gATTdUJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@astrojs/compiler-binding-linux-x64-gnu@0.5.1': + resolution: {integrity: sha512-uLGc9jtxwiDe4cQyhNPQim4ECiB/YzbWlXssfLAv8r374kArfTwBHjwR2+j+XVyF71yCx5mF9YNRclRqdNBgeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@astrojs/compiler-binding-linux-x64-musl@0.4.1': + resolution: {integrity: sha512-E8j7lGanqNpudtkUbsFaOLJhmwFe4Ecqux7K9hkj4wULIPNZRM2bYZNcMSj8r1oWvaUiwZl8LaaNwv3+14jqhw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@astrojs/compiler-binding-linux-x64-musl@0.5.1': + resolution: {integrity: sha512-G0cywinaQUc8v1MrytvYKUX7svTDMmYtOCZfxjwKPbJi59zIqM+Ll0rWgO0GQWLZH3XoPYo19ZwlP3Xt9ep9YA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@astrojs/compiler-binding-wasm32-wasi@0.4.1': + resolution: {integrity: sha512-vNLxIv41ZE+nmPFtw24cRW5ZXG+ZPcAGtgigtjCP8lvcxKI7u2GrXoMsCgEH5fMw8WCEJpwLvsVBg15rJKAkyA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@astrojs/compiler-binding-wasm32-wasi@0.5.1': + resolution: {integrity: sha512-yMhLL8asmLydoAdnEZxAw6eLfNDs3YJl6jqn0pjPt6zrb2U5qIDLnii4/twThpIFODKkQ6YDpBRTOz/vSfZCxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@astrojs/compiler-binding-win32-arm64-msvc@0.4.1': + resolution: {integrity: sha512-/xTDgRsFDoTh0/+KFXJ74DY4Jqn8CoSHwa8UP5RAblHfnRHkenwfSl4zYDET61O9vXKLk09UzHjjq09yP20/AQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@astrojs/compiler-binding-win32-arm64-msvc@0.5.1': + resolution: {integrity: sha512-aClIG6poFt3Wf/bZykrEAX2nAnKvBHW6RXiKUa69QjBtWH0/FOlOAjnFcxRNlhej2AMWNYFx0o+LGjwMcH8P7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@astrojs/compiler-binding-win32-x64-msvc@0.4.1': + resolution: {integrity: sha512-0x5J6iHZO0Fjo/CVzoIbXKzWnGESftDbEUQ/oXQ61HhUAFoR7+T1jfToZSNAS+gYyBXHSTpMWYC7lzarbhClcg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@astrojs/compiler-binding-win32-x64-msvc@0.5.1': + resolution: {integrity: sha512-/E6uBCagY70YpWqssBC+/FuniNSloeQPRgj7l3LDWiqjxIyTLnIgX1sTt18FU7jrigetpSnC9cHAM6b6IxSgPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@astrojs/compiler-binding@0.4.1': + resolution: {integrity: sha512-ZYVDW1P58OXyxqZbOMp+/2U9KA7y2esIx9r4pWdpZ/l6fCZhaw4ul+D7EgpRVxYLkyTWqzANbTTUFGPFP8zDHA==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@astrojs/compiler-binding@0.5.1': + resolution: {integrity: sha512-oczyolLUl9mNrtuBuNjuvn5thpHrFLXAHAWGTRg0JDHQ8cGVsVeFekOu2uSoQGgainJIi+A3/9m0DyYwz3Nlnw==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@astrojs/compiler-rs@0.4.1': + resolution: {integrity: sha512-//NmAuRhy7eU9iP0ceI5Pymy55M+nUZ//kS6XeOyhBWdDib2KbJOyFaVJ52k7sDkJwTKVox7oPu3SpYiVnGm5A==} + engines: {node: '>=22.12.0'} + + '@astrojs/compiler-rs@0.5.1': + resolution: {integrity: sha512-1k1acnX7DepIiea9U9/Y+d1hdAFfylu1ZQQkkYgRSwBOBAsXInolyZuh/2tax+83vu/A+hOYP6P3+265RvMTvw==} + engines: {node: '>=22.12.0'} + + '@astrojs/internal-helpers@0.11.0': + resolution: {integrity: sha512-3rzxJ+xbo0+8YyqOzLziIN32wmsHdCjEVz2sGOpRxJ+Ben/KiLph4ItxBy1abEL+E8fkRzqjg0rfXmaHJGw9JA==} + + '@astrojs/language-server@2.17.1': + resolution: {integrity: sha512-gG7bvMVNIXgoSe1pM9dI1HsSDLU1EIc/dSAYQ0DuReLyLkOBjsiqvaRYgQltWaldLxh0bNEqjgreLfjd9OnUxQ==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + + '@astrojs/markdown-satteri@0.4.2': + resolution: {integrity: sha512-g4QehXnB/MzxQWnSf17NciFInMqEJy9948p8cc7zGg/r7995m0Ln5PMr3Xlnj24iey1LchaoZ3SzTOa3D4P/3w==} + + '@astrojs/mdx@8.0.2': + resolution: {integrity: sha512-WedIVP2Bu8fXSzxb3ZYZ8xDrWWjf2SRKCneh52Llhoa+O3lcpHPhoZVP3FerlivUaH/0LJz2yVaCNvMdg0u8Bw==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@astrojs/markdown-remark': ^7.3.0 + '@astrojs/markdown-satteri': ^0.4.0 + astro: ^7.2.10 + peerDependenciesMeta: + '@astrojs/markdown-remark': + optional: true + + '@astrojs/prism@4.0.2': + resolution: {integrity: sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==} + engines: {node: '>=22.12.0'} + + '@astrojs/sitemap@3.7.4': + resolution: {integrity: sha512-LbKNC24bdUWcQf/pThB6qLlSqHojxGjZDURIzFocY8rlWnAn2t74nnhnK6S5x0NHriHoAduLEpVjRykmeGiVvA==} + + '@astrojs/starlight@0.42.5': + resolution: {integrity: sha512-S6hjUexk9rrKtjJF3WR8T9dM1woNR7X2XU8e3wj4OpRT4X15AXk/mzPjWe/Qd5+P8whHj2+ves+k06qirBzimg==} + peerDependencies: + '@astrojs/markdown-remark': ^7.3.0 + astro: ^7.2.10 + peerDependenciesMeta: + '@astrojs/markdown-remark': + optional: true + + '@astrojs/telemetry@3.3.3': + resolution: {integrity: sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/yaml2ts@0.2.4': + resolution: {integrity: sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==} + + '@axe-core/playwright@4.13.0': + resolution: {integrity: sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==} + peerDependencies: + playwright-core: '>= 1.0.0' + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bruits/satteri-darwin-arm64@0.10.5': + resolution: {integrity: sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA==} + cpu: [arm64] + os: [darwin] + + '@bruits/satteri-darwin-x64@0.10.5': + resolution: {integrity: sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q==} + cpu: [x64] + os: [darwin] + + '@bruits/satteri-linux-arm64-gnu@0.10.5': + resolution: {integrity: sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@bruits/satteri-linux-arm64-musl@0.10.5': + resolution: {integrity: sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@bruits/satteri-linux-x64-gnu@0.10.5': + resolution: {integrity: sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@bruits/satteri-linux-x64-musl@0.10.5': + resolution: {integrity: sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@bruits/satteri-wasm32-wasi@0.10.5': + resolution: {integrity: sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@bruits/satteri-win32-arm64-msvc@0.10.5': + resolution: {integrity: sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw==} + cpu: [arm64] + os: [win32] + + '@bruits/satteri-win32-x64-msvc@0.10.5': + resolution: {integrity: sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ==} + cpu: [x64] + os: [win32] + + '@cacheable/memory@2.2.0': + resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} + + '@cacheable/utils@2.5.0': + resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==} + + '@capsizecss/unpack@4.0.1': + resolution: {integrity: sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==} + engines: {node: '>=18'} + + '@clack/core@1.5.1': + resolution: {integrity: sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.8.1': + resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==} + engines: {node: '>= 20.12.0'} + + '@commitlint/cli@21.2.3': + resolution: {integrity: sha512-5yOX6IRjcrIReCxpyAd04hgI/yhUMpVBJ/ouwFV0wk9yx5+fYRDWZWJ5h55Tbedgfol+5m2mU5nqzbB8g3v0vQ==} + engines: {node: '>=22.12.0'} + hasBin: true + + '@commitlint/config-conventional@21.2.3': + resolution: {integrity: sha512-EP2n5DN2GwRWNVrLX0Jx3mpQlW5PfFi9HnD9OJ8hN/H0xFqpCFyHeGDhg+0P8/21iMMcdkcQgbNqmVklebqYsQ==} + engines: {node: '>=22.12.0'} + + '@commitlint/config-validator@21.2.3': + resolution: {integrity: sha512-JQ13v0GafSM4fBYX8UowSy++07Was20auq0NcTdnzSEq6/05Cq5GBUMIfKkqjzyBwC9sJFmPpKWgLLhH7cygkw==} + engines: {node: '>=22.12.0'} + + '@commitlint/ensure@21.2.3': + resolution: {integrity: sha512-gEjNKgRocbODI5/sMx2Qc9XOf2/zFTNV2aEK1QZ4gKDKxDC/VR7us3QtIX1C5d5xxHxx0I/tEbYbdSP1Am2LwA==} + engines: {node: '>=22.12.0'} + + '@commitlint/execute-rule@21.0.1': + resolution: {integrity: sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==} + engines: {node: '>=22.12.0'} + + '@commitlint/format@21.2.3': + resolution: {integrity: sha512-KKwCjXYqA8r295uhLnulDXL9yekdXmj2yp2Q0XsBPhxQ2iPuvRE8F43j7g7yexo69wTdmUQmFz15jpderluzUQ==} + engines: {node: '>=22.12.0'} + + '@commitlint/is-ignored@21.2.3': + resolution: {integrity: sha512-Ng4nRj/LBAOvc+GQu7bsFW4/fL89LvZ14OOC/ERLbUpnG6QBwWRzY/1oXGwPs3C8YHWjJObin4kLYo2YeEn70w==} + engines: {node: '>=22.12.0'} + + '@commitlint/lint@21.2.3': + resolution: {integrity: sha512-lUZcSuVLLEDkhge9ERk8Rp+FXQtrUhCK+h328BFnzKXrTuXGdqiYMEpTlNWdZaOrjyMhMJaz354ZaWEGJxcLeg==} + engines: {node: '>=22.12.0'} + + '@commitlint/load@21.2.3': + resolution: {integrity: sha512-cDrL8lOo23Lk7knGzQdjbFcq7z2JgvjwV6PhbESVvEc+Fe21MeLjPKBJi8nKjg7j+jh79CVbVSUbZc104thXRw==} + engines: {node: '>=22.12.0'} + + '@commitlint/message@21.2.0': + resolution: {integrity: sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==} + engines: {node: '>=22.12.0'} + + '@commitlint/parse@21.2.3': + resolution: {integrity: sha512-Jt61QpF1xA88damCELe8I0FlDZPSpXNc8IhN7z5kZ0EcDYPjdZ+ZHPvsSXl69r2MIlLnNKeHhOn2cmVpziFE9w==} + engines: {node: '>=22.12.0'} + + '@commitlint/read@21.2.3': + resolution: {integrity: sha512-szAepuLA1vTTr7fuudEg7jYyVHudf+FH+vptU91bhxE1WnE8TKrfTAi44HVllz8XpI8Pvp4GjR/iP5Nshgh+og==} + engines: {node: '>=22.12.0'} + + '@commitlint/resolve-extends@21.2.3': + resolution: {integrity: sha512-bqJdmAtXgAQel+l02uIsF7rHgKgx1AGZdGBjam7uK4XW54ZAQtB6XAIUXezYprQiCcRMpBh4fHs5JodXK4mVZA==} + engines: {node: '>=22.12.0'} + + '@commitlint/rules@21.2.3': + resolution: {integrity: sha512-5+YnI/LuTx3JDl9GjqfP2z9x96M8KKDrd+4jOel/T0L1BB3oAmBlSN8xppf63QsudiVEgR8C0gRoAMrPVrms6g==} + engines: {node: '>=22.12.0'} + + '@commitlint/to-lines@21.0.1': + resolution: {integrity: sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==} + engines: {node: '>=22.12.0'} + + '@commitlint/top-level@21.2.0': + resolution: {integrity: sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==} + engines: {node: '>=22.12.0'} + + '@commitlint/types@21.2.3': + resolution: {integrity: sha512-sGEA473TlCvhCGNoTBY9OXZVWdscIXdGt0dm8EpB+2MRfmt8LNh/X56P/T3nCtEB+eH5qGEa3odphSAoUxSfSQ==} + engines: {node: '>=22.12.0'} + + '@conventional-changelog/git-client@3.1.2': + resolution: {integrity: sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==} + engines: {node: '>=22'} + peerDependencies: + conventional-commits-filter: ^6.0.1 + conventional-commits-parser: ^7.1.2 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true + + '@conventional-changelog/template@1.4.0': + resolution: {integrity: sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==} + engines: {node: '>=22'} + + '@ctrl/tinycolor@4.2.1': + resolution: {integrity: sha512-Vh5uy9Y1JBMId6J1f68TMXnZrTCvNdo9Fu2k0d1YnJngR+sM5kWTKl441c/3TFs+cG0NWOjRWgQ/iaWhbYx+7A==} + engines: {node: '>=14'} + + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.1': + resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/core@1.11.3': + resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@emnapi/wasi-threads@1.2.3': + resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-plugin-eslint-comments@4.8.1': + resolution: {integrity: sha512-vafrdi15JHBFh1qWOAucyJwlEEuhdoMof28jOdKdgnwZ7bXwd6v4wzHYSmAVLwnx0yc7bqAtfJ0EZQV1WAzV/Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.3': + resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@expressive-code/core@0.44.2': + resolution: {integrity: sha512-Yw5KonQCD1HoOO2P89y4wifqS9qrJcGVY0LkVMUsVZjhi++bhz4WwQ37inenKFNOEVo50P7cjoVsCdaVnUbq8w==} + + '@expressive-code/plugin-frames@0.44.2': + resolution: {integrity: sha512-gVLm6TQMNIRWIaM7o+mCtxkGNjLZY5/2faSTLoXeo9wv/Pkj/0QAtZyXPVPrRsPARqiipATNFmfvQFLJudxPHQ==} + + '@expressive-code/plugin-shiki@0.44.2': + resolution: {integrity: sha512-ebWIvCDzXvjyD8rH6T6DfytYJw1GoArLR4nusIAE4VxXrCQf3d+owytiv010KU/dUg+SRLZskDn0N+HaFXeuSg==} + + '@expressive-code/plugin-text-markers@0.44.2': + resolution: {integrity: sha512-SNI7EgUSiADqbqUzzMbj1ZeC/r2pa5eg3KyFOGAd3a7EsIA4qzjXQLp+ODWQtHVDKFCdGs4J3ovSJUqvKIK96g==} + + '@fontsource/overpass@5.3.0': + resolution: {integrity: sha512-601DJ9xb8+8XbxAS05+lkh9cW6wc4TnXL2vhQBUN6PrtE/mQTgxqrqac7rXkthyJO7c3KMHOpl9Tt0sRql1p5Q==} + + '@gerrit0/mini-shiki@3.23.0': + resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.5': + resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.5': + resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.5': + resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.4': + resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.4': + resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.4': + resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.3.4': + resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.3.4': + resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.3.4': + resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.3.4': + resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.3.4': + resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.35.5': + resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.35.5': + resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.35.5': + resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.35.5': + resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.35.5': + resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.35.5': + resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.35.5': + resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.35.5': + resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.35.5': + resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.5': + resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.5': + resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.5': + resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.5': + resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@keyv/bigmap@1.3.1': + resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.6.0 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@napi-rs/wasm-runtime@1.2.4': + resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + + '@pagefind/darwin-arm64@1.5.2': + resolution: {integrity: sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==} + cpu: [arm64] + os: [darwin] + + '@pagefind/darwin-x64@1.5.2': + resolution: {integrity: sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==} + cpu: [x64] + os: [darwin] + + '@pagefind/default-ui@1.5.2': + resolution: {integrity: sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==} + + '@pagefind/freebsd-x64@1.5.2': + resolution: {integrity: sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==} + cpu: [x64] + os: [freebsd] + + '@pagefind/linux-arm64@1.5.2': + resolution: {integrity: sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==} + cpu: [arm64] + os: [linux] + + '@pagefind/linux-x64@1.5.2': + resolution: {integrity: sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==} + cpu: [x64] + os: [linux] + + '@pagefind/windows-arm64@1.5.2': + resolution: {integrity: sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==} + cpu: [arm64] + os: [win32] + + '@pagefind/windows-x64@1.5.2': + resolution: {integrity: sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==} + cpu: [x64] + os: [win32] + + '@playwright/test@1.63.0': + resolution: {integrity: sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==} + engines: {node: '>=20'} + hasBin: true + + '@prettier/parse-srcset@3.1.0': + resolution: {integrity: sha512-FIRv2rZotO9NP/r66taYqD1zICpPiBbrECqjKV/xqNotqDxF7fLzzUeK+1RSRx9Tenk0DajR/GcRmTJ2qtHaKQ==} + + '@rolldown/binding-android-arm-eabi@1.2.12': + resolution: {integrity: sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.12': + resolution: {integrity: sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.12': + resolution: {integrity: sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.12': + resolution: {integrity: sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.12': + resolution: {integrity: sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + resolution: {integrity: sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.12': + resolution: {integrity: sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.12': + resolution: {integrity: sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + resolution: {integrity: sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.12': + resolution: {integrity: sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.12': + resolution: {integrity: sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.12': + resolution: {integrity: sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.12': + resolution: {integrity: sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.12': + resolution: {integrity: sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.12': + resolution: {integrity: sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@shikijs/core@4.5.0': + resolution: {integrity: sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.5.0': + resolution: {integrity: sha512-Ed6UdEj84LyYkwnxM5CvSMWAD+id9ExTYri0/+x41U9zrgNifPSS9lZm0JqBP0VfYI8yr8tY5nbNq9+p9R0kFA==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/engine-oniguruma@4.5.0': + resolution: {integrity: sha512-dKVmbJecVdB9recl8a/CU4jdpNXyjJQrIcesIkpS70s5HS02ohpnCSpMUbotPnEUzbAcLQ+S4hwZYbkBBiTcJw==} + engines: {node: '>=20'} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/langs@4.5.0': + resolution: {integrity: sha512-wwBTqwduXSz0X4v9Qa2EWeFaYJEw1clCncWjoRNQRbVDu3SPRTRWucnouYapKj3b8Q+sk/41Vbc4B5c8nuYzLA==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.5.0': + resolution: {integrity: sha512-UM7ShlA8oz0OWxLLO8BmO9Ip6fVqdkt8yF0EH7nOCqHfrx1eEAzF0ZUVK7L+I8NEgeu1PEWSRlxaiPPeCb4iDg==} + engines: {node: '>=20'} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/themes@4.5.0': + resolution: {integrity: sha512-zGb87H3UBbpuI4yoh5UguB1XbU+2BDU3kflM7UfZ+Cy2hWo9lBtqjSVK20gmLY6ukYl040hAypGXMgud9vul8Q==} + engines: {node: '>=20'} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/types@4.5.0': + resolution: {integrity: sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@simple-libs/child-process-utils@2.0.0': + resolution: {integrity: sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==} + engines: {node: '>=22'} + + '@simple-libs/stream-utils@2.0.0': + resolution: {integrity: sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==} + engines: {node: '>=22'} + + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/node@24.19.1': + resolution: {integrity: sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==} + + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@typescript-eslint/eslint-plugin@8.71.0': + resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.71.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.71.0': + resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.71.0': + resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.71.0': + resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.71.0': + resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.71.0': + resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.71.0': + resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.71.0': + resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.71.0': + resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.71.0': + resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + '@vitest/coverage-v8@5.0.3': + resolution: {integrity: sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==} + peerDependencies: + '@vitest/browser': 5.0.3 + vitest: 5.0.3 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/istanbul-lib-coverage@1.0.2': + resolution: {integrity: sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==} + engines: {node: '>=22'} + + '@vitest/istanbul-lib-report@1.0.2': + resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} + engines: {node: '>=22'} + + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + + '@volar/kit@2.4.28': + resolution: {integrity: sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/language-server@2.4.28': + resolution: {integrity: sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@volar/language-service@2.4.28': + resolution: {integrity: sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-i18n@4.2.0: + resolution: {integrity: sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==} + peerDependencies: + ajv: ^8.0.0-beta.0 + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + am-i-vibing@0.4.0: + resolution: {integrity: sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==} + hasBin: true + + ansi-regex@6.4.0: + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ansi-styles@7.0.0: + resolution: {integrity: sha512-kKvt3m4uwzqL0wlkPd09CmljPJGOZZ4D0fP65sqFSvPkMRKhNi+74MgIJ5QxE6SxqB4t4KyUFGg8+n5zjo6hew==} + engines: {node: '>=22'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + argue-cli@3.2.0: + resolution: {integrity: sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==} + engines: {node: '>=22'} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} + + astro-expressive-code@0.44.2: + resolution: {integrity: sha512-avop7nZcRwC7quvIxVguhwEZ9Tp6IoTzKUCLwP30Fb7+er+pWQgcysP01dcuzRIJo81kLGg30q/YAI2Yb1Sdzg==} + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta || ^7.0.0 + + astro@7.3.5: + resolution: {integrity: sha512-1p/ernaq/RuESbM5NMjWzbO6SiebMGrGcYlGjMfBhzzgM+n/X7AUmVyw8BqW2QYCdivpsHwJBcD1fqmlTZjZHg==} + engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + peerDependencies: + '@astrojs/markdown-remark': ^7.3.0 + peerDependenciesMeta: + '@astrojs/markdown-remark': + optional: true + + axe-core@4.13.0: + resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + + bcp-47@2.1.1: + resolution: {integrity: sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + + cacheable@2.5.0: + resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + chalk@6.0.1: + resolution: {integrity: sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==} + engines: {node: '>=22'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + common-ancestor-path@2.0.0: + resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} + engines: {node: '>= 18'} + + conventional-changelog-angular@9.4.0: + resolution: {integrity: sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==} + engines: {node: '>=22'} + + conventional-changelog-conventionalcommits@10.4.0: + resolution: {integrity: sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==} + engines: {node: '>=22'} + + conventional-commits-parser@7.1.2: + resolution: {integrity: sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==} + engines: {node: '>=22'} + hasBin: true + + cookie-es@1.2.3: + resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + + cookie@2.0.1: + resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==} + engines: {node: '>=22'} + + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} + engines: {node: '>=v18'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-select@6.0.0: + resolution: {integrity: sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==} + + css-selector-parser@3.3.0: + resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@7.0.0: + resolution: {integrity: sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devalue@5.9.4: + resolution: {integrity: sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@9.0.0: + resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==} + engines: {node: '>=0.3.1'} + + direction@2.0.1: + resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} + hasBin: true + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + dom-serializer@3.1.1: + resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==} + engines: {node: '>=20.19.0'} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domelementtype@3.0.0: + resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==} + engines: {node: '>=20.19.0'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domhandler@6.0.1: + resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==} + engines: {node: '>=20.19.0'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + domutils@4.0.2: + resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==} + engines: {node: '>=20.19.0'} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@8.1.0: + resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==} + engines: {node: '>=20.19.0'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.11.0: + resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + expressive-code@0.44.2: + resolution: {integrity: sha512-MLNghkvhyFgVW4oCD5DxBuYnnTXIf0PygzGmfT6OZeOOYWh/D4sH1j931biElkA5RG/J9wY/iuSMGAPF5vzHOg==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@11.1.5: + resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==} + + find-proc@0.2.0: + resolution: {integrity: sha512-a6h2nTrgB3g5Wrn4au9Ux4kMk7IJtyF6oA8uGn+mk3weLZssUtc1cp6meHIzzuW2o1srrBAxzkmXLbqjRkgB6w==} + engines: {node: ^20.19.0 || >=22.12.0} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@6.1.23: + resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.4.1: + resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} + + fontkitten@1.0.3: + resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} + engines: {node: '>=20'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.7.0: + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} + engines: {node: '>=18'} + + get-tsconfig@5.0.0-beta.4: + resolution: {integrity: sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==} + engines: {node: '>=20.20.0'} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + global-directory@5.0.0: + resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} + engines: {node: '>=20'} + + h3@1.15.11: + resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} + engines: {node: '>=20'} + + hast-util-embedded@3.0.0: + resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} + + hast-util-format@1.1.0: + resolution: {integrity: sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==} + + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + + hast-util-is-body-ok-link@3.0.1: + resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-minify-whitespace@1.0.1: + resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-phrasing@3.0.1: + resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==} + + hast-util-select@6.0.4: + resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + html-whitespace-sensitive-tag-names@3.0.1: + resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==} + + htmlparser2@12.0.0: + resolution: {integrity: sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==} + engines: {node: '>=20.19.0'} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + i18next@26.4.2: + resolution: {integrity: sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug==} + peerDependencies: + typescript: ^5 || ^6 || ^7 + peerDependenciesMeta: + typescript: + optional: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.11: + resolution: {integrity: sha512-YChdK5txDjwGUvgR7oCJcLGkwi3LDh8Zx8tS7ndYciLEg/oOCL26VUBSUEtgS7EjiybcBqFxh5j0rAdDX7/bbg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-docker@4.0.0: + resolution: {integrity: sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==} + engines: {node: '>=20'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-parse-even-better-errors@6.0.0: + resolution: {integrity: sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + linkify-it@5.0.2: + resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==} + + linkinator@8.1.0: + resolution: {integrity: sha512-3AeucoHrnlsbxPinqP1w0bsVvit2ESRSFEhFoHAsJf8CNjvmNGny/f/AgtYQXVmz+x9qn+a74DlPX06yYbSRXg==} + engines: {node: '>=22'} + hasBin: true + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + + magic-string@1.4.2: + resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==} + + magicast@0.5.5: + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} + + markdown-it@14.3.2: + resolution: {integrity: sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q==} + hasBin: true + + marked-gfm-heading-id@4.1.4: + resolution: {integrity: sha512-CspnvVfHSkb/znqdPS4jUR8HtCjq3M/DnrsJCrfLBLvdrgbemmoINKpeWKQYkBiXAoBGejw0cV7xzqrPdup3WA==} + peerDependencies: + marked: '>=13 <19' + + marked@18.0.14: + resolution: {integrity: sha512-mBHK6FBHuBAlhgRe88w9F0O1AbwwXJUcQibUbC/QcdTbVGAD7aWza+xt3N6oT/jCZx3/OMeS+8rnuiHZcQ9s7A==} + engines: {node: '>= 20'} + hasBin: true + + mdast-util-directive@3.1.0: + resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.3: + resolution: {integrity: sha512-wgyJtgUkUcdU7zci7uuwc/tzoAhm0TswWhaXuvnWolqug+8jY7bgNkBBL0dYBnHgc/tHL7lCzRdPsdUxUKKBqw==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + mdurl@2.1.0: + resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + meow@14.1.0: + resolution: {integrity: sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==} + engines: {node: '>=20'} + + micromark-core-commonmark@2.0.4: + resolution: {integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==} + + micromark-extension-directive@4.0.0: + resolution: {integrity: sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.1.0: + resolution: {integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-edit-map@1.0.0: + resolution: {integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.3: + resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} + + micromark@4.0.3: + resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} + + mime@4.1.0: + resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} + engines: {node: '>=16'} + hasBin: true + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neotraverse@1.0.1: + resolution: {integrity: sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-mock-http@1.0.5: + resolution: {integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-normalize-package-bin@6.0.0: + resolution: {integrity: sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + npm-run-all2@9.0.3: + resolution: {integrity: sha512-BQAEdU1PtYc48qYRdghW2BVTQT3VqWCoFQmO87NlM1h1PYwMCKQpUWaNyB20V26caNzFsXQDfyOdznLlHCih6g==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0, npm: '>= 10'} + hasBin: true + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + obug@3.0.0: + resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} + engines: {node: '>=12.20.0'} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.12: + resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} + + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@7.3.3: + resolution: {integrity: sha512-SKgVvDU5TNIxQ4r30U75BNLC6a+6GOEjngrk/ysM5+Zu3oJuk1SC5ApzIzY/7PjCEjoVPMWJ0zho/4QUiEB7TQ==} + engines: {node: '>=20'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-queue@9.3.3: + resolution: {integrity: sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==} + engines: {node: '>=20'} + + p-timeout@7.0.2: + resolution: {integrity: sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==} + engines: {node: '>=20'} + + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + pagefind@1.5.2: + resolution: {integrity: sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==} + hasBin: true + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pidtree@1.0.0: + resolution: {integrity: sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q==} + engines: {node: '>=18'} + hasBin: true + + playwright-core@1.63.0: + resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.63.0: + resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==} + engines: {node: '>=20'} + hasBin: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.4: + resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==} + engines: {node: '>=4'} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-astro@1.1.0: + resolution: {integrity: sha512-ujJ1VX+iin4eYJPHVCEbmXHzrEVsi1SaVehHtbAteTZv8VTrZyBiZPmYc6Ud6+fkr/UBMXuEu3N3+d49h7YfKw==} + engines: {node: '>=22.12.0'} + peerDependencies: + prettier: ^3.5.3 + + prettier@3.9.9: + resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} + engines: {node: '>=14'} + hasBin: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + process-ancestry@0.1.0: + resolution: {integrity: sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==} + engines: {node: '>=18.0.0'} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} + engines: {node: '>=20'} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + read-package-json-fast@6.0.0: + resolution: {integrity: sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-expressive-code@0.44.2: + resolution: {integrity: sha512-jvjQpsfWKBquDrEl5YQA3CJybRRIuDOYsui1BJTs5oKDUK8pPkJSSxY8FjxNlIFIdtgrTjC1zuahuY4Cc1j9SQ==} + + remark-directive@4.0.0: + resolution: {integrity: sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==} + + request-light@0.5.8: + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + rolldown@1.2.12: + resolution: {integrity: sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + s.color@0.0.15: + resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} + + sass-formatter@0.8.0: + resolution: {integrity: sha512-Nf4eMGE2O5vnJG7KPCRyksWS9gVKSYoUWtPeMXRRxwZSPVQhSGtL602xVYS65LvMkHmkvKa7xpauF0+/5pPwrg==} + hasBin: true + + satteri@0.10.5: + resolution: {integrity: sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==} + + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} + engines: {node: '>=11.0.0'} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.35.5: + resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.11.0: + resolution: {integrity: sha512-JdxDPD0DBTyu08pq0kPC0xSNet/qsU07qT6IsX1AS8oO2ICNRY4ldNa8OAI6PuwAH8tG3lxEhbqmyp4Dw4036g==} + engines: {node: '>= 0.4'} + + shiki@4.5.0: + resolution: {integrity: sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ==} + engines: {node: '>=20'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + sitemap@9.0.1: + resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==} + engines: {node: '>=20.19.5', npm: '>=10.8.2'} + hasBin: true + + smol-toml@1.9.0: + resolution: {integrity: sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==} + engines: {node: '>= 18'} + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + srcset@5.0.3: + resolution: {integrity: sha512-AZswtOXIsu0LeHdo6YY7d0r2pCH2Rl1D8ae1utvXUX4GxG3RggsVUAOFX1r8RI4YHFMYb4g89+UBPBv3mNUU2g==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + starlight-typedoc@0.23.1: + resolution: {integrity: sha512-H45Mxv4mZ1xUMhUNjTR4CwKDgKKHZboQA5v5+vTVMdjlCX02Xo7veTB7+F/ivmMHPmQ7SV5auF2BTlU/EpQIoQ==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@astrojs/starlight': '>=0.39.0' + astro: '>=6.0.0' + typedoc: '>=0.28.0' + typedoc-plugin-markdown: '>=4.6.0' + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.3.0: + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} + engines: {node: '>=20'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + suf-log@2.5.3: + resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} + + svgo@4.1.0: + resolution: {integrity: sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==} + engines: {node: '>=16'} + hasBin: true + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinybench@6.2.0: + resolution: {integrity: sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==} + engines: {node: '>=20.0.0'} + + tinyclip@1.0.3: + resolution: {integrity: sha512-nTv2raKFMzGG6JJp0lpNDsRPdfFE6UHlu2rVEjXo8vkx1ZS3LW2CVXqIaLu8xZlu+6tbFs9V58Qc/Jmk/Ii7wA==} + engines: {node: ^16.14.0 || >= 17.3.0} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typedoc-plugin-markdown@4.13.1: + resolution: {integrity: sha512-d3jchJ4CyIrV9w9uozTSkfMYG8IEM1RvvGOaybJ/3rRporzoX7p0lprffo95GOGUviQqP0phjHJE3bY8PQbilA==} + engines: {node: '>= 18'} + peerDependencies: + typedoc: 0.28.x + + typedoc@0.28.20: + resolution: {integrity: sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg==} + engines: {node: '>= 18', pnpm: '>= 10'} + hasBin: true + peerDependencies: + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x + + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} + + typescript-eslint@8.71.0: + resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + ultrahtml@1.7.0: + resolution: {integrity: sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + undici@8.11.2: + resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==} + engines: {node: '>=22.19.0'} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.7.5: + resolution: {integrity: sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + unstorage@1.17.5: + resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} + 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 + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-extras@0.1.0: + resolution: {integrity: sha512-8tzwTeXFPuX/5PHuCDQE5Dd9Ts4rwoq2t9aIT+HS4iAVpmj5l4Ao7Q+BuuFjvWRqrLswBhQDk8O96ZicgCqQqw==} + engines: {node: '>=20'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + verkit@0.4.1: + resolution: {integrity: sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==} + engines: {node: '>=18.12.0'} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@8.3.2: + resolution: {integrity: sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + 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 + + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + volar-service-css@0.0.71: + resolution: {integrity: sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.71: + resolution: {integrity: sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.71: + resolution: {integrity: sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.71: + resolution: {integrity: sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.71: + resolution: {integrity: sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A==} + peerDependencies: + '@volar/language-service': ~2.4.0 + typescript: '*' + peerDependenciesMeta: + '@volar/language-service': + optional: true + typescript: + optional: true + + volar-service-typescript@0.0.71: + resolution: {integrity: sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + typescript: '*' + peerDependenciesMeta: + '@volar/language-service': + optional: true + typescript: + optional: true + + volar-service-yaml@0.0.71: + resolution: {integrity: sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.3.10: + resolution: {integrity: sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==} + + vscode-html-languageservice@5.6.2: + resolution: {integrity: sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==} + + vscode-json-languageservice@4.1.8: + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-jsonrpc@9.0.3: + resolution: {integrity: sha512-qETYf9PCl1lVfwR+oBmMfc4vP2nqLNA35ABxorw3j06+xlKRgDVxbLgKm4Y/xPL0YWdeDjLoI4ImICIT3nB0zQ==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-protocol@3.18.4: + resolution: {integrity: sha512-CCXbyr99dafRJzJH/0Ryk7ly5T8XPmSR0//OuF2w4m+9JiOin7zcAT5iET8GRE7Z9taFBejb3GbM0a0mbjdbrg==} + + vscode-languageserver-textdocument@1.0.15: + resolution: {integrity: sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver-types@3.18.4: + resolution: {integrity: sha512-nQsacoiijvl0fRf2q28uFDrAVwRhdCa3QveiF7/CX0r18mLR5FU/1WskSsCKOjmEbtteKUnY8x8Yzxop+k0zwg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@3.2.0: + resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@7.0.0: + resolution: {integrity: sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + hasBin: true + + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml-language-server@1.23.0: + resolution: {integrity: sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==} + hasBin: true + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.2.0: + resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@astrojs/astro2tsx@0.1.2(@emnapi/runtime@1.11.3)': + dependencies: + '@emnapi/core': 1.11.3 + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + transitivePeerDependencies: + - '@emnapi/runtime' + + '@astrojs/check@0.9.10(@emnapi/runtime@1.11.3)(prettier-plugin-astro@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(prettier@3.9.9))(prettier@3.9.9)(typescript@6.0.3)': + dependencies: + '@astrojs/language-server': 2.17.1(@emnapi/runtime@1.11.3)(prettier-plugin-astro@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(prettier@3.9.9))(prettier@3.9.9)(typescript@6.0.3) + chokidar: 4.0.3 + kleur: 4.1.5 + typescript: 6.0.3 + yargs: 18.2.0 + transitivePeerDependencies: + - '@emnapi/runtime' + - prettier + - prettier-plugin-astro + + '@astrojs/compiler-binding-android-arm64@0.4.1': + optional: true + + '@astrojs/compiler-binding-android-arm64@0.5.1': + optional: true + + '@astrojs/compiler-binding-darwin-arm64@0.4.1': + optional: true + + '@astrojs/compiler-binding-darwin-arm64@0.5.1': + optional: true + + '@astrojs/compiler-binding-darwin-x64@0.4.1': + optional: true + + '@astrojs/compiler-binding-darwin-x64@0.5.1': + optional: true + + '@astrojs/compiler-binding-linux-arm64-gnu@0.4.1': + optional: true + + '@astrojs/compiler-binding-linux-arm64-gnu@0.5.1': + optional: true + + '@astrojs/compiler-binding-linux-arm64-musl@0.4.1': + optional: true + + '@astrojs/compiler-binding-linux-arm64-musl@0.5.1': + optional: true + + '@astrojs/compiler-binding-linux-x64-gnu@0.4.1': + optional: true + + '@astrojs/compiler-binding-linux-x64-gnu@0.5.1': + optional: true + + '@astrojs/compiler-binding-linux-x64-musl@0.4.1': + optional: true + + '@astrojs/compiler-binding-linux-x64-musl@0.5.1': + optional: true + + '@astrojs/compiler-binding-wasm32-wasi@0.4.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + dependencies: + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@astrojs/compiler-binding-wasm32-wasi@0.5.1': + dependencies: + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + optional: true + + '@astrojs/compiler-binding-win32-arm64-msvc@0.4.1': + optional: true + + '@astrojs/compiler-binding-win32-arm64-msvc@0.5.1': + optional: true + + '@astrojs/compiler-binding-win32-x64-msvc@0.4.1': + optional: true + + '@astrojs/compiler-binding-win32-x64-msvc@0.5.1': + optional: true + + '@astrojs/compiler-binding@0.4.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + 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(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + '@astrojs/compiler-binding-win32-arm64-msvc': 0.4.1 + '@astrojs/compiler-binding-win32-x64-msvc': 0.4.1 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + '@astrojs/compiler-binding@0.5.1': + 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 + + '@astrojs/compiler-rs@0.4.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + dependencies: + '@astrojs/compiler-binding': 0.4.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + '@astrojs/compiler-rs@0.5.1': + dependencies: + '@astrojs/compiler-binding': 0.5.1 + + '@astrojs/internal-helpers@0.11.0': + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + js-yaml: 4.3.2 + picomatch: 4.0.7 + retext-smartypants: 6.2.0 + shiki: 4.5.0 + smol-toml: 1.9.0 + unified: 11.0.5 + + '@astrojs/language-server@2.17.1(@emnapi/runtime@1.11.3)(prettier-plugin-astro@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(prettier@3.9.9))(prettier@3.9.9)(typescript@6.0.3)': + dependencies: + '@astrojs/astro2tsx': 0.1.2(@emnapi/runtime@1.11.3) + '@astrojs/yaml2ts': 0.2.4 + '@volar/kit': 2.4.28(typescript@6.0.3) + '@volar/language-core': 2.4.28 + '@volar/language-server': 2.4.28(typescript@6.0.3) + '@volar/language-service': 2.4.28(typescript@6.0.3) + muggle-string: 0.4.1 + tinyglobby: 0.2.17 + volar-service-css: 0.0.71(@volar/language-service@2.4.28(typescript@6.0.3)) + volar-service-emmet: 0.0.71(@volar/language-service@2.4.28(typescript@6.0.3)) + volar-service-html: 0.0.71(@volar/language-service@2.4.28(typescript@6.0.3)) + volar-service-prettier: 0.0.71(@volar/language-service@2.4.28(typescript@6.0.3))(prettier@3.9.9) + volar-service-typescript: 0.0.71(@volar/language-service@2.4.28(typescript@6.0.3))(typescript@6.0.3) + volar-service-typescript-twoslash-queries: 0.0.71(@volar/language-service@2.4.28(typescript@6.0.3))(typescript@6.0.3) + volar-service-yaml: 0.0.71(@volar/language-service@2.4.28(typescript@6.0.3)) + vscode-html-languageservice: 5.6.2 + vscode-uri: 3.2.0 + optionalDependencies: + prettier: 3.9.9 + prettier-plugin-astro: 1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(prettier@3.9.9) + transitivePeerDependencies: + - '@emnapi/runtime' + - typescript + + '@astrojs/markdown-satteri@0.4.2': + dependencies: + '@astrojs/internal-helpers': 0.11.0 + '@astrojs/prism': 4.0.2 + github-slugger: 2.0.0 + satteri: 0.10.5 + + '@astrojs/mdx@8.0.2(@astrojs/markdown-satteri@0.4.2)(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1))': + dependencies: + '@astrojs/internal-helpers': 0.11.0 + '@astrojs/markdown-satteri': 0.4.2 + astro: 7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1) + es-module-lexer: 2.3.2 + + '@astrojs/prism@4.0.2': + dependencies: + prismjs: 1.30.0 + + '@astrojs/sitemap@3.7.4': + dependencies: + sitemap: 9.0.1 + zod: 4.6.5 + + '@astrojs/starlight@0.42.5(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1))(typescript@6.0.3)': + dependencies: + '@astrojs/markdown-satteri': 0.4.2 + '@astrojs/mdx': 8.0.2(@astrojs/markdown-satteri@0.4.2)(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1)) + '@astrojs/sitemap': 3.7.4 + '@pagefind/default-ui': 1.5.2 + '@types/hast': 3.0.5 + '@types/js-yaml': 4.0.9 + '@types/mdast': 4.0.4 + astro: 7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1) + astro-expressive-code: 0.44.2(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1)) + bcp-47: 2.1.1 + hast-util-format: 1.1.0 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-string: 3.0.1 + hastscript: 9.0.1 + i18next: 26.4.2(typescript@6.0.3) + js-yaml: 4.3.2 + klona: 2.0.6 + magic-string: 1.4.2 + mdast-util-directive: 3.1.0 + mdast-util-to-markdown: 2.1.3 + mdast-util-to-string: 4.0.0 + pagefind: 1.5.2 + remark-directive: 4.0.0 + satteri: 0.10.5 + ultrahtml: 1.7.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + - typescript + + '@astrojs/telemetry@3.3.3': + dependencies: + ci-info: 4.4.0 + dset: 3.1.4 + is-docker: 4.0.0 + package-manager-detector: 1.8.0 + + '@astrojs/yaml2ts@0.2.4': + dependencies: + yaml: 2.9.1 + + '@axe-core/playwright@4.13.0(playwright-core@1.63.0)': + dependencies: + axe-core: 4.13.0 + playwright-core: 1.63.0 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.9': + dependencies: + '@babel/types': 7.29.8 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@bruits/satteri-darwin-arm64@0.10.5': + optional: true + + '@bruits/satteri-darwin-x64@0.10.5': + optional: true + + '@bruits/satteri-linux-arm64-gnu@0.10.5': + optional: true + + '@bruits/satteri-linux-arm64-musl@0.10.5': + optional: true + + '@bruits/satteri-linux-x64-gnu@0.10.5': + optional: true + + '@bruits/satteri-linux-x64-musl@0.10.5': + optional: true + + '@bruits/satteri-wasm32-wasi@0.10.5': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@bruits/satteri-win32-arm64-msvc@0.10.5': + optional: true + + '@bruits/satteri-win32-x64-msvc@0.10.5': + optional: true + + '@cacheable/memory@2.2.0': + dependencies: + '@cacheable/utils': 2.5.0 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + '@cacheable/utils@2.5.0': + dependencies: + hashery: 1.5.1 + keyv: 5.6.0 + + '@capsizecss/unpack@4.0.1': + dependencies: + fontkitten: 1.0.3 + + '@clack/core@1.5.1': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.8.1': + dependencies: + '@clack/core': 1.5.1 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@commitlint/cli@21.2.3(@types/node@24.19.1)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': + dependencies: + '@commitlint/config-conventional': 21.2.3 + '@commitlint/format': 21.2.3 + '@commitlint/lint': 21.2.3 + '@commitlint/load': 21.2.3(@types/node@24.19.1)(typescript@6.0.3) + '@commitlint/read': 21.2.3(conventional-commits-parser@7.1.2) + '@commitlint/types': 21.2.3 + tinyexec: 1.3.1 + yargs: 18.2.0 + transitivePeerDependencies: + - '@types/node' + - conventional-commits-filter + - conventional-commits-parser + - typescript + + '@commitlint/config-conventional@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + conventional-changelog-conventionalcommits: 10.4.0 + + '@commitlint/config-validator@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + ajv: 8.20.0 + + '@commitlint/ensure@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + es-toolkit: 1.52.0 + + '@commitlint/execute-rule@21.0.1': {} + + '@commitlint/format@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + picocolors: 1.1.1 + + '@commitlint/is-ignored@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + semver: 7.8.5 + + '@commitlint/lint@21.2.3': + dependencies: + '@commitlint/is-ignored': 21.2.3 + '@commitlint/parse': 21.2.3 + '@commitlint/rules': 21.2.3 + '@commitlint/types': 21.2.3 + + '@commitlint/load@21.2.3(@types/node@24.19.1)(typescript@6.0.3)': + dependencies: + '@commitlint/config-validator': 21.2.3 + '@commitlint/execute-rule': 21.0.1 + '@commitlint/resolve-extends': 21.2.3 + '@commitlint/types': 21.2.3 + cosmiconfig: 9.0.2(typescript@6.0.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@24.19.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) + es-toolkit: 1.52.0 + is-plain-obj: 4.1.0 + picocolors: 1.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@21.2.0': {} + + '@commitlint/parse@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + conventional-changelog-angular: 9.4.0 + conventional-commits-parser: 7.1.2 + + '@commitlint/read@21.2.3(conventional-commits-parser@7.1.2)': + dependencies: + '@commitlint/top-level': 21.2.0 + '@commitlint/types': 21.2.3 + '@conventional-changelog/git-client': 3.1.2(conventional-commits-parser@7.1.2) + tinyexec: 1.3.1 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + + '@commitlint/resolve-extends@21.2.3': + dependencies: + '@commitlint/config-validator': 21.2.3 + '@commitlint/types': 21.2.3 + es-toolkit: 1.52.0 + global-directory: 5.0.0 + resolve-from: 5.0.0 + + '@commitlint/rules@21.2.3': + dependencies: + '@commitlint/ensure': 21.2.3 + '@commitlint/message': 21.2.0 + '@commitlint/to-lines': 21.0.1 + '@commitlint/types': 21.2.3 + + '@commitlint/to-lines@21.0.1': {} + + '@commitlint/top-level@21.2.0': + dependencies: + escalade: 3.2.0 + + '@commitlint/types@21.2.3': + dependencies: + conventional-commits-parser: 7.1.2 + picocolors: 1.1.1 + + '@conventional-changelog/git-client@3.1.2(conventional-commits-parser@7.1.2)': + dependencies: + '@simple-libs/child-process-utils': 2.0.0 + '@simple-libs/stream-utils': 2.0.0 + semver: 7.8.5 + optionalDependencies: + conventional-commits-parser: 7.1.2 + + '@conventional-changelog/template@1.4.0': {} + + '@ctrl/tinycolor@4.2.1': {} + + '@emmetio/abbreviation@2.3.3': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-abbreviation@2.1.8': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-parser@0.4.1': + dependencies: + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 + + '@emmetio/html-matcher@1.3.0': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/scanner@1.0.4': {} + + '@emmetio/stream-reader-utils@0.1.0': {} + + '@emmetio/stream-reader@2.2.0': {} + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.11.3': + dependencies: + '@emnapi/wasi-threads': 1.2.3 + tslib: 2.8.1 + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.3': + dependencies: + tslib: 2.8.1 + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@eslint-community/eslint-plugin-eslint-comments@4.8.1(eslint@10.11.0(jiti@2.6.1))': + dependencies: + escape-string-regexp: 4.0.0 + eslint: 10.11.0(jiti@2.6.1) + ignore: 7.0.11 + + '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(jiti@2.6.1))': + dependencies: + eslint: 10.11.0(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.6 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.7.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.11.0(jiti@2.6.1))': + optionalDependencies: + eslint: 10.11.0(jiti@2.6.1) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.3': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@expressive-code/core@0.44.2': + dependencies: + '@ctrl/tinycolor': 4.2.1 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hastscript: 9.0.1 + postcss: 8.5.28 + postcss-nested: 6.2.0(postcss@8.5.28) + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + + '@expressive-code/plugin-frames@0.44.2': + dependencies: + '@expressive-code/core': 0.44.2 + + '@expressive-code/plugin-shiki@0.44.2': + dependencies: + '@expressive-code/core': 0.44.2 + shiki: 4.5.0 + + '@expressive-code/plugin-text-markers@0.44.2': + dependencies: + '@expressive-code/core': 0.44.2 + + '@fontsource/overpass@5.3.0': {} + + '@gerrit0/mini-shiki@3.23.0': + dependencies: + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.4 + optional: true + + '@img/sharp-darwin-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.4 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + optional: true + + '@img/sharp-linux-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.4 + optional: true + + '@img/sharp-linux-arm@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.4 + optional: true + + '@img/sharp-linux-ppc64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.4 + optional: true + + '@img/sharp-linux-riscv64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.4 + optional: true + + '@img/sharp-linux-s390x@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.4 + optional: true + + '@img/sharp-linux-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + optional: true + + '@img/sharp-wasm32@0.35.5': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 + optional: true + + '@img/sharp-win32-arm64@0.35.5': + optional: true + + '@img/sharp-win32-ia32@0.35.5': + optional: true + + '@img/sharp-win32-x64@0.35.5': + optional: true + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@keyv/bigmap@1.3.1(keyv@5.6.0)': + dependencies: + hashery: 1.5.1 + hookified: 1.15.1 + keyv: 5.6.0 + + '@keyv/serialize@1.1.1': {} + + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.4 + optional: true + + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + dependencies: + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@tybys/wasm-util': 0.10.4 + + '@oslojs/encoding@1.1.0': {} + + '@oxc-project/types@0.152.0': {} + + '@pagefind/darwin-arm64@1.5.2': + optional: true + + '@pagefind/darwin-x64@1.5.2': + optional: true + + '@pagefind/default-ui@1.5.2': {} + + '@pagefind/freebsd-x64@1.5.2': + optional: true + + '@pagefind/linux-arm64@1.5.2': + optional: true + + '@pagefind/linux-x64@1.5.2': + optional: true + + '@pagefind/windows-arm64@1.5.2': + optional: true + + '@pagefind/windows-x64@1.5.2': + optional: true + + '@playwright/test@1.63.0': + dependencies: + playwright: 1.63.0 + + '@prettier/parse-srcset@3.1.0': {} + + '@rolldown/binding-android-arm-eabi@1.2.12': + optional: true + + '@rolldown/binding-android-arm64@1.2.12': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.12': + optional: true + + '@rolldown/binding-darwin-x64@1.2.12': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.12': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.12': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.12': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.12': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.12': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.12': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@shikijs/core@4.5.0': + 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 + + '@shikijs/engine-javascript@4.5.0': + dependencies: + '@shikijs/types': 4.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/engine-oniguruma@4.5.0': + dependencies: + '@shikijs/types': 4.5.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/langs@4.5.0': + dependencies: + '@shikijs/types': 4.5.0 + + '@shikijs/primitive@4.5.0': + dependencies: + '@shikijs/types': 4.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@4.5.0': + dependencies: + '@shikijs/types': 4.5.0 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/types@4.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@simple-libs/child-process-utils@2.0.0': + dependencies: + '@simple-libs/stream-utils': 2.0.0 + + '@simple-libs/stream-utils@2.0.0': {} + + '@tybys/wasm-util@0.10.4': + dependencies: + tslib: 2.8.1 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + + '@types/estree@1.0.9': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/js-yaml@4.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@24.19.1': + dependencies: + undici-types: 7.24.6 + + '@types/sax@1.2.7': + dependencies: + '@types/node': 24.19.1 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/type-utils': 8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 + eslint: 10.11.0(jiti@2.6.1) + ignore: 7.0.11 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 + debug: 4.4.3 + eslint: 10.11.0(jiti@2.6.1) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.71.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.71.0': + dependencies: + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 + + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.11.0(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.71.0': {} + + '@typescript-eslint/typescript-estree@8.71.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.71.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 + debug: 4.4.3 + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3) + eslint: 10.11.0(jiti@2.6.1) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.71.0': + dependencies: + '@typescript-eslint/types': 8.71.0 + eslint-visitor-keys: 5.0.1 + + '@ungap/structured-clone@1.4.0': {} + + '@vitest/coverage-v8@5.0.3(vitest@5.0.3)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/istanbul-lib-coverage': 1.0.2 + '@vitest/istanbul-lib-report': 1.0.2 + ast-v8-to-istanbul: 1.0.7 + magicast: 0.5.5 + obug: 2.2.1 + std-env: 4.3.0 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@types/node@24.19.1)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1)) + + '@vitest/istanbul-lib-coverage@1.0.2': {} + + '@vitest/istanbul-lib-report@1.0.2': + dependencies: + '@vitest/istanbul-lib-coverage': 1.0.2 + + '@vitest/mocker@5.0.3(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.2 + optionalDependencies: + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1) + + '@vitest/spy@5.0.3': {} + + '@volar/kit@2.4.28(typescript@6.0.3)': + dependencies: + '@volar/language-service': 2.4.28(typescript@6.0.3) + '@volar/typescript': 2.4.28(typescript@6.0.3) + typesafe-path: 0.2.2 + typescript: 6.0.3 + vscode-languageserver-textdocument: 1.0.15 + vscode-uri: 3.2.0 + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/language-server@2.4.28(typescript@6.0.3)': + dependencies: + '@volar/language-core': 2.4.28 + '@volar/language-service': 2.4.28(typescript@6.0.3) + '@volar/typescript': 2.4.28(typescript@6.0.3) + path-browserify: 1.0.1 + request-light: 0.7.0 + vscode-languageserver: 9.0.1 + vscode-languageserver-protocol: 3.18.4 + vscode-languageserver-textdocument: 1.0.15 + vscode-uri: 3.2.0 + optionalDependencies: + typescript: 6.0.3 + + '@volar/language-service@2.4.28(typescript@6.0.3)': + dependencies: + '@volar/language-core': 2.4.28 + vscode-languageserver-protocol: 3.18.4 + vscode-languageserver-textdocument: 1.0.15 + vscode-uri: 3.2.0 + optionalDependencies: + typescript: 6.0.3 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28(typescript@6.0.3)': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.2.0 + optionalDependencies: + typescript: 6.0.3 + + '@vscode/emmet-helper@2.11.0': + dependencies: + emmet: 2.4.11 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.15 + vscode-languageserver-types: 3.18.4 + vscode-uri: 3.2.0 + + '@vscode/l10n@0.0.18': {} + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.18.0: {} + + ajv-draft-04@1.0.0(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-i18n@4.2.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.8 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + am-i-vibing@0.4.0: + dependencies: + process-ancestry: 0.1.0 + + ansi-regex@6.4.0: {} + + ansi-styles@6.2.3: {} + + ansi-styles@7.0.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + argue-cli@3.2.0: {} + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.7: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + astro-expressive-code@0.44.2(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1)): + dependencies: + astro: 7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1) + rehype-expressive-code: 0.44.2 + url-extras: 0.1.0 + + astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1): + dependencies: + '@astrojs/compiler-rs': 0.5.1 + '@astrojs/internal-helpers': 0.11.0 + '@astrojs/markdown-satteri': 0.4.2 + '@astrojs/telemetry': 3.3.3 + '@capsizecss/unpack': 4.0.1 + '@clack/prompts': 1.8.1 + '@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.9.4 + diff: 9.0.0 + dset: 3.1.4 + es-module-lexer: 2.3.2 + esbuild: 0.28.2 + 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.4.2 + magicast: 0.5.5 + mrmime: 2.0.1 + neotraverse: 1.0.1 + obug: 3.0.0 + p-limit: 7.3.3 + p-queue: 9.3.3 + package-manager-detector: 1.8.0 + piccolore: 0.1.3 + picomatch: 4.0.7 + shiki: 4.5.0 + smol-toml: 1.9.0 + svgo: 4.1.0 + tinyclip: 1.0.3 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + ultrahtml: 1.7.0 + unifont: 0.7.5 + unstorage: 1.17.5 + verkit: 0.4.1 + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1) + vitefu: 1.1.3(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1)) + xxhash-wasm: 1.1.0 + yargs-parser: 22.0.0 + zod: 4.6.5 + optionalDependencies: + sharp: 0.35.5(@types/node@24.19.1) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - '@vitejs/devtools' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - uploadthing + - yaml + + axe-core@4.13.0: {} + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + balanced-match@4.0.4: {} + + bcp-47-match@2.0.3: {} + + bcp-47@2.1.1: + dependencies: + is-alphabetical: 2.0.1 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + + boolbase@1.0.0: {} + + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + + cacheable@2.5.0: + dependencies: + '@cacheable/memory': 2.2.0 + '@cacheable/utils': 2.5.0 + hookified: 1.15.1 + keyv: 5.6.0 + qified: 0.10.1 + + callsites@3.1.0: {} + + ccount@2.0.1: {} + + chai@6.3.0: {} + + chalk@6.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + ci-info@4.4.0: {} + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + clsx@2.1.1: {} + + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + + common-ancestor-path@2.0.0: {} + + conventional-changelog-angular@9.4.0: + dependencies: + '@conventional-changelog/template': 1.4.0 + + conventional-changelog-conventionalcommits@10.4.0: + dependencies: + '@conventional-changelog/template': 1.4.0 + + conventional-commits-parser@7.1.2: + dependencies: + '@simple-libs/stream-utils': 2.0.0 + argue-cli: 3.2.0 + + cookie-es@1.2.3: {} + + cookie@2.0.1: {} + + cosmiconfig-typescript-loader@6.3.0(@types/node@24.19.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): + dependencies: + '@types/node': 24.19.1 + cosmiconfig: 9.0.2(typescript@6.0.3) + jiti: 2.6.1 + typescript: 6.0.3 + + cosmiconfig@9.0.2(typescript@6.0.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + parse-json: 5.2.0 + optionalDependencies: + typescript: 6.0.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-select@6.0.0: + dependencies: + boolbase: 1.0.0 + css-what: 7.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-selector-parser@3.3.0: {} + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.2 + + css-what@7.0.0: {} + + cssesc@3.0.0: {} + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + deep-is@0.1.4: {} + + defu@6.1.7: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + devalue@5.9.4: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@9.0.0: {} + + direction@2.0.1: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + dom-serializer@3.1.1: + dependencies: + domelementtype: 3.0.0 + domhandler: 6.0.1 + entities: 8.1.0 + + domelementtype@2.3.0: {} + + domelementtype@3.0.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domhandler@6.0.1: + dependencies: + domelementtype: 3.0.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + domutils@4.0.2: + dependencies: + dom-serializer: 3.1.1 + domelementtype: 3.0.0 + domhandler: 6.0.1 + + dset@3.1.4: {} + + emmet@2.4.11: + dependencies: + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 + + emoji-regex@10.6.0: {} + + entities@4.5.0: {} + + entities@8.1.0: {} + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-module-lexer@2.3.2: {} + + es-toolkit@1.52.0: {} + + esbuild@0.28.2: + 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 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.11.0(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.7.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.3 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 11.1.5 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.6 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + expect-type@1.4.0: {} + + expressive-code@0.44.2: + dependencies: + '@expressive-code/core': 0.44.2 + '@expressive-code/plugin-frames': 0.44.2 + '@expressive-code/plugin-shiki': 0.44.2 + '@expressive-code/plugin-text-markers': 0.44.2 + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.8: {} + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + file-entry-cache@11.1.5: + dependencies: + flat-cache: 6.1.23 + + find-proc@0.2.0: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@6.1.23: + dependencies: + cacheable: 2.5.0 + flatted: 3.4.4 + hookified: 1.15.1 + + flatted@3.4.4: {} + + flattie@1.1.1: {} + + fontace@0.4.1: + dependencies: + fontkitten: 1.0.3 + + fontkitten@1.0.3: + dependencies: + tiny-inflate: 1.0.3 + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.7.0: {} + + get-tsconfig@5.0.0-beta.4: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-slugger@2.0.0: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + + global-directory@5.0.0: + dependencies: + ini: 6.0.0 + + h3@1.15.11: + dependencies: + cookie-es: 1.2.3 + crossws: 0.3.5 + defu: 6.1.7 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.5 + radix3: 1.1.2 + ufo: 1.6.4 + uncrypto: 0.1.3 + + hashery@1.5.1: + dependencies: + hookified: 1.15.1 + + hast-util-embedded@3.0.0: + dependencies: + '@types/hast': 3.0.5 + hast-util-is-element: 3.0.0 + + hast-util-format@1.1.0: + dependencies: + '@types/hast': 3.0.5 + hast-util-embedded: 3.0.0 + hast-util-minify-whitespace: 1.0.1 + hast-util-phrasing: 3.0.1 + hast-util-whitespace: 3.0.0 + html-whitespace-sensitive-tag-names: 3.0.1 + unist-util-visit-parents: 6.0.2 + + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-is-body-ok-link@3.0.1: + dependencies: + '@types/hast': 3.0.5 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-minify-whitespace@1.0.1: + dependencies: + '@types/hast': 3.0.5 + hast-util-embedded: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-whitespace: 3.0.0 + unist-util-is: 6.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-phrasing@3.0.1: + dependencies: + '@types/hast': 3.0.5 + hast-util-embedded: 3.0.0 + hast-util-has-property: 3.0.0 + hast-util-is-body-ok-link: 3.0.1 + hast-util-is-element: 3.0.0 + + hast-util-select@6.0.4: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.5 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + + hookified@1.15.1: {} + + hookified@2.2.0: {} + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + html-whitespace-sensitive-tag-names@3.0.1: {} + + htmlparser2@12.0.0: + dependencies: + domelementtype: 3.0.0 + domhandler: 6.0.1 + domutils: 4.0.2 + entities: 8.1.0 + + http-cache-semantics@4.2.0: {} + + i18next@26.4.2(typescript@6.0.3): + optionalDependencies: + typescript: 6.0.3 + + ignore@5.3.2: {} + + ignore@7.0.11: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + ini@6.0.0: {} + + iron-webcrypto@1.2.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-arrayish@0.2.1: {} + + is-decimal@2.0.1: {} + + is-docker@4.0.0: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-plain-obj@4.1.0: {} + + isexe@2.0.0: {} + + isexe@4.0.0: {} + + jiti@2.6.1: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + json-parse-even-better-errors@2.3.1: {} + + json-parse-even-better-errors@6.0.0: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonc-parser@2.3.1: {} + + jsonc-parser@3.3.1: {} + + keyv@5.6.0: + dependencies: + '@keyv/serialize': 1.1.1 + + kleur@4.1.5: {} + + klona@2.0.6: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + 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 + + lines-and-columns@1.2.4: {} + + linkify-it@5.0.2: + dependencies: + uc.micro: 2.1.0 + + linkinator@8.1.0: + dependencies: + chalk: 6.0.1 + escape-html: 1.0.3 + glob: 13.0.6 + htmlparser2: 12.0.0 + marked: 18.0.14 + marked-gfm-heading-id: 4.1.4(marked@18.0.14) + meow: 14.1.0 + mime: 4.1.0 + saxes: 6.0.0 + srcset: 5.0.3 + undici: 8.11.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + longest-streak@3.1.0: {} + + lru-cache@11.5.3: {} + + lunr@2.3.9: {} + + magic-string@1.4.2: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + magicast@0.5.5: + dependencies: + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + source-map-js: 1.2.2 + + markdown-it@14.3.2: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.2 + mdurl: 2.1.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + marked-gfm-heading-id@4.1.4(marked@18.0.14): + dependencies: + github-slugger: 2.0.0 + marked: 18.0.14 + + marked@18.0.14: {} + + mdast-util-directive@3.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.3 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.3 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-character: 2.1.1 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.0.28: {} + + mdn-data@2.27.1: {} + + mdurl@2.1.0: {} + + memorystream@0.3.1: {} + + meow@14.1.0: {} + + micromark-core-commonmark@2.0.4: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.1.0 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-edit-map: 1.0.0 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-directive@4.0.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.1.0 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + parse-entities: 4.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-space@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.3 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-edit-map@1.0.0: + dependencies: + micromark-util-types: 2.0.3 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.3 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.3: {} + + micromark@4.0.3: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.4 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-edit-map: 1.0.0 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + transitivePeerDependencies: + - supports-color + + mime@4.1.0: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + + minipass@7.1.3: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.19: {} + + natural-compare@1.4.0: {} + + neotraverse@1.0.1: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.7: {} + + node-mock-http@1.0.5: {} + + normalize-path@3.0.0: {} + + npm-normalize-package-bin@6.0.0: {} + + npm-run-all2@9.0.3: + dependencies: + ansi-styles: 7.0.0 + cross-spawn: 7.0.6 + memorystream: 0.3.1 + picomatch: 4.0.7 + pidtree: 1.0.0 + read-package-json-fast: 6.0.0 + shell-quote: 1.11.0 + which: 7.0.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + obug@2.2.1: {} + + obug@3.0.0: {} + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.4 + + ohash@2.0.12: {} + + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@7.3.3: + dependencies: + yocto-queue: 1.2.2 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-queue@9.3.3: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 7.0.2 + + p-timeout@7.0.2: {} + + package-manager-detector@1.8.0: {} + + pagefind@1.5.2: + optionalDependencies: + '@pagefind/darwin-arm64': 1.5.2 + '@pagefind/darwin-x64': 1.5.2 + '@pagefind/freebsd-x64': 1.5.2 + '@pagefind/linux-arm64': 1.5.2 + '@pagefind/linux-x64': 1.5.2 + '@pagefind/windows-arm64': 1.5.2 + '@pagefind/windows-x64': 1.5.2 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.3 + minipass: 7.1.3 + + piccolore@0.1.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + pidtree@1.0.0: {} + + playwright-core@1.63.0: {} + + playwright@1.63.0: + dependencies: + playwright-core: 1.63.0 + + postcss-nested@6.2.0(postcss@8.5.28): + dependencies: + postcss: 8.5.28 + postcss-selector-parser: 6.1.4 + + postcss-selector-parser@6.1.4: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.28: + dependencies: + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + prelude-ls@1.2.1: {} + + prettier-plugin-astro@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(prettier@3.9.9): + dependencies: + '@astrojs/compiler-rs': 0.4.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + '@prettier/parse-srcset': 3.1.0 + prettier: 3.9.9 + sass-formatter: 0.8.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + prettier@3.9.9: {} + + prismjs@1.30.0: {} + + process-ancestry@0.1.0: {} + + property-information@7.2.0: {} + + punycode.js@2.3.1: {} + + punycode@2.3.1: {} + + qified@0.10.1: + dependencies: + hookified: 2.2.0 + + radix3@1.1.2: {} + + read-package-json-fast@6.0.0: + dependencies: + json-parse-even-better-errors: 6.0.0 + npm-normalize-package-bin: 6.0.0 + + readdirp@4.1.2: {} + + readdirp@5.1.1: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-expressive-code@0.44.2: + dependencies: + expressive-code: 0.44.2 + + remark-directive@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-directive: 3.1.0 + micromark-extension-directive: 4.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + request-light@0.5.8: {} + + request-light@0.7.0: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.1.0 + + rolldown@1.2.12: + dependencies: + '@oxc-project/types': 0.152.0 + '@rolldown/pluginutils': 1.0.1 + 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 + + s.color@0.0.15: {} + + sass-formatter@0.8.0: + dependencies: + suf-log: 2.5.3 + + satteri@0.10.5: + 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 + + sax@1.6.1: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + semver@7.8.5: {} + + sharp@0.35.5(@types/node@24.19.1): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + 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 + '@types/node': 24.19.1 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.11.0: {} + + shiki@4.5.0: + 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 + + sisteransi@1.0.5: {} + + sitemap@9.0.1: + dependencies: + '@types/node': 24.19.1 + '@types/sax': 1.2.7 + arg: 5.0.2 + sax: 1.6.1 + + smol-toml@1.9.0: {} + + source-map-js@1.2.2: {} + + space-separated-tokens@2.0.2: {} + + srcset@5.0.3: {} + + starlight-typedoc@0.23.1(@astrojs/starlight@0.42.5(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1))(typescript@6.0.3))(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1))(typedoc-plugin-markdown@4.13.1(typedoc@0.28.20(typescript@6.0.3)))(typedoc@0.28.20(typescript@6.0.3)): + dependencies: + '@astrojs/starlight': 0.42.5(astro@7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1))(typescript@6.0.3) + astro: 7.3.5(@types/node@24.19.1)(jiti@2.6.1)(yaml@2.9.1) + github-slugger: 2.0.0 + typedoc: 0.28.20(typescript@6.0.3) + typedoc-plugin-markdown: 4.13.1(typedoc@0.28.20(typescript@6.0.3)) + + std-env@4.3.0: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + string-width@8.3.0: + dependencies: + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.4.0 + + suf-log@2.5.3: + dependencies: + s.color: 0.0.15 + + svgo@4.1.0: + dependencies: + commander: 11.1.0 + css-select: 6.0.0 + css-tree: 3.2.1 + css-what: 7.0.0 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.6.1 + + tiny-inflate@1.0.3: {} + + tinybench@6.2.0: {} + + tinyclip@1.0.3: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyrainbow@3.2.0: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typedoc-plugin-markdown@4.13.1(typedoc@0.28.20(typescript@6.0.3)): + dependencies: + typedoc: 0.28.20(typescript@6.0.3) + + typedoc@0.28.20(typescript@6.0.3): + dependencies: + '@gerrit0/mini-shiki': 3.23.0 + lunr: 2.3.9 + markdown-it: 14.3.2 + minimatch: 10.2.6 + typescript: 6.0.3 + yaml: 2.9.1 + + typesafe-path@0.2.2: {} + + typescript-auto-import-cache@0.3.6: + dependencies: + semver: 7.8.5 + + typescript-eslint@8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/parser': 8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.11.0(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.11.0(jiti@2.6.1) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + uc.micro@2.1.0: {} + + ufo@1.6.4: {} + + ultrahtml@1.7.0: {} + + uncrypto@0.1.3: {} + + undici-types@7.24.6: {} + + undici@8.11.2: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.7.5: + dependencies: + css-tree: 3.2.1 + ohash: 2.0.12 + undici: 8.11.2 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unstorage@1.17.5: + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.11 + lru-cache: 11.5.3 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.4 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-extras@0.1.0: {} + + util-deprecate@1.0.2: {} + + verkit@0.4.1: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.12 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.19.1 + esbuild: 0.28.2 + fsevents: 2.3.3 + jiti: 2.6.1 + yaml: 2.9.1 + + vitefu@1.1.3(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1)): + optionalDependencies: + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1) + + vitest@5.0.3(@types/node@24.19.1)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.2 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(yaml@2.9.1) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 24.19.1 + '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) + transitivePeerDependencies: + - msw + + volar-service-css@0.0.71(@volar/language-service@2.4.28(typescript@6.0.3)): + dependencies: + vscode-css-languageservice: 6.3.10 + vscode-languageserver-textdocument: 1.0.15 + vscode-uri: 3.2.0 + optionalDependencies: + '@volar/language-service': 2.4.28(typescript@6.0.3) + + volar-service-emmet@0.0.71(@volar/language-service@2.4.28(typescript@6.0.3)): + dependencies: + '@emmetio/css-parser': 0.4.1 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 + vscode-uri: 3.2.0 + optionalDependencies: + '@volar/language-service': 2.4.28(typescript@6.0.3) + + volar-service-html@0.0.71(@volar/language-service@2.4.28(typescript@6.0.3)): + dependencies: + vscode-html-languageservice: 5.6.2 + vscode-languageserver-textdocument: 1.0.15 + vscode-uri: 3.2.0 + optionalDependencies: + '@volar/language-service': 2.4.28(typescript@6.0.3) + + volar-service-prettier@0.0.71(@volar/language-service@2.4.28(typescript@6.0.3))(prettier@3.9.9): + dependencies: + vscode-uri: 3.2.0 + optionalDependencies: + '@volar/language-service': 2.4.28(typescript@6.0.3) + prettier: 3.9.9 + + volar-service-typescript-twoslash-queries@0.0.71(@volar/language-service@2.4.28(typescript@6.0.3))(typescript@6.0.3): + dependencies: + vscode-uri: 3.2.0 + optionalDependencies: + '@volar/language-service': 2.4.28(typescript@6.0.3) + typescript: 6.0.3 + + volar-service-typescript@0.0.71(@volar/language-service@2.4.28(typescript@6.0.3))(typescript@6.0.3): + dependencies: + path-browserify: 1.0.1 + semver: 7.8.5 + typescript-auto-import-cache: 0.3.6 + vscode-languageserver-textdocument: 1.0.15 + vscode-nls: 5.2.0 + vscode-uri: 3.2.0 + optionalDependencies: + '@volar/language-service': 2.4.28(typescript@6.0.3) + typescript: 6.0.3 + + volar-service-yaml@0.0.71(@volar/language-service@2.4.28(typescript@6.0.3)): + dependencies: + vscode-uri: 3.2.0 + yaml-language-server: 1.23.0 + optionalDependencies: + '@volar/language-service': 2.4.28(typescript@6.0.3) + + vscode-css-languageservice@6.3.10: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.15 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.2.0 + + vscode-html-languageservice@5.6.2: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.15 + vscode-languageserver-types: 3.18.4 + vscode-uri: 3.2.0 + + vscode-json-languageservice@4.1.8: + dependencies: + jsonc-parser: 3.3.1 + vscode-languageserver-textdocument: 1.0.15 + vscode-languageserver-types: 3.18.4 + vscode-nls: 5.2.0 + vscode-uri: 3.2.0 + + vscode-jsonrpc@8.2.0: {} + + vscode-jsonrpc@9.0.3: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-protocol@3.18.4: + dependencies: + vscode-jsonrpc: 9.0.3 + vscode-languageserver-types: 3.18.4 + + vscode-languageserver-textdocument@1.0.15: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver-types@3.18.4: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-nls@5.2.0: {} + + vscode-uri@3.2.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@7.0.0: + dependencies: + isexe: 4.0.0 + + why-is-node-running@3.2.1: {} + + word-wrap@1.2.5: {} + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xmlchars@2.2.0: {} + + xxhash-wasm@1.1.0: {} + + y18n@5.0.8: {} + + yaml-language-server@1.23.0: + dependencies: + '@vscode/l10n': 0.0.18 + ajv: 8.20.0 + ajv-draft-04: 1.0.0(ajv@8.20.0) + ajv-i18n: 4.2.0(ajv@8.20.0) + prettier: 3.9.9 + request-light: 0.5.8 + vscode-json-languageservice: 4.1.8 + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.15 + vscode-languageserver-types: 3.18.4 + vscode-uri: 3.2.0 + yaml: 2.8.3 + + yaml@2.8.3: {} + + yaml@2.9.1: {} + + yargs-parser@22.0.0: {} + + yargs@18.2.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 8.3.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} + + zod@4.6.5: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..49c0ad7 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: false diff --git a/public/apple-touch-icon-180.png b/public/apple-touch-icon-180.png new file mode 100644 index 0000000..04b512b Binary files /dev/null and b/public/apple-touch-icon-180.png differ diff --git a/public/favicon-32.png b/public/favicon-32.png new file mode 100644 index 0000000..e8caaaa Binary files /dev/null and b/public/favicon-32.png differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..3ea5e58 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,14 @@ + + Gatepost + + + + diff --git a/public/og-image.png b/public/og-image.png new file mode 100644 index 0000000..37b3836 Binary files /dev/null and b/public/og-image.png differ diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..cfed83e --- /dev/null +++ b/renovate.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "description": [ + "Each pull request body ends with the sign-off line that the pull request job needs.", + "prHeader holds the line because a JSON string cannot wrap, and the template ends with it." + ], + "extends": ["schedule:weekly", "helpers:pinGitHubActionDigests", ":gitSignOff"], + "semanticCommits": "enabled", + "semanticCommitType": "chore", + "semanticCommitScope": "deps", + "commitMessageTopic": "{{depName}}", + "customManagers": [ + { + "description": "Pins of the uvx tools in the scripts of package.json", + "customType": "regex", + "managerFilePatterns": ["/^package\\.json$/"], + "matchStrings": [ + "uvx (?:--with \\S+ )*(?[A-Za-z0-9][A-Za-z0-9._-]*)@(?[^\\s\"]+)", + "--with (?[A-Za-z0-9][A-Za-z0-9._-]*)==(?[^\\s\"]+)" + ], + "datasourceTemplate": "pypi" + } + ], + "packageRules": [ + { + "matchPackageNames": ["@eslint-community/eslint-plugin-eslint-comments"], + "commitMessageTopic": "eslint-comments plugin" + }, + { + "description": [ + "The standards name the TypeScript version.", + "TypeScript 6.1 and later wait until typescript-eslint supports them.", + "An update to 6.1 or later needs a change to the standards first." + ], + "matchPackageNames": ["typescript"], + "allowedVersions": "<6.1" + }, + { + "description": [ + "The site builds on Node 24, so its types follow Node 24.", + "A major update needs a change to the Node version of the site first." + ], + "matchPackageNames": ["@types/node"], + "matchUpdateTypes": ["major"], + "enabled": false + } + ], + "prHeader": "Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>", + "prBodyTemplate": "Weekly dependency update.\n\n{{{header}}}" +} diff --git a/scripts/check-licences.ts b/scripts/check-licences.ts new file mode 100644 index 0000000..2a787d8 --- /dev/null +++ b/scripts/check-licences.ts @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// Checks each dependency against DEP-4: a permissive licence, or an exception with its reason. +// It reads `pnpm licenses list --json`, so run `pnpm install` first. +import { execFileSync } from 'node:child_process'; +import process from 'node:process'; + +/** The packages of one licence, as `pnpm licenses list --json` lists them. */ +export type LicenceReport = Readonly>; + +// The licences that DEP-4 names, and three more permissive ones that tools in the tree use. +const ALLOWED = [ + 'MIT', + 'ISC', + 'Apache-2.0', + 'BSD-2-Clause', + 'BSD-3-Clause', + '0BSD', + 'BlueOak-1.0.0', + 'CC0-1.0', + 'Python-2.0', +]; + +// A name and a licence that may pass, and the reason. A change of licence fails the check. +const EXCEPTIONS: readonly { name: RegExp; licence: string; reason: string }[] = [ + { + name: /^@fontsource\/overpass$/, + licence: 'OFL-1.1', + reason: 'The brand font. Each font file holds its copyright and a link to the licence.', + }, + { + name: /^@img\/sharp-libvips-/, + licence: 'LGPL-3.0-or-later', + reason: 'Astro runs it at build time. The site does not ship it, and we do not change it.', + }, + { + name: /^lightningcss(?:-.+)?$/, + licence: 'MPL-2.0', + reason: 'A build tool that Vite runs, unchanged. The site does not ship it.', + }, + { + name: /^(?:@axe-core\/playwright|axe-core)$/, + licence: 'MPL-2.0', + reason: 'The accessibility checker of the browser tests, unchanged. The site does not ship it.', + }, +]; + +/** + * Lists the packages that DEP-4 does not allow. + * + * @param report - The packages by licence. + * @returns One line for each package that breaks the rule. + */ +export function findViolations(report: LicenceReport): readonly string[] { + return Object.entries(report).flatMap(([licence, packages]) => + ALLOWED.includes(licence) + ? [] + : packages + .filter(({ name }) => !EXCEPTIONS.some((e) => e.licence === licence && e.name.test(name))) + .map(({ name }) => `${name} uses ${licence}, which DEP-4 does not allow.`), + ); +} + +if (import.meta.main) { + const json = execFileSync('pnpm', ['licenses', 'list', '--json'], { encoding: 'utf8' }); + const violations = findViolations(JSON.parse(json) as LicenceReport); + process.stdout.write(violations.map((line) => `${line}\n`).join('')); + process.exitCode = violations.length === 0 ? 0 : 1; +} diff --git a/scripts/check-links.ts b/scripts/check-links.ts new file mode 100644 index 0000000..da80196 --- /dev/null +++ b/scripts/check-links.ts @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// Checks the links of the built site. By default it follows only the links inside the site, +// with their fragments, so it needs no network. With --external, it also checks the links to +// other sites, as a weekly job does. It never calls the hosts that NIPOST's rules close to us. +import { cpSync, mkdtempSync, rmSync } from 'node:fs'; +import { createServer } from 'node:net'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import process from 'node:process'; +import { check, LinkState } from 'linkinator'; + +// linkinator serves a folder itself, on a free port of this address. +const LOCAL = 'http://127\\.0\\.0\\.1:\\d+'; + +// A crawl that finds fewer links than this has missed the site. +export const MINIMUM_LINKS = 100; + +// Every host of postcode.gov.ng except the public docs host: the gateway, the platform, the +// dashboard and the API paths of the website. A new NIPOST host is skipped by default. +const NIPOST = '(?:[^/?#]*\\.)?postcode\\.gov\\.ng(?:[:/?#]|$)'; +const DOCS_HOST = 'docs\\.postcode\\.gov\\.ng(?:[:/?#]|$)'; +const NEVER = [`^https?://(?!${DOCS_HOST})${NIPOST}`]; + +// The published address of the site. Links to it, such as the canonical link of a page, go to the +// local server in its place, so that a wrong hand-written link of this kind fails the check. +const PUBLISHED = 'https://gatepost-dev.github.io'; +const PUBLISHED_PATTERN = 'https://gatepost-dev\\.github\\.io'; + +// The 404 page answers at any address, and it has no page at /docs/404/. Its canonical link names +// that address, so this one link is skipped. +const NO_PAGE = `^(?:${LOCAL}|${PUBLISHED_PATTERN})/docs/404/$`; + +/** + * Lists the links that the check skips. + * + * @param external - Whether the check follows links to other sites. + * @returns The patterns of the links to skip. + */ +export function linksToSkip(external: boolean): readonly string[] { + return external ? [...NEVER, NO_PAGE] : [...NEVER, NO_PAGE, `^(?!${LOCAL}|${PUBLISHED_PATTERN})`]; +} + +// An address that nothing uses now. The check needs it before the server starts, because the +// rewrite of the published address names it. +function freePort(): Promise { + return new Promise((resolve, reject) => { + const server = createServer(); + server.once('error', reject); + server.listen(0, '127.0.0.1', () => { + const address = server.address(); + const port = typeof address === 'object' && address !== null ? address.port : 0; + server.close(() => { + resolve(port); + }); + }); + }); +} + +/** + * Checks the links of a built site, as GitHub Pages serves it: under /docs/. A link that lacks + * the /docs base finds no page, as it does on GitHub Pages. + * + * @param dist - The folder of the built site. + * @param external - Whether the check follows links to other sites. + * @returns The number of links that the check followed, and a line for each broken link. + */ +export async function brokenLinks( + dist: string, + external: boolean, +): Promise<{ total: number; broken: readonly string[] }> { + const port = await freePort(); + const root = mkdtempSync(join(tmpdir(), 'gatepost-links-')); + try { + cpSync(dist, join(root, 'docs'), { recursive: true }); + const result = await check({ + path: 'docs/', + serverRoot: root, + port, + urlRewriteExpressions: [ + { pattern: new RegExp(`^${PUBLISHED}`), replacement: `http://127.0.0.1:${String(port)}` }, + ], + recurse: true, + checkFragments: true, + // Many requests to one host bring 429 answers, so the job asks fewer and tries again. + concurrency: 10, + retry: true, + linksToSkip: [...linksToSkip(external)], + }); + const broken = result.links + .filter((link) => link.state === LinkState.BROKEN) + .map((link) => `${link.parent ?? ''}: ${link.url} (${String(link.status)})`); + return { total: result.links.length, broken }; + } finally { + rmSync(root, { recursive: true, force: true }); + } +} + +/** + * Decides whether the check passes. + * + * @param total - The number of links that the crawl followed. + * @param broken - A line for each broken link. + * @returns The lines to print, and the exit code. A crawl with too few links fails. + */ +export function verdict( + total: number, + broken: readonly string[], +): { lines: readonly string[]; code: 0 | 1 } { + const lines = [...broken, `${String(total)} links, ${String(broken.length)} broken.`]; + if (total < MINIMUM_LINKS) { + lines.push(`Only ${String(total)} links found. The crawl missed the site.`); + } + return { lines, code: broken.length === 0 && total >= MINIMUM_LINKS ? 0 : 1 }; +} + +if (import.meta.main) { + const { total, broken } = await brokenLinks('dist', process.argv.includes('--external')); + const { lines, code } = verdict(total, broken); + for (const line of lines) process.stdout.write(`${line}\n`); + process.exitCode = code; +} diff --git a/scripts/check-prose.ts b/scripts/check-prose.ts new file mode 100644 index 0000000..c03c2fb --- /dev/null +++ b/scripts/check-prose.ts @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// Checks the prose of the hand-written pages against the parts of DOC-4 that a script can see: +// sentence length, semicolons, dashes and marketing words. A reviewer checks the rest of DOC-4, +// such as the active voice and phrasal verbs. The pages that the build writes from the spec and +// the doc comments are not checked here, because their own repos own that text. +import { execFileSync } from 'node:child_process'; +import { existsSync, readFileSync } from 'node:fs'; +import process from 'node:process'; + +/** One break of a prose rule, with the line of the page where it starts. */ +export interface ProseProblem { + readonly line: number; + readonly message: string; +} + +const MAX_WORDS = 25; +const EM_DASH = String.fromCodePoint(0x2014); +const EN_DASH = String.fromCodePoint(0x2013); +// DOC-4 names "powerful" and "seamless". The others are the words that make docs read like +// an advert or like a machine wrote them. +const MARKETING_WORDS = [ + 'powerful', + 'seamless', + 'seamlessly', + 'simply', + 'just', + 'easy', + 'easily', + 'effortless', + 'robust', + 'leverage', + 'utilise', + 'utilize', + 'cutting-edge', + 'blazing', + 'delve', +]; +const MARKETING = new RegExp(`\\b(${MARKETING_WORDS.join('|')})\\b`, 'i'); + +// The text of a line that a reader sees as prose: no code, no link targets, no HTML or MDX tags. +function proseOf(line: string): string { + return line + .replace(/`[^`]*`/g, 'code') + .replace(/\]\([^)]*\)/g, ']') + .replace(/<[^>]+>/g, ' ') + .replace(/^\s*(?:[-*+]|\d+\.|#+|>)\s+/, '') + .replace(/^\s*\|/, '') + .replace(/\|/g, '.'); +} + +// Front matter and code fences hold no prose, except the title and the description. +function proseLines(text: string): readonly (readonly [number, string])[] { + const lines = text.split('\n'); + const prose: (readonly [number, string])[] = []; + let inFence = false; + let inFrontMatter = lines[0] === '---'; + lines.forEach((line, index) => { + if (inFrontMatter) { + inFrontMatter = index === 0 || line !== '---'; + const field = /^(?:title|description|\s+tagline):\s*(.*)$/.exec(line); + if (field?.[1] !== undefined) prose.push([index + 1, field[1]]); + return; + } + if (/^\s*(?:```|~~~)/.test(line)) { + inFence = !inFence; + return; + } + if (!inFence && !/^\s*(?:import |export |\|\s*-)/.test(line)) prose.push([index + 1, line]); + }); + return prose; +} + +/** + * Finds the breaks of the prose rules in one page. + * + * @param text - The Markdown or MDX source of the page. + * @returns Each problem, in the order of the lines. + */ +export function checkProse(text: string): readonly ProseProblem[] { + const problems: ProseProblem[] = []; + for (const [line, raw] of proseLines(text)) { + const prose = proseOf(raw); + if (prose.includes(';')) problems.push({ line, message: 'Use a full stop, not a semicolon.' }); + if (prose.includes(EM_DASH) || prose.includes(` ${EN_DASH} `)) { + problems.push({ line, message: 'Use a full stop or a comma, not a dash.' }); + } + const word = MARKETING.exec(prose)?.[1]; + if (word !== undefined) problems.push({ line, message: `Use a plain word, not "${word}".` }); + for (const sentence of prose.split(/[.?!:](?:\s|$)/)) { + const words = sentence.split(/\s+/).filter((part) => /\w/.test(part)).length; + if (words > MAX_WORDS) { + problems.push({ line, message: `A sentence has ${String(words)} words. Use 25 or fewer.` }); + } + } + } + return problems; +} + +/** + * Lists the hand-written pages: the Markdown and MDX files under src/content/docs that git + * tracks, or would track. The build writes the other pages there, and git ignores them. + * + * @returns The paths of the pages, relative to the root of the repo. + */ +export function trackedPages(): readonly string[] { + const output = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard'], { + encoding: 'utf8', + }); + return output + .split('\n') + .filter((path) => /^src\/content\/docs\/.+\.mdx?$/.test(path) && existsSync(path)); +} + +if (import.meta.main) { + let count = 0; + for (const path of trackedPages()) { + for (const { line, message } of checkProse(readFileSync(path, 'utf8'))) { + process.stdout.write(`${path}:${String(line)}: ${message}\n`); + count += 1; + } + } + process.exitCode = count === 0 ? 0 : 1; +} diff --git a/scripts/client-samples.ts b/scripts/client-samples.ts new file mode 100644 index 0000000..b1a9040 --- /dev/null +++ b/scripts/client-samples.ts @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// Records what @gatepost/client returns for a few calls, so that the playground can show real +// results with no network. The build runs the client against Gatepost's mock gateway, which +// answers with the synthetic fixtures of the spec. +import { mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { PostcodeClient, PostcodeError } from '@gatepost/client'; +import { startMockServer } from '@gatepost/mock-server'; + +/** One call of the client, as the page shows it, and what the client returned. */ +export interface ClientSample { + readonly call: string; + readonly result: unknown; +} + +/** The outcome that a sample expects: a result, or the exact code of one error. */ +type Expected = 'success' | PostcodeError['code']; + +/** One call to make, with the outcome that the build requires of it. */ +export interface SampleCall { + readonly key: string; + readonly call: string; + readonly expected: Expected; + readonly run: (client: PostcodeClient) => Promise; +} + +// The keys come from spec/fixtures/keys.json. Each one makes the mock gateway act as a real +// key with that grant would. +const CALLS: readonly SampleCall[] = [ + { + key: 'nipost_test_mock_l3', + call: "await client.lookup('FC-01-Z99-ZZ-01')", + expected: 'success', + run: (client) => client.lookup('FC-01-Z99-ZZ-01'), + }, + { + key: 'nipost_test_mock_l3', + call: "await client.lookup('FC-01-Z99-ZZ-01', { level: 2 })", + expected: 'success', + run: (client) => client.lookup('FC-01-Z99-ZZ-01', { level: 2 }), + }, + { + key: 'nipost_test_mock_l3', + call: "await client.lookup('FC-01-Z99-ZZ-02')", + expected: 'success', + run: (client) => client.lookup('FC-01-Z99-ZZ-02'), + }, + { + key: 'nipost_test_mock_l3', + call: 'await client.reverse(9, 7)', + expected: 'success', + run: (client) => client.reverse(9, 7), + }, + { + key: 'nipost_test_mock_l3', + call: "await client.autocomplete('fc 01 z')", + expected: 'success', + run: (client) => client.autocomplete('fc 01 z'), + }, + { + key: 'nipost_test_mock_no_credits', + call: "await client.lookup('FC-01-Z99-ZZ-01', { level: 2 }) // a key with no credits", + expected: 'insufficient_credits', + run: (client) => client.lookup('FC-01-Z99-ZZ-01', { level: 2 }), + }, +]; + +// An error shows the fields that a caller reads, as the client guide lists them. +function describeError(error: PostcodeError): unknown { + const { name, code, status, apiCode, retryAfterMs } = error; + return { name, code, status, apiCode, retryAfterMs }; +} + +function mismatch(call: string, expected: Expected, got: string): Error { + return new Error(`Sample "${call}" expected ${expected} but got ${got}.`); +} + +// The no-NIPOST rule rests on every client calling the mock gateway on this machine. +function assertLoopback(url: string): void { + const { hostname } = new URL(url); + if (!['127.0.0.1', 'localhost', '[::1]'].includes(hostname)) { + throw new Error(`The mock gateway listens on ${hostname}, which is not a loopback host.`); + } +} + +async function record(client: PostcodeClient, sample: SampleCall): Promise { + const { call, expected, run } = sample; + try { + const result = await run(client); + if (expected !== 'success') throw mismatch(call, expected, 'success'); + return { call, result }; + } catch (error) { + if (!(error instanceof PostcodeError)) throw error; + if (error.code !== expected) throw mismatch(call, expected, error.code); + return { call, result: describeError(error) }; + } +} + +/** + * Makes each sample call against a mock gateway that runs for the length of the calls. The build + * fails when a call ends in any outcome other than the one that the sample expects. + * + * @param calls - The calls to make, in order. + * @returns Each call with its result, in order. + */ +export async function recordClientSamples( + calls: readonly SampleCall[] = CALLS, +): Promise { + const mock = await startMockServer({ port: 0 }); + try { + assertLoopback(mock.url); + const samples: ClientSample[] = []; + for (const sample of calls) { + const client = new PostcodeClient({ apiKey: sample.key, baseUrl: mock.url }); + samples.push(await record(client, sample)); + } + return samples; + } finally { + await mock.close(); + } +} + +/** + * Writes the samples to `src/generated/client-samples.json`, which the playground page reads. + * + * @param root - The root folder of the docs repo. + */ +export async function writeClientSamples(root: string): Promise { + const folder = join(root, 'src/generated'); + mkdirSync(folder, { recursive: true }); + const samples = await recordClientSamples(); + writeFileSync(join(folder, 'client-samples.json'), `${JSON.stringify(samples, null, 2)}\n`); +} diff --git a/scripts/php-reference.php b/scripts/php-reference.php new file mode 100644 index 0000000..340c18a --- /dev/null +++ b/scripts/php-reference.php @@ -0,0 +1,335 @@ + + +namespace Gatepost\Docs; + +use FilesystemIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use ReflectionClass; +use ReflectionClassConstant; +use ReflectionEnum; +use ReflectionEnumBackedCase; +use ReflectionMethod; +use ReflectionNamedType; +use ReflectionParameter; +use ReflectionProperty; +use ReflectionType; +use RuntimeException; +use SplFileInfo; + +const PREFIX = 'Gatepost\\Postcode\\'; +// A @param tag: a type that can hold spaces inside <>, {} or (), the name and the meaning. +const PARAM_TAG = '/^(?:[^\s<{(]|<[^>]*>|\{[^}]*\}|\([^)]*\))+\s+\$(\w+)\s*(.*)$/'; + +/** + * @return array{string, list} The description, and each tag with its text. + */ +function readDocComment(string|false $comment): array +{ + if ($comment === false) { + return ['', []]; + } + $lines = \explode("\n", \trim(\substr($comment, 3, -2))); + $lines = \array_map(static fn(string $line): string => \preg_replace('/^\s*\* ?/', '', $line) + ?? $line, $lines); + $description = []; + $tags = []; + foreach ($lines as $line) { + if (\preg_match('/^@(\w+)\s*(.*)$/', $line, $tag) === 1) { + $tags[] = [$tag[1], $tag[2]]; + } elseif ($tags !== []) { + $last = \array_key_last($tags); + $tags[$last][1] .= ' ' . \trim($line); + } else { + $description[] = $line; + } + } + + return [\trim(\implode("\n", $description)), $tags]; +} + +function isInternal(string|false $comment): bool +{ + return $comment !== false && \str_contains($comment, '@internal'); +} + +function typeName(?ReflectionType $type): string +{ + $name = (string) $type; + + return \str_replace(PREFIX, '', $name); +} + +function isInternalType(?ReflectionType $type): bool +{ + return $type instanceof ReflectionNamedType && \str_contains($type->getName(), '\\Internal\\'); +} + +// A default value or a constant as PHP code. The package uses scalars and null only. +function literal(mixed $value): string +{ + return match (true) { + $value === null => 'null', + \is_bool($value) => $value ? 'true' : 'false', + \is_int($value), \is_float($value) => (string) $value, + \is_string($value) => "'" . \addcslashes($value, "'\\") . "'", + default => throw new RuntimeException('A default value is not a scalar.'), + }; +} + +function parameterText(ReflectionParameter $parameter): string +{ + $text = \trim(typeName($parameter->getType()) . ' $' . $parameter->getName()); + + return $parameter->isDefaultValueAvailable() + ? $text . ' = ' . literal($parameter->getDefaultValue()) + : $text; +} + +/** + * @return list The parameters that callers use. A parameter of an internal + * type is a seam for the tests of the package. + */ +function publicParameters(ReflectionMethod $method): array +{ + return \array_values(\array_filter( + $method->getParameters(), + static fn(ReflectionParameter $parameter): bool => !isInternalType($parameter->getType()), + )); +} + +/** + * @return list + */ +function methodLines(ReflectionMethod $method): array +{ + [$description, $tags] = readDocComment($method->getDocComment()); + $parameters = \array_map(parameterText(...), publicParameters($method)); + $static = $method->isStatic() ? 'static ' : ''; + $return = $method->hasReturnType() ? ': ' . typeName($method->getReturnType()) : ''; + $head = "public {$static}function {$method->getName()}("; + $signature = $head . \implode(', ', $parameters) . "){$return}"; + // A long signature puts each parameter on its own line, as the PER coding style does. + if (\strlen($signature) > 100) { + $signature = $head . "\n " . \implode(",\n ", $parameters) . ",\n){$return}"; + } + $lines = ["### {$method->getName()}()", '', '```php', $signature, '```', '']; + if ($description !== '') { + $lines = [...$lines, $description, '']; + } + + return [...$lines, ...tagLines($tags, publicParameters($method))]; +} + +/** + * @param list $tags + * @param list $parameters + * + * @return list + */ +function tagLines(array $tags, array $parameters): array +{ + $names = \array_map(static fn(ReflectionParameter $p): string => $p->getName(), $parameters); + $meanings = []; + $throws = []; + foreach ($tags as [$tag, $text]) { + $text = \trim((string) \preg_replace('/\s+/', ' ', $text)); + if ($tag === 'param' && \preg_match(PARAM_TAG, $text, $param) === 1) { + if (\in_array($param[1], $names, true)) { + $meanings[$param[1]] = $param[2]; + } + } elseif ($tag === 'throws') { + $throws[] = "- {$text}"; + } + } + $rows = []; + foreach ($names as $name) { + $rows[] = "| `\${$name}` | " . ($meanings[$name] ?? 'Not documented.') . ' |'; + } + $lines = $rows === [] ? [] : ['#### Parameters', '', '| Name | Meaning |', '| --- | --- |']; + $lines = $rows === [] ? [] : [...$lines, ...$rows, '']; + + return $throws === [] ? $lines : [...$lines, '#### Throws', '', ...$throws, '']; +} + +/** + * @return list + */ +function memberLines(ReflectionClassConstant|ReflectionProperty $member, string $code): array +{ + [$description] = readDocComment($member->getDocComment()); + $meaning = $description === '' ? '' : ': ' . \str_replace("\n", ' ', $description); + + return ["- `{$code}`{$meaning}"]; +} + +/** + * @param ReflectionClass $class + * + * @return list + */ +function classLines(ReflectionClass $class): array +{ + [$description] = readDocComment($class->getDocComment()); + $short = $class->getShortName(); + $first = \preg_split('/(?<=\.)\s/', \str_replace("\n", ' ', $description))[0] ?? ''; + $lines = [ + '---', + "title: {$short}", + 'description: ' . \json_encode($first, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE), + 'editUrl: false', + '---', + '', + "`{$class->getName()}`", + '', + $description, + '', + ]; + $members = []; + if ($class instanceof ReflectionEnum) { + foreach ($class->getCases() as $case) { + $value = $case instanceof ReflectionEnumBackedCase + ? ' = ' . literal($case->getBackingValue()) + : ''; + $members = [...$members, ...memberLines($case, $case->getName() . $value)]; + } + } + foreach ($class->getReflectionConstants(ReflectionClassConstant::IS_PUBLIC) as $constant) { + if (!$constant->isEnumCase() && !isInternal($constant->getDocComment())) { + $code = $constant->getName() . ' = ' . literal($constant->getValue()); + $members = [...$members, ...memberLines($constant, $code)]; + } + } + // An enum has the properties name and value, which the cases already show. + $properties = $class->isEnum() ? [] : $class->getProperties(ReflectionProperty::IS_PUBLIC); + foreach ($properties as $property) { + if (isInternal($property->getDocComment())) { + continue; + } + $code = typeName($property->getType()) . ' $' . $property->getName(); + $members = [...$members, ...memberLines($property, $code)]; + } + if ($members !== []) { + $lines = [...$lines, '## Members', '', ...$members, '']; + } + $methods = \array_filter( + $class->getMethods(ReflectionMethod::IS_PUBLIC), + static fn(ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() + === $class->getName() && !isInternal($method->getDocComment()) + && !\in_array($method->getName(), ['cases', 'from', 'tryFrom'], true), + ); + if ($methods !== []) { + $lines[] = '## Methods'; + $lines[] = ''; + foreach ($methods as $method) { + $lines = [...$lines, ...methodLines($method)]; + } + } + + return $lines; +} + +/** + * @return list> The public classes, enums and interfaces, by name. + */ +function publicClasses(string $source): array +{ + \spl_autoload_register(static function (string $name) use ($source): void { + $file = $source . '/' . \str_replace('\\', '/', \substr($name, \strlen(PREFIX))) . '.php'; + if (\str_starts_with($name, PREFIX) && \is_file($file)) { + require_once $file; + } + }); + $classes = []; + $files = []; + $found = new RecursiveIteratorIterator(new RecursiveDirectoryIterator( + $source, + FilesystemIterator::SKIP_DOTS, + )); + foreach ($found as $item) { + if ($item instanceof SplFileInfo && $item->getExtension() === 'php') { + $files[] = $item->getPathname(); + } + } + foreach ($files as $file) { + $path = \substr($file, \strlen($source) + 1, -4); + $name = PREFIX . \str_replace('/', '\\', $path); + $exists = \class_exists($name) || \interface_exists($name) || \enum_exists($name); + if (!$exists) { + throw new RuntimeException("{$file} holds no class named {$name}."); + } + $class = \enum_exists($name) ? new ReflectionEnum($name) : new ReflectionClass($name); + if (!\str_contains($path, 'Internal') && !isInternal($class->getDocComment())) { + $classes[$name] = $class; + } + } + \ksort($classes); + + return \array_values($classes); +} + +function slugOf(string $name): string +{ + return \strtolower(\str_replace('\\', '-', \substr($name, \strlen(PREFIX)))); +} + +/** + * @param list $arguments + */ +function writeReference(array $arguments): int +{ + if (\count($arguments) !== 2) { + \fwrite(\STDERR, "Usage: php scripts/php-reference.php \n"); + + return 2; + } + [$package, $output] = $arguments; + if (!\is_dir($package . '/src')) { + \fwrite(\STDERR, "The package folder {$package} has no src folder.\n"); + + return 1; + } + $classes = publicClasses($package . '/src'); + if ($classes === []) { + \fwrite(\STDERR, "The folder {$package}/src holds no public class. Write no page.\n"); + + return 1; + } + if (!\is_dir($output) && !\mkdir($output, 0o777, true)) { + throw new RuntimeException("Cannot make {$output}."); + } + $index = [ + '---', + 'title: gatepost/postcode', + 'description: The public classes and enums of the PHP package.', + 'editUrl: false', + '---', + '', + 'The doc comments of the PHP package give this reference. Each page lists the public', + 'members of one class or enum. A name with a backslash, such as', + '`Psr\Http\Client\ClientInterface`, is a full name. A name without one is in the', + '`Gatepost\Postcode` namespace.', + '', + ]; + foreach ($classes as $class) { + $name = $class->getName(); + $text = \implode("\n", classLines($class)) . "\n"; + \file_put_contents($output . '/' . slugOf($name) . '.md', $text); + $index[] = '- [`' . \substr($name, \strlen(PREFIX)) . '`](' . slugOf($name) . '/)'; + } + \file_put_contents($output . '/index.md', \implode("\n", $index) . "\n"); + + return 0; +} + +exit(writeReference(\array_slice($argv, 1))); diff --git a/scripts/spec-pages.ts b/scripts/spec-pages.ts new file mode 100644 index 0000000..9fcd76f --- /dev/null +++ b/scripts/spec-pages.ts @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// Writes the spec pages of the site from the spec submodule, so that the site shows the grammar, +// the client contract and the glossary that the SDKs implement, word for word. +import { execFileSync } from 'node:child_process'; +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +/** A page that the site builds from one file of the spec. */ +export interface SpecPage { + readonly slug: string; + readonly text: string; +} + +const SOURCES = [ + { + file: 'grammar.md', + slug: 'grammar', + title: 'Postcode grammar', + description: 'How every Gatepost SDK reads and writes postcodes, and the core interface.', + }, + { + file: 'client.md', + slug: 'client', + title: 'Client contract', + description: "How every Gatepost client calls NIPOST's gateway: results, errors and retries.", + }, + { + file: 'CONTEXT.md', + slug: 'glossary', + title: 'Glossary', + description: 'One name for each concept, in every language and every repo.', + }, +] as const; + +/** + * Puts each Markdown table in a box that scrolls and takes the keyboard focus. A wide table + * scrolls sideways on a narrow screen, and a keyboard user cannot reach it without a focus stop + * (WCAG 2.1.1). The label of the box names the heading above the table. + * + * @param markdown - The Markdown of a page. It must hold no code fence with a table in it. + * @returns The Markdown with each table inside an HTML box. + */ +export function wrapTables(markdown: string): string { + const out: string[] = []; + let heading = ''; + let inTable = false; + for (const line of markdown.split('\n')) { + const isRow = line.startsWith('|'); + if (isRow && !inTable) { + const label = heading === '' ? 'Table' : `Table: ${heading}`; + const box = `
`; + out.push(box, ''); + } else if (!isRow && inTable) { + out.push('', '
'); + } + inTable = isRow; + heading = /^#{1,6} /.test(line) ? line.replace(/^#+ /, '').trim() : heading; + out.push(line); + } + return out.join('\n'); +} + +/** + * Builds the spec pages from the files of a spec folder. + * + * @param specDir - The spec folder, such as the `spec` submodule. + * @param commit - The commit of the spec folder, for the link to the source. + * @returns One page for each spec file, with its front matter. + */ +export function specPages(specDir: string, commit: string): readonly SpecPage[] { + const version = readFileSync(join(specDir, 'VERSION'), 'utf8').trim(); + return SOURCES.map(({ file, slug, title, description }, index) => { + const source = readFileSync(join(specDir, file), 'utf8'); + // The site shows the title above the page, so the first heading of the file goes. + const body = source.replace(/^# .*\n+/, ''); + const url = `https://github.com/gatepost-dev/spec/blob/${commit}/${file}`; + const frontMatter = [ + '---', + `title: ${title}`, + `description: "${description}"`, + 'editUrl: false', + `sidebar: { order: ${String(index + 1)} }`, + '---', + ].join('\n'); + const note = `:::note\nThis page shows [\`${file}\`](${url}) from spec ${version}.\n:::`; + return { slug, text: `${frontMatter}\n\n${note}\n\n${wrapTables(body)}` }; + }); +} + +/** + * Writes the spec pages into the docs collection. The build calls it before it reads the pages. + * + * @param root - The root folder of the docs repo. + */ +export function writeSpecPages(root: string): void { + const specDir = join(root, 'spec'); + const commit = execFileSync('git', ['-C', specDir, 'rev-parse', 'HEAD'], { encoding: 'utf8' }); + const folder = join(root, 'src/content/docs/spec'); + mkdirSync(folder, { recursive: true }); + for (const { slug, text } of specPages(specDir, commit.trim())) { + writeFileSync(join(folder, `${slug}.md`), text); + } +} diff --git a/spec b/spec new file mode 160000 index 0000000..9e20f87 --- /dev/null +++ b/spec @@ -0,0 +1 @@ +Subproject commit 9e20f87addb2a2b238cea82d65b9bd071bedc3f6 diff --git a/src/assets/gatepost-lockup-dark.svg b/src/assets/gatepost-lockup-dark.svg new file mode 100644 index 0000000..091e708 --- /dev/null +++ b/src/assets/gatepost-lockup-dark.svg @@ -0,0 +1,60 @@ + + Gatepost + + + + diff --git a/src/assets/gatepost-lockup.svg b/src/assets/gatepost-lockup.svg new file mode 100644 index 0000000..88a837f --- /dev/null +++ b/src/assets/gatepost-lockup.svg @@ -0,0 +1,60 @@ + + Gatepost + + + + diff --git a/src/components/ClientSamples.astro b/src/components/ClientSamples.astro new file mode 100644 index 0000000..5cb3d27 --- /dev/null +++ b/src/components/ClientSamples.astro @@ -0,0 +1,15 @@ +--- +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// The answers of the client that the build recorded against the mock gateway. The page needs no +// script and makes no call to show them. +import { Code } from '@astrojs/starlight/components'; +import samples from '../generated/client-samples.json'; +--- + +{samples.map(({ call, result }) => ( + + + + +))} diff --git a/src/components/Footer.astro b/src/components/Footer.astro new file mode 100644 index 0000000..66d0759 --- /dev/null +++ b/src/components/Footer.astro @@ -0,0 +1,19 @@ +--- +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// Every page carries the line that says that NIPOST did not make the site (LIC-3). +import Default from '@astrojs/starlight/components/Footer.astro'; +--- + + +

Unofficial. Not made or endorsed by NIPOST.

+ + diff --git a/src/components/Playground.astro b/src/components/Playground.astro new file mode 100644 index 0000000..53e72c7 --- /dev/null +++ b/src/components/Playground.astro @@ -0,0 +1,150 @@ +--- +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// The playground runs @gatepost/core in the reader's browser. The field has no form and no name, +// so the Enter key sends nothing, and the address bar never holds what the reader typed. +import { message } from '../playground/describe.ts'; +--- + +
+ +

+ {message('playground.hint')} +

+ + +

+ {message('playground.idle')} +

+ +

{message('playground.segments')}

+
    +

    {message('playground.details')}

    +
    +

    {message('accuracy.heading')}

    + +

    + {message('accuracy.hint')} +

    + +

    + +
    + + + + diff --git a/src/content.config.ts b/src/content.config.ts new file mode 100644 index 0000000..a45e1dd --- /dev/null +++ b/src/content.config.ts @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { docsLoader } from '@astrojs/starlight/loaders'; +import { docsSchema } from '@astrojs/starlight/schema'; +import { defineCollection } from 'astro:content'; + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), +}; diff --git a/src/content/docs/404.md b/src/content/docs/404.md new file mode 100644 index 0000000..962822e --- /dev/null +++ b/src/content/docs/404.md @@ -0,0 +1,11 @@ +--- +title: Page not found +description: This page does not exist. +head: + - tag: meta + attrs: + name: robots + content: noindex +--- + +This page does not exist. Use the search box or the menu to find the page that you want. diff --git a/src/content/docs/guides/php.md b/src/content/docs/guides/php.md new file mode 100644 index 0000000..a2b12b7 --- /dev/null +++ b/src/content/docs/guides/php.md @@ -0,0 +1,122 @@ +--- +title: Check and look up postcodes in PHP +description: Read Nigeria's digital postcodes offline and query NIPOST's gateway with gatepost/postcode. +sidebar: + label: PHP + order: 3 +--- + +`gatepost/postcode` reads the postcodes that users type or paste, with no network call. Its client queries NIPOST's gateway with your own API key. It needs PHP 8.1 or later, and it does not need the intl or mbstring extensions. + +## Install + +The first alpha is not published yet. This page will say when it is. + +```sh +composer require gatepost/postcode:^0.1@alpha +``` + +Composer installs only stable releases unless the constraint allows an alpha. + +## Read a postcode + +`Postcode::parse` takes the text that a user typed, and returns a result. It never throws. + +```php +isOk()) { + echo $result->value->canonical, "\n"; // EK-01-A03-FK-01 + echo $result->value->display, "\n"; // EK 01 A03 FK 01 +} +``` + +When `isOk()` is false, `error->code` names the first problem that `parse` found. The codes are the same in every Gatepost SDK. The [TypeScript guide](../typescript/#tell-the-user-what-is-wrong) lists them. For a typo with a look-alike character, `error->suggestion` holds the fixed postcode. + +```php +error?->code->value, "\n"; // bad_segment +echo $typo->error?->suggestion, "\n"; // EK-01-A03-FK-01 +``` + +Show the suggestion as a question, and let the user accept it. + +## Look up a postcode + +The client needs a PSR-18 HTTP client and a PSR-17 request factory, such as Guzzle 7. + +```sh +composer require guzzlehttp/guzzle +``` + +Keep the key on a server, and read it from the environment. PSR-18 has no timeout, so the client cannot stop a slow request. Set the timeout of your HTTP client. With no timeout on the HTTP client, a request can wait without limit. + +Pass the same value as `timeoutMs`. It does not set a limit. It tells the client how to read a failed attempt. An attempt that fails after `timeoutMs` or more gives `timeout`, and a faster failure gives `network_error`. One `timeoutMs` applies to every call. When you pass none, the client uses 8000 ms, and 15000 ms for `autocomplete`. + +```php + 15]), + new HttpFactory(), + apiKey: getenv('NIPOST_API_KEY') ?: null, + timeoutMs: 15000, +); + +try { + $result = $client->lookup('FC-01-Z99-ZZ-01'); + echo $result->valid ? 'valid' : 'not found', "\n"; // valid +} catch (PostcodeException $error) { + echo $error->errorCode()->value, "\n"; +} +``` + +An empty string for `apiKey` throws an `InvalidArgumentException`. With `null`, the client sends no key, and the gateway answers 401. + +The examples on this page use `FC-01-Z99-ZZ-01`, a synthetic postcode. They show the answers of Gatepost's mock gateway, which runs them in CI. The real gateway does not know this postcode. + +`lookup` checks the postcode with `Postcode::parse` first, and sends no request for a bad postcode. Pass `level: 2` for more data, when your key allows it. Each failed call throws a `PostcodeException`. Its `errorCode()` gives the same codes as the [TypeScript client](../typescript-client/#handle-errors). + +## Find a place and complete a postcode + +`reverse` finds the postcode at a latitude and a longitude. `autocomplete` names the segment that the user is typing, and returns the gateway's values for it. + +```php + 15]), + new HttpFactory(), + apiKey: getenv('NIPOST_API_KEY') ?: null, + timeoutMs: 15000, +); + +$place = $client->reverse(9.0, 7.0); +echo $place->unit?->postcode->canonical, "\n"; // FC-01-Z99-ZZ-01 + +$typing = $client->autocomplete('fc 01 z'); +echo $typing->segment->value, "\n"; // district +``` + +The client is synchronous. Each call waits for its result, and the client sends one request at a time. + +## Next steps + +- [Read the API reference of `gatepost/postcode`](../../reference/php/). +- [Read the postcode grammar](../../spec/grammar/) that every SDK follows. diff --git a/src/content/docs/guides/typescript-client.md b/src/content/docs/guides/typescript-client.md new file mode 100644 index 0000000..bc36e15 --- /dev/null +++ b/src/content/docs/guides/typescript-client.md @@ -0,0 +1,151 @@ +--- +title: Look up postcodes in TypeScript +description: Ask NIPOST's gateway about a postcode, a place or a typed code with @gatepost/client. +sidebar: + label: TypeScript client + order: 2 +--- + +`@gatepost/client` calls NIPOST's postcode gateway with your own API key. It checks each postcode offline first, so a bad code sends no request. It retries a failed request when a retry can help. + +## Install + +The first alpha is not published yet. This page will say when it is. + +```sh +pnpm add @gatepost/client +``` + +The package installs `@gatepost/core` too. Node 22 and current browsers have the `fetch` that the client uses. + +## Keep the key on a server + +The gateway refuses every call without a key. A key that starts with `nipost_test_` or `nipost_live_` is a secret. Keep it on a server, and read it from the environment. The client refuses a secret key in a web page. + +```ts +import { PostcodeClient } from '@gatepost/client'; + +const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); +const result = await client.lookup('fc 01 z99 zz 01'); +result.postcode.canonical; // 'FC-01-Z99-ZZ-01' +result.valid; // true +``` + +The examples on this page use `FC-01-Z99-ZZ-01`, a synthetic postcode. They show the answers of Gatepost's mock gateway, which runs them in CI. The real gateway does not know this postcode. + +## Look up a postcode + +A postcode that the gateway does not know is a result with `valid` set to false. It is not an error. + +```ts +import { PostcodeClient } from '@gatepost/client'; + +const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); +const unknown = await client.lookup('FC-01-Z99-ZZ-02'); +unknown.valid; // false +unknown.status; // 'not_found' +``` + +`lookup` asks for level 1 when you give no level. NIPOST's docs say that level 1 tells only whether a postcode exists. Level 2 adds the names of the places that hold it and a recent house address. Level 3 adds what the building is used for. Levels 2 and higher use credits, and your key must allow the level. + +```ts +import { PostcodeClient } from '@gatepost/client'; + +const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); +const result = await client.lookup('FC-01-Z99-ZZ-01', { level: 2 }); +result.levelReceived; // 2 +result.administrativeAddress?.lgaName; // 'SYNTHETIC LGA' +``` + +The gateway does not say which level it sent, so `levelReceived` comes from the fields of the response. + +## Handle errors + +Each call throws a `PostcodeError` when it cannot give a result. Its `code` tells you what to do. + +
    + +| Code | What to do | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `invalid_input` | Ask the user to correct the postcode. The client sends no request for a bad postcode. The code also covers a 4xx response that no other code names. | +| `unauthorized` | Check that the key is present and correct. | +| `forbidden` | Ask for a lower level, or get a key that allows the level. | +| `origin_not_allowed` | Add the address of the page to the key in NIPOST's dashboard. | +| `insufficient_credits` | Use level 1, or ask NIPOST about credits for the level. | +| `rate_limited` | Wait for `retryAfterMs` when it is not null, then try again. The client already retried a wait of 10 s or less. | +| `server_error` | Try again later. The client retries a 502, 503 or 504 only. Any other 5xx ends the call at once. | +| `network_error` | Check the network, then try again later. The client has already retried. | +| `timeout` | Try again, or raise `timeoutMs`. The client retries `lookup` and `reverse`, but not `autocomplete`. | +| `unexpected_response` | Report it. The gateway sent a body that the client cannot read. The client does not retry. | + +
    + +```ts +import { PostcodeClient, PostcodeError } from '@gatepost/client'; + +const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); +try { + await client.lookup('900108'); +} catch (error) { + if (error instanceof PostcodeError) { + error.code; // 'invalid_input' + } +} +``` + +Show the user your own message for each code. The message of the error is for developers. + +## Find the postcode of a place + +`reverse` takes a latitude and a longitude in degrees. The gateway looks for a unit within 25 m, or within the radius that you give, up to 250 m. + +```ts +import { PostcodeClient } from '@gatepost/client'; + +const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); +const place = await client.reverse(9, 7, { maxDistanceM: 50 }); +place.found; // true +place.unit?.postcode.canonical; // 'FC-01-Z99-ZZ-01' +``` + +`found` can be true while `unit` is null. The gateway then found an area, but no unit within the radius. Before you show a unit, check the accuracy of the GPS fix with `precisionForAccuracy` from `@gatepost/core`. + +## Complete a postcode while the user types + +`autocomplete` takes the text that a user typed. It names the segment that the user is typing, and returns the gateway's values for it. Cancel the last call at each keystroke, as the example does with `abort`. + +```ts +import { PostcodeClient } from '@gatepost/client'; + +const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); +const previous = new AbortController(); +const stale = client.autocomplete('fc 01', { signal: previous.signal }).catch(() => null); +previous.abort(); +const typing = await client.autocomplete('fc 01 z'); +typing.segment; // 'district' +typing.suggestions[0]?.postcode?.canonical; // 'FC-01-Z99' +await stale; // null +``` + +A cancelled call ends with the reason of the signal, not with a `PostcodeError`. + +## Choose the settings + +
    + +| Option | Meaning | Default | +| ------------ | ---------------------------------------- | ---------------------------------------- | +| `apiKey` | the key for the `X-API-Key` header | none | +| `baseUrl` | the address of the gateway | `https://api.postcode.gov.ng` | +| `timeoutMs` | the longest wait for one attempt | 8000 ms, and 15000 ms for `autocomplete` | +| `maxRetries` | the most retries after the first attempt | 2 | +| `cacheTtlMs` | how long the client keeps a result | 0, so it keeps none | + +
    + +The client sends at most 4 requests at a time, and identical calls share one request. It never logs, and none of its error messages holds your key. + +## Next steps + +- [Read the client contract](../../spec/client/) that every Gatepost client follows. +- [Read the API reference of `@gatepost/client`](../../reference/js/client/). diff --git a/src/content/docs/guides/typescript.md b/src/content/docs/guides/typescript.md new file mode 100644 index 0000000..180eae0 --- /dev/null +++ b/src/content/docs/guides/typescript.md @@ -0,0 +1,125 @@ +--- +title: Check postcodes in TypeScript +description: Read, clean and format Nigeria's digital postcodes with @gatepost/core, with no network call. +sidebar: + label: TypeScript + order: 1 +--- + +`@gatepost/core` reads the postcodes that users type or paste. It runs in Node 22 or later and in current browsers. It makes no network call, so it needs no API key. + +## Install + +The first alpha is not published yet. This page will say when it is. + +```sh +pnpm add @gatepost/core +``` + +## Read a postcode + +`parse` takes the text that a user typed, and returns a result. When `ok` is true, `value` holds the postcode in three forms. + +```ts +import { parse } from '@gatepost/core'; + +const result = parse('ek 01 a03 fk 01'); +if (result.ok) { + result.value.canonical; // 'EK-01-A03-FK-01' + result.value.display; // 'EK 01 A03 FK 01' + result.value.compact; // 'EK01A03FK01' +} +``` + +Store the compact or the canonical form, and show the display form to people. `parse` accepts spaces, hyphens, dashes and any letter case. It also accepts the full-width characters that some phone keyboards type. It never throws. + +## Tell the user what is wrong + +When `ok` is false, `error.code` names the first problem that `parse` found. + +
    + +| Code | Meaning | +| --------------- | -------------------------------------------------------- | +| `empty` | The text holds no letters or digits. | +| `legacy_code` | The text is an old 6-digit postcode. | +| `bad_character` | The text holds a character that no postcode has. | +| `bad_length` | The text has too many or too few characters. | +| `unknown_state` | The first two letters do not name a state. | +| `bad_segment` | One segment breaks its rule, for example a unit of `00`. | + +
    + +For `unknown_state` and `bad_segment`, `error.segment` names the segment. When a look-alike character causes the problem, `error.suggestion` holds the fixed postcode. + +```ts +import { parse } from '@gatepost/core'; + +const typo = parse('EK-O1-A03-FK-01'); +if (!typo.ok) { + typo.error.code; // 'bad_segment' + typo.error.segment; // 'lga' + typo.error.suggestion; // 'EK-01-A03-FK-01' +} +``` + +Show the suggestion as a question, and let the user accept it. `parse` never accepts the fixed code by itself. + +## Recognise an old postcode + +Many records still hold a legacy postcode of 6 digits. It names an area, not a building. `isLegacy` recognises one, so your form can ask for the new postcode. + +```ts +import { isLegacy } from '@gatepost/core'; + +isLegacy('900 108'); // true +isLegacy('EK-01-A03-FK-01'); // false +``` + +## Work with part of a postcode + +Set `allowPartial` to accept a postcode that stops after a segment. `precision` names the last segment that the postcode holds. `truncate`, `parent` and `contains` move between the segments. + +```ts +import { contains, parent, parse, truncate } from '@gatepost/core'; + +const building = parse('EK-01-A03-FK-01'); +const district = parse('EK 01 A03', { allowPartial: true }); +if (building.ok && district.ok) { + district.value.precision; // 'district' + truncate(building.value, 'area').canonical; // 'EK-01-A03-FK' + parent(building.value)?.canonical; // 'EK-01-A03-FK' + contains(district.value, building.value); // true +} +``` + +## Use a GPS fix + +A phone reports the accuracy of each GPS fix in metres. `precisionForAccuracy` gives the most precise segment that a fix of that accuracy supports. Do not show a unit for a fix that cannot tell two buildings apart. + +```ts +import { precisionForAccuracy } from '@gatepost/core'; + +precisionForAccuracy(6); // 'unit' +precisionForAccuracy(35); // 'district' +precisionForAccuracy(null); // 'lga' +``` + +## Keep postcodes out of logs + +A full postcode names one building. `redact` hides the unit, so a log shows the area and not the building. + +```ts +import { parse, redact } from '@gatepost/core'; + +const result = parse('EK-01-A03-FK-01'); +if (result.ok) { + redact(result.value); // 'EK-01-A03-FK-**' +} +``` + +## Next steps + +- [Look up a postcode with the gateway](../typescript-client/). +- [Try the parser in the playground](../../playground/). +- [Read the API reference of `@gatepost/core`](../../reference/js/core/). diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx new file mode 100644 index 0000000..da37196 --- /dev/null +++ b/src/content/docs/index.mdx @@ -0,0 +1,31 @@ +--- +title: Tools for Nigeria's digital postcode +description: Unofficial, open-source developer tools for Nigeria's digital postcode. +template: splash +hero: + title: Gatepost + tagline: Open-source tools that read, check and look up Nigeria's digital postcodes. Unofficial. Not made or endorsed by NIPOST. + actions: + - text: Choose a tool + link: start/ + icon: right-arrow + - text: Try the playground + link: playground/ + variant: minimal +--- + +On 1 October 2026, NIPOST launched a digital postcode for addressable buildings and locations in Nigeria. A postcode such as `EK-01-A03-FK-01` has five segments. From large to small, they are the state, the LGA, the district, the area and the unit. The unit is one building. + +Gatepost helps apps, websites and online stores accept these postcodes. + +- Check the form of a postcode offline, with no API key and no network call. +- Clean typed or pasted input: spaces, dashes, full-width characters and invisible characters. +- Suggest a fix for a common typo, such as the letter O where a zero belongs. +- Recognise an old 6-digit postcode. +- Ask NIPOST's gateway about a postcode with your own API key. + +Only NIPOST's gateway can say whether a building has a given postcode. Gatepost checks the form first, so your app calls the gateway less often and with clean input. + +Gatepost is independent. NIPOST did not make it and does not support it. For a problem with NIPOST's own app, data or API, contact NIPOST through its official website. + +The [readiness tracker](readiness/) lists the common tools that still reject the new postcode, and the state of the fix for each one. The [privacy page](privacy/) says what this site stores. diff --git a/src/content/docs/playground.mdx b/src/content/docs/playground.mdx new file mode 100644 index 0000000..51b6ea5 --- /dev/null +++ b/src/content/docs/playground.mdx @@ -0,0 +1,19 @@ +--- +title: Playground +description: Try the postcode parser of @gatepost/core in your browser, with no network call. +--- + +import ClientSamples from '../../components/ClientSamples.astro'; +import Playground from '../../components/Playground.astro'; + +The playground runs `@gatepost/core` in your browser. It checks the form of the text as you type, and it shows the forms and the segments of a postcode. It sends nothing to any server, and it keeps nothing. + + + +The playground checks the form only. Only NIPOST's gateway can say whether a building has a postcode. The [TypeScript guide](../guides/typescript/) shows the same functions in code. + +## What the client returns + +`@gatepost/client` needs a key and a network, so the playground does not run it. These are the answers that the client gave to Gatepost's mock gateway when this site was built. The answers use synthetic postcodes with the district `Z99`. The [API reference](../reference/js/client/) explains each field. + + diff --git a/src/content/docs/privacy.md b/src/content/docs/privacy.md new file mode 100644 index 0000000..e4616c8 --- /dev/null +++ b/src/content/docs/privacy.md @@ -0,0 +1,29 @@ +--- +title: Privacy +description: What this site keeps, what it sends and what GitHub logs. +--- + +This site has no analytics, no tracking and no cookies. Every script, font, image and style comes from this site itself. No page loads anything from another website. + +## What your browser keeps + +Your browser keeps two settings of this site, and the site never sends them anywhere. + +- Your choice of a light or a dark theme, in local storage under the name `starlight-theme`. +- Which groups of the side menu you opened, and how far the menu has scrolled, in session storage under the name `sl-sidebar-state`. The browser deletes it when you close the tab. + +## Search + +The search box runs in your browser. It reads an index that this site built when it was published. Your search text stays in your browser. + +## Playground + +The playground runs in your browser. It sends what you type to no server, and it keeps none of it. The address bar never holds what you type. + +## Hosting + +GitHub hosts this site on GitHub Pages. [GitHub logs the IP address of each visitor](https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages#data-collection) for security. The Gatepost team does not see these logs. + +## Fonts + +The site sets its text in Overpass, a font under the [SIL Open Font License 1.1](https://openfontlicense.org). The font files come from this site, not from another site. diff --git a/src/content/docs/readiness.md b/src/content/docs/readiness.md new file mode 100644 index 0000000..115d2cf --- /dev/null +++ b/src/content/docs/readiness.md @@ -0,0 +1,22 @@ +--- +title: Readiness tracker +description: Common tools that reject or hide Nigeria's new postcode, and the state of the fix for each one. +--- + +On 1 October 2026, NIPOST launched a postcode of 11 letters and digits, as [its docs](https://docs.postcode.gov.ng) describe. Some common tools still accept only the old 6-digit postcode, or hide the postcode field for Nigeria. Gatepost has sent a fix to three of the five tools below and has not sent a fix to the other two. This page shows the state on 3 October 2026. + +
    + +| Tool | What fails | Fix | Status | +| ------------------------------------------------------------------------- | ------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------- | +| [postcode-validator](https://github.com/melwynfurtado/postcode-validator) | The rule for NG accepts only 6 digits. | pull request | [sent, open](https://github.com/melwynfurtado/postcode-validator/pull/302) | +| [faker-js](https://github.com/faker-js/faker) | The `en_NG` locale makes 4-digit and 5-digit postcodes. | pull request | [sent, open, approved by one maintainer](https://github.com/faker-js/faker/pull/4124) | +| [Shopify worldwide](https://github.com/Shopify/worldwide) | The rule for NG accepts only 6 digits. | pull request | [sent, open](https://github.com/Shopify/worldwide/pull/622) | +| [WooCommerce](https://github.com/woocommerce/woocommerce) | The checkout hides the postcode field for NG. | pull request | not sent | +| [Google libaddressinput](https://github.com/google/libaddressinput) | The rule for NG accepts only 6 digits. | issue | not sent | + +
    + +The fixes for postcode-validator and Shopify worldwide accept both the old 6-digit postcode and the new 11-character postcode. They reject `00` in the LGA and the unit, as NIPOST's format does. The fix for faker-js makes postcodes in the new form only, and it never makes `00` in the LGA or the unit. + +When a fix is sent, the row links the pull request or the issue. diff --git a/src/content/docs/start.md b/src/content/docs/start.md new file mode 100644 index 0000000..56cb7dc --- /dev/null +++ b/src/content/docs/start.md @@ -0,0 +1,31 @@ +--- +title: Choose a tool +description: Find the Gatepost package that fits your app, and learn what each one needs. +--- + +Each Gatepost tool follows the same spec, so each one reads a postcode in the same way. Choose by the language of your app and by what it must know. + +
    + +| You want to | Use | Needs a key | +| -------------------------------------------------------- | -------------------------------------------------- | ---------------- | +| Check the form of a postcode in TypeScript or JavaScript | [`@gatepost/core`](../guides/typescript/) | no | +| Look up a postcode from a TypeScript server | [`@gatepost/client`](../guides/typescript-client/) | yes | +| Check or look up a postcode in PHP | [`gatepost/postcode`](../guides/php/) | only for lookups | + +
    + +## What a check of the form tells you + +The core packages read the text that a user typed. They tell you whether the text has the form of a postcode, and what is wrong when it does not. They work offline, so they cost nothing and send nothing. + +A postcode with the right form can still be wrong. Only NIPOST's gateway knows which postcodes exist. Check the form first, and then ask the gateway when your app needs to know more. + +## Get a key + +The gateway refuses every call without a key. Register on [NIPOST's dashboard](https://dashboard.postcode.gov.ng/register) to get one. NIPOST gives two kinds of key. + +- A secret key starts with `nipost_test_` or `nipost_live_`. Keep it on a server. +- A publishable key starts with `nipost_pk_test_` or `nipost_pk_live_`. A web page can hold it, but it works only from the addresses that you allow in the dashboard. + +Test keys work only on NIPOST's staging gateway. NIPOST's docs do not give the address of the staging gateway. Ask NIPOST for it, and pass it as `baseUrl`. NIPOST's docs explain the levels, the credits and the limits: [docs.postcode.gov.ng](https://docs.postcode.gov.ng). diff --git a/src/messages/en-GB.json b/src/messages/en-GB.json new file mode 100644 index 0000000..e95bb7f --- /dev/null +++ b/src/messages/en-GB.json @@ -0,0 +1,42 @@ +{ + "playground.label": "Postcode", + "playground.hint": "Type or paste a postcode, such as ek 01 a03 fk 01. Nothing you type leaves this page.", + "playground.partial": "Accept a partial postcode", + "playground.idle": "The playground checks the form of the text as you type.", + "playground.segments": "Segments", + "playground.details": "Forms of the postcode", + "playground.useSuggestion": "Use the suggestion", + "segment.state": "State", + "segment.lga": "LGA", + "segment.district": "District", + "segment.area": "Area", + "segment.unit": "Unit", + "segment.problem": "Problem", + "form.canonical": "Canonical", + "form.display": "Display", + "form.compact": "Compact", + "form.redacted": "For logs", + "form.stateName": "State name", + "form.parent": "One segment up", + "result.unit": "The text has the form of a postcode. Only NIPOST's gateway can say whether a building has it.", + "result.partial": "The text has the form of a partial postcode.", + "result.suggestion": "Did you mean this postcode?", + "error.empty": "Type a postcode.", + "error.legacy_code": "This is an old 6-digit postcode. Ask for the new postcode of 11 letters and digits.", + "error.bad_character": "A postcode holds only letters and digits.", + "error.bad_length": "A postcode has 11 letters and digits.", + "error.bad_length.partial": "A partial postcode has 2, 4, 7 or 9 letters and digits.", + "error.unknown_state": "The first two letters do not name a state.", + "error.bad_segment.lga": "The LGA is two digits from 01 to 99.", + "error.bad_segment.area": "The area is two letters.", + "error.bad_segment.unit": "The unit is two digits from 01 to 99.", + "accuracy.label": "GPS accuracy in metres", + "accuracy.hint": "A phone reports the accuracy of each GPS fix.", + "accuracy.invalid": "An accuracy is a number of metres, zero or more.", + "accuracy.lga": "A fix this rough can show the postcode up to the LGA.", + "accuracy.district": "A fix of this accuracy can show the postcode up to the district.", + "accuracy.area": "A fix of this accuracy can show the postcode up to the area.", + "accuracy.unit": "A fix of this accuracy can show the whole postcode, with the unit.", + "playground.noScript": "The playground needs JavaScript. The guides show the same results.", + "accuracy.heading": "Match a GPS fix" +} diff --git a/src/playground/describe.ts b/src/playground/describe.ts new file mode 100644 index 0000000..1100f5b --- /dev/null +++ b/src/playground/describe.ts @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// Turns the text of the playground into what the page shows. It calls only @gatepost/core, so +// it needs no network, and each string comes from the message catalogue (UI-2). +import { + parent, + parse, + precisionForAccuracy, + redact, + stateName, + type ParseError, + type Postcode, + type Precision, +} from '@gatepost/core'; +import messages from '../messages/en-GB.json'; + +/** The name of a string in the message catalogue. */ +export type MessageKey = keyof typeof messages; + +/** One segment box of the playground. */ +export interface SegmentView { + readonly name: Precision; + readonly label: string; + readonly text: string; + readonly problem: boolean; +} + +/** What the playground shows for the text in its field. */ +export interface PostcodeView { + readonly status: 'idle' | 'valid' | 'invalid'; + readonly message: string; + readonly segments: readonly SegmentView[]; + readonly forms: readonly { readonly label: string; readonly value: string }[]; + readonly suggestion: string | null; +} + +const SEGMENT_NAMES: readonly Precision[] = ['state', 'lga', 'district', 'area', 'unit']; + +/** + * Reads a string from the message catalogue. + * + * @param key - The name of the string. + * @returns The string. + */ +export function message(key: MessageKey): string { + return messages[key]; +} + +function isMessageKey(key: string): key is MessageKey { + return Object.hasOwn(messages, key); +} + +/** + * Names the message for a parse error. A segment error has one message for each segment, so + * no sentence is built from parts. + * + * @param error - The error that `parse` returned. + * @param allowPartial - Whether the playground accepts a partial postcode. + * @returns The name of the message. + */ +export function errorMessageKey(error: ParseError, allowPartial: boolean): MessageKey { + const candidates = [ + allowPartial ? `error.${error.code}.partial` : '', + `error.${error.code}.${error.segment ?? ''}`, + `error.${error.code}`, + ]; + const key = candidates.find(isMessageKey); + if (key === undefined) throw new Error(`The catalogue has no message for ${error.code}.`); + return key; +} + +function segmentViews(postcode: Postcode | null, problem: Precision | null): SegmentView[] { + return SEGMENT_NAMES.map((name) => ({ + name, + label: message(`segment.${name}`), + text: postcode?.segments[name] ?? '', + problem: name === problem, + })); +} + +function formsOf(postcode: Postcode): PostcodeView['forms'] { + const forms = [ + { label: message('form.canonical'), value: postcode.canonical }, + { label: message('form.display'), value: postcode.display }, + { label: message('form.compact'), value: postcode.compact }, + { label: message('form.stateName'), value: stateName(postcode.segments.state) ?? '' }, + ]; + const up = parent(postcode); + if (up !== null) forms.push({ label: message('form.parent'), value: up.canonical }); + if (postcode.precision === 'unit') { + forms.push({ label: message('form.redacted'), value: redact(postcode) }); + } + return forms; +} + +/** + * Describes the text of the playground field. + * + * @param input - The text that the reader typed. + * @param allowPartial - Whether to accept a partial postcode. + * @returns What the page shows. + */ +export function describePostcode(input: string, allowPartial: boolean): PostcodeView { + const result = parse(input, { allowPartial }); + if (result.ok) { + const key = result.value.precision === 'unit' ? 'result.unit' : 'result.partial'; + return { + status: 'valid', + message: message(key), + segments: segmentViews(result.value, null), + forms: formsOf(result.value), + suggestion: null, + }; + } + const { error } = result; + return { + status: error.code === 'empty' ? 'idle' : 'invalid', + message: message( + error.code === 'empty' ? 'playground.idle' : errorMessageKey(error, allowPartial), + ), + segments: segmentViews(null, error.segment), + forms: [], + suggestion: error.suggestion, + }; +} + +/** + * Describes what a GPS fix of a given accuracy can show. + * + * @param text - The accuracy in metres, as the reader typed it. Empty text gives null. + * @returns The message, or null when the field is empty. A negative or invalid number gives the + * message for an invalid accuracy. + */ +export function describeAccuracy(text: string): string | null { + if (text.trim() === '') return null; + const metres = Number(text); + if (!Number.isFinite(metres) || metres < 0) return message('accuracy.invalid'); + const key = `accuracy.${precisionForAccuracy(metres)}`; + if (!isMessageKey(key)) throw new Error(`The catalogue has no message ${key}.`); + return message(key); +} diff --git a/src/playground/view.ts b/src/playground/view.ts new file mode 100644 index 0000000..ade9c50 --- /dev/null +++ b/src/playground/view.ts @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +// Connects the playground's markup to describePostcode. It writes text only, never HTML, and it +// keeps nothing: no request, no storage and no change of the address bar. +import { describeAccuracy, describePostcode, message, type PostcodeView } from './describe.ts'; + +// A screen reader hears the status after the typing pauses, not at each keystroke. +const STATUS_DELAY_MS = 600; + +function part(root: HTMLElement, name: string): HTMLElement { + const element = root.querySelector(`[data-part="${name}"]`); + if (element === null) throw new Error(`The playground has no part named ${name}.`); + return element; +} + +function inputPart(root: HTMLElement, name: string): HTMLInputElement { + const element = part(root, name); + if (!(element instanceof HTMLInputElement)) throw new Error(`${name} is not an input.`); + return element; +} + +function renderSegments(list: HTMLElement, view: PostcodeView): void { + list.replaceChildren( + ...view.segments.map((segment) => { + const item = document.createElement('li'); + item.dataset['problem'] = String(segment.problem); + const label = document.createElement('span'); + label.textContent = segment.label; + const text = document.createElement('code'); + text.textContent = segment.text; + item.append(label, text); + if (segment.problem) { + const problem = document.createElement('strong'); + problem.textContent = message('segment.problem'); + item.append(problem); + } + return item; + }), + ); +} + +function renderForms(list: HTMLElement, view: PostcodeView): void { + list.replaceChildren( + ...view.forms.flatMap(({ label, value }) => { + const term = document.createElement('dt'); + term.textContent = label; + const detail = document.createElement('dd'); + const code = document.createElement('code'); + code.textContent = value; + detail.append(code); + return [term, detail]; + }), + ); +} + +/** + * Starts the playground in its markup. + * + * @param root - The element that holds the parts of the playground. + */ +export function startPlayground(root: HTMLElement): void { + const input = inputPart(root, 'postcode'); + const partial = inputPart(root, 'partial'); + const status = part(root, 'status'); + const suggestion = part(root, 'suggestion'); + const suggested = part(root, 'suggested'); + const accuracy = inputPart(root, 'accuracy'); + const accuracyStatus = part(root, 'accuracy-status'); + let timer: ReturnType | undefined; + let accuracyTimer: ReturnType | undefined; + + const render = (): void => { + const view = describePostcode(input.value, partial.checked); + root.dataset['status'] = view.status; + renderSegments(part(root, 'segments'), view); + renderForms(part(root, 'forms'), view); + suggestion.hidden = view.suggestion === null; + suggested.textContent = view.suggestion ?? ''; + clearTimeout(timer); + timer = setTimeout(() => { + status.textContent = view.message; + }, STATUS_DELAY_MS); + }; + + input.addEventListener('input', render); + partial.addEventListener('change', render); + part(root, 'use-suggestion').addEventListener('click', () => { + input.value = suggested.textContent; + render(); + input.focus(); + }); + accuracy.addEventListener('input', () => { + const text = describeAccuracy(accuracy.value) ?? ''; + clearTimeout(accuracyTimer); + accuracyTimer = setTimeout(() => { + accuracyStatus.textContent = text; + }, STATUS_DELAY_MS); + }); + render(); +} diff --git a/src/release-state.json b/src/release-state.json new file mode 100644 index 0000000..7d24069 --- /dev/null +++ b/src/release-state.json @@ -0,0 +1,3 @@ +{ + "state": "not published" +} diff --git a/src/styles/theme.css b/src/styles/theme.css new file mode 100644 index 0000000..56d9dde --- /dev/null +++ b/src/styles/theme.css @@ -0,0 +1,76 @@ +/* SPDX-FileCopyrightText: 2026 The Gatepost authors */ +/* SPDX-License-Identifier: Apache-2.0 */ +/* + * The theme tokens of the site (UI-3). The colours come from the brand: near-black #0E1513, white + * and teal #0B8574. Teal text needs a lighter tone on the dark theme and a darker one on the + * light theme to reach 4.5:1, so each theme sets its own text accent. + * test/browser/accessibility.spec.ts checks the contrast of each pair that the site uses. + */ +:root { + --sl-font: 'Overpass'; + --sl-color-white: #ffffff; + --sl-color-gray-1: #e6ebe9; + --sl-color-gray-2: #c5cecb; + --sl-color-gray-3: #97a29f; + --sl-color-gray-4: #5f6a67; + --sl-color-gray-5: #3a4341; + --sl-color-gray-6: #1d2523; + --sl-color-black: #0e1513; + --sl-color-accent-low: #0c3b34; + --sl-color-accent: #0b8574; + --sl-color-accent-high: #8fdfd1; + --sl-color-text-accent: var(--sl-color-accent-high); +} + +:root[data-theme='light'] { + --sl-color-white: #0e1513; + --sl-color-gray-1: #1d2523; + --sl-color-gray-2: #2e3735; + --sl-color-gray-3: #4b5653; + --sl-color-gray-4: #7a8582; + --sl-color-gray-5: #c0c8c5; + --sl-color-gray-6: #e7ecea; + --sl-color-gray-7: #f3f6f5; + --sl-color-black: #ffffff; + --sl-color-accent-low: #c9eee7; + --sl-color-accent: #0b8574; + --sl-color-accent-high: #075a4f; + --sl-color-text-accent: var(--sl-color-accent-high); +} + +/* + * A table scrolls inside its own box, which takes the focus. The `wrapTables` function of + * `scripts/spec-pages.ts` and the `div` tags in the pages make the box. The table itself must + * not scroll, or the box would have nothing to scroll. + */ +.table-scroll { + overflow-x: auto; +} + +.table-scroll:focus-visible { + outline: 2px solid var(--sl-color-accent); + outline-offset: 2px; +} + +.sl-markdown-content .table-scroll > table { + display: table; + overflow: visible; +} + +h1, +h2, +h3, +h4 { + font-weight: 600; + letter-spacing: -0.01em; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0s !important; + animation-duration: 0s !important; + } +} diff --git a/test/browser/accessibility.spec.ts b/test/browser/accessibility.spec.ts new file mode 100644 index 0000000..94f0d46 --- /dev/null +++ b/test/browser/accessibility.spec.ts @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import AxeBuilder from '@axe-core/playwright'; +import { expect, test } from '@playwright/test'; +import { sitePaths } from './site-pages.ts'; + +// The tags of the WCAG 2.2 AA success criteria that axe can test (UI-1). +const WCAG_22_AA = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']; + +for (const path of [...sitePaths(), '/docs/404.html']) { + test(`${path} has no WCAG 2.2 AA violation that axe finds`, async ({ page }) => { + await page.goto(path); + const results = await new AxeBuilder({ page }).withTags(WCAG_22_AA).analyze(); + expect( + results.violations.map(({ id, nodes }) => `${id}: ${nodes[0]?.target.join(' ') ?? ''}`), + ).toEqual([]); + }); +} + +test('the first Tab moves the focus to the skip link', async ({ page }) => { + await page.goto('/docs/privacy/'); + await page.keyboard.press('Tab'); + await expect(page.locator(':focus')).toHaveText('Skip to content'); +}); + +test('a page fits a screen 320 pixels wide with no sideways scroll', async ({ page }) => { + await page.setViewportSize({ width: 320, height: 640 }); + for (const path of sitePaths()) { + await page.goto(path); + const width = await page.evaluate(() => document.documentElement.scrollWidth); + expect(width, path).toBeLessThanOrEqual(320); + } +}); + +// A wide table scrolls inside its own box on a narrow screen. The box needs a focus stop, or a +// keyboard user cannot scroll it (WCAG 2.1.1). The desktop run above never sees this. +for (const path of [...sitePaths(), '/docs/404.html']) { + test(`${path} has no violation that axe finds at 320 pixels`, async ({ page }) => { + await page.setViewportSize({ width: 320, height: 640 }); + await page.goto(path); + const results = await new AxeBuilder({ page }).withTags(WCAG_22_AA).analyze(); + expect( + results.violations.map(({ id, nodes }) => `${id}: ${nodes[0]?.target.join(' ') ?? ''}`), + ).toEqual([]); + }); +} diff --git a/test/browser/playground.spec.ts b/test/browser/playground.spec.ts new file mode 100644 index 0000000..5482ab7 --- /dev/null +++ b/test/browser/playground.spec.ts @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import AxeBuilder from '@axe-core/playwright'; +import { expect, test } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('/docs/playground/'); +}); + +test('shows the forms of a typed postcode', async ({ page }) => { + await page.getByLabel('Postcode', { exact: true }).fill('ek 01 a03 fk 01'); + const forms = page.getByRole('definition'); + await expect(forms).toHaveText([ + 'EK-01-A03-FK-01', + 'EK 01 A03 FK 01', + 'EK01A03FK01', + 'Ekiti', + 'EK-01-A03-FK', + 'EK-01-A03-FK-**', + ]); + await expect(page.getByRole('status').first()).toHaveText( + "The text has the form of a postcode. Only NIPOST's gateway can say whether a building has it.", + ); +}); + +test('works with the keyboard alone, from the field to the suggestion', async ({ page }) => { + const field = page.getByLabel('Postcode', { exact: true }); + await field.focus(); + await page.keyboard.type('EK-O1-A03-FK-01'); + await expect(page.getByRole('listitem').filter({ hasText: 'Problem' })).toHaveText(/LGA/); + await page.keyboard.press('Tab'); + await expect(page.getByRole('checkbox', { name: 'Accept a partial postcode' })).toBeFocused(); + await page.keyboard.press('Tab'); + await expect(page.getByRole('button', { name: 'Use the suggestion' })).toBeFocused(); + await page.keyboard.press('Enter'); + await expect(field).toHaveValue('EK-01-A03-FK-01'); + await expect(field).toBeFocused(); +}); + +// The site may load its own scripts later, such as the search code, but no request may leave the +// site or carry a query. +test('sends nothing and keeps the typed text out of the address bar', async ({ page, baseURL }) => { + const requests: URL[] = []; + page.on('request', (request) => requests.push(new URL(request.url()))); + const field = page.getByLabel('Postcode', { exact: true }); + await field.fill('EK-01-A03-FK-01'); + await field.press('Enter'); + await page.getByLabel('GPS accuracy in metres').fill('6'); + await expect(page.getByRole('status').last()).toHaveText( + 'A fix of this accuracy can show the whole postcode, with the unit.', + ); + expect(requests.filter((url) => url.origin !== baseURL || url.search !== '')).toEqual([]); + expect(new URL(page.url()).search).toBe(''); + const stored = await page.evaluate(() => [ + ...Object.keys(localStorage), + ...Object.keys(sessionStorage), + document.cookie, + ]); + // Only the settings of the site itself, which the privacy page names, may be there. + expect( + stored.filter((key) => !['starlight-theme', 'sl-sidebar-state', ''].includes(key)), + ).toEqual([]); +}); + +test('has no WCAG 2.2 AA violation while it shows a suggestion', async ({ page }) => { + await page.getByLabel('Postcode', { exact: true }).fill('EK-O1-A03-FK-01'); + await expect(page.getByRole('button', { name: 'Use the suggestion' })).toBeVisible(); + const results = await new AxeBuilder({ page }) + .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']) + .analyze(); + expect(results.violations.map(({ id }) => id)).toEqual([]); +}); + +test('announces the result after the typing pauses, not at each keystroke', async ({ page }) => { + const status = page.getByRole('status').first(); + const idle = await status.textContent(); + await page.getByLabel('Postcode', { exact: true }).fill('EK-01-A03-FK-01'); + await expect(status).toHaveText(idle ?? ''); + await expect(status).toHaveText(/form of a postcode/); +}); + +test('fits a screen 320 pixels wide while it shows a suggestion and a long value', async ({ + page, +}) => { + await page.setViewportSize({ width: 320, height: 640 }); + await page.getByLabel('Postcode', { exact: true }).fill('EK-O1-A03-FK-01'); + await expect(page.getByRole('button', { name: 'Use the suggestion' })).toBeVisible(); + const results = await new AxeBuilder({ page }) + .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']) + .analyze(); + expect(results.violations.map(({ id }) => id)).toEqual([]); + await page.getByLabel('Postcode', { exact: true }).fill('ek 01 a03 fk 01'); + await expect(page.getByRole('definition').first()).toBeVisible(); + const width = await page.evaluate(() => document.documentElement.scrollWidth); + expect(width).toBeLessThanOrEqual(320); +}); diff --git a/test/browser/privacy.spec.ts b/test/browser/privacy.spec.ts new file mode 100644 index 0000000..2253996 --- /dev/null +++ b/test/browser/privacy.spec.ts @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { expect, test } from '@playwright/test'; +import { sitePaths } from './site-pages.ts'; + +// The privacy page names each key that the site keeps in the browser, and no other. +const LOCAL_KEYS = ['starlight-theme']; +const SESSION_KEYS = ['sl-sidebar-state']; + +test('no page asks another site for anything', async ({ page, baseURL }) => { + const origins = new Set(); + page.on('request', (request) => origins.add(new URL(request.url()).origin)); + for (const path of sitePaths()) { + await page.goto(path); + } + expect([...origins]).toEqual([baseURL]); +}); + +test('a search sends the text nowhere and keeps no cookie', async ({ page, baseURL }) => { + const origins = new Set(); + page.on('request', (request) => origins.add(new URL(request.url()).origin)); + await page.goto('/docs/privacy/'); + await page.getByRole('button', { name: 'Search' }).click(); + const search = page.getByRole('dialog', { name: 'Search' }); + await search.getByRole('textbox', { name: 'Search' }).fill('postcode'); + // Pagefind loads cold under parallel test load, so the default 5 s can run out. + await expect(search.getByRole('link').first()).toBeVisible({ timeout: 15_000 }); + expect([...origins]).toEqual([baseURL]); + expect(await page.context().cookies()).toEqual([]); +}); + +// Every page, the 404 page included, because a script on any one of them could store a key. +for (const path of [...sitePaths(), '/docs/no-such-page/']) { + test(`${path} keeps only the keys that the privacy page names`, async ({ page }) => { + await page.goto('/docs/privacy/'); + await page.getByRole('combobox', { name: 'Select theme' }).first().selectOption('light'); + await page.goto(path); + const stored = await page.evaluate(() => ({ + local: Object.keys(localStorage), + session: Object.keys(sessionStorage), + })); + expect(stored.local).toContain('starlight-theme'); + expect(stored.local.filter((key) => !LOCAL_KEYS.includes(key))).toEqual([]); + expect(stored.session.filter((key) => !SESSION_KEYS.includes(key))).toEqual([]); + }); +} + +test('the privacy page names each key', async ({ page }) => { + await page.goto('/docs/privacy/'); + for (const key of [...LOCAL_KEYS, ...SESSION_KEYS]) { + await expect(page.getByRole('main')).toContainText(key); + } +}); diff --git a/test/browser/site-pages.ts b/test/browser/site-pages.ts new file mode 100644 index 0000000..9d23a4b --- /dev/null +++ b/test/browser/site-pages.ts @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { readFileSync } from 'node:fs'; + +/** + * Lists the path of each page in the sitemap of the built site, such as `/docs/start/`. + * + * @returns The paths, in the order of the sitemap. + */ +export function sitePaths(): readonly string[] { + const sitemap = readFileSync(new URL('../../dist/sitemap-0.xml', import.meta.url), 'utf8'); + return Array.from( + sitemap.matchAll(/([^<]+)<\/loc>/g), + ([, url]) => new URL(url ?? '').pathname, + ); +} diff --git a/test/site/api-report.ts b/test/site/api-report.ts new file mode 100644 index 0000000..8ce663e --- /dev/null +++ b/test/site/api-report.ts @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 + +/** One public symbol of an API report, with the folder that TypeDoc gives its page. */ +export interface ReportedExport { + readonly folder: string; + readonly symbol: string; +} + +// The folder of each kind of export, as the TypeDoc pages name it. +const FOLDERS: Readonly> = { + function: 'functions', + interface: 'interfaces', + type: 'type-aliases', + const: 'variables', + class: 'classes', + 'abstract class': 'classes', + enum: 'enums', + 'const enum': 'enums', + namespace: 'namespaces', +}; + +const KINDS = Object.keys(FOLDERS) + .sort((a, b) => b.length - a.length) + .join('|'); +const EXPORT_LINE = new RegExp(`^export (?:declare )?(${KINDS}) (\\w+)`); + +/** + * Lists the public symbols of an API report. Every line that starts with `export` must be one + * of the known kinds, so a new kind of export fails here instead of passing without a page. + * + * @param report - The text of the `.api.md` file that API Extractor writes. + * @returns The symbols, in the order of the report. + * @throws When the report holds no export, or a line starts with `export` and is no known kind. + */ +export function exportsOf(report: string): readonly ReportedExport[] { + const lines = report.split('\n').filter((line) => line.startsWith('export ')); + const found: ReportedExport[] = []; + const unknown: string[] = []; + for (const line of lines) { + const [, kind, symbol] = EXPORT_LINE.exec(line) ?? []; + if (kind === undefined || symbol === undefined) unknown.push(line); + else found.push({ folder: FOLDERS[kind] ?? '', symbol }); + } + if (unknown.length > 0) { + throw new Error(`The test does not know these exports: ${unknown.join(' | ')}`); + } + if (found.length === 0) throw new Error('The API report holds no export.'); + return found; +} diff --git a/test/site/built-site.ts b/test/site/built-site.ts new file mode 100644 index 0000000..cd6bacc --- /dev/null +++ b/test/site/built-site.ts @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { existsSync, readdirSync, readFileSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +/** The folder that `astro build` writes. */ +export const DIST = fileURLToPath(new URL('../../dist/', import.meta.url)); + +/** A file of the built site, by its path inside dist/. */ +export interface BuiltFile { + readonly path: string; + readonly text: string; +} + +function filesUnder(folder: string): readonly string[] { + return readdirSync(folder, { withFileTypes: true }).flatMap((entry) => { + const path = join(folder, entry.name); + return entry.isDirectory() ? filesUnder(path) : [path]; + }); +} + +/** + * Reads each built file whose name ends with the suffix. + * + * @param suffix - The end of the file name, such as `.html` or `.css`. + * @returns The files, in the order of their paths. + */ +export function builtFiles(suffix: string): readonly BuiltFile[] { + if (!existsSync(DIST)) { + throw new Error('dist/ is missing. Run pnpm build before the site tests.'); + } + return filesUnder(DIST) + .filter((path) => path.endsWith(suffix)) + .sort() + .map((path) => ({ path: relative(DIST, path), text: readFileSync(path, 'utf8') })); +} diff --git a/test/site/external-urls.ts b/test/site/external-urls.ts new file mode 100644 index 0000000..5896ee8 --- /dev/null +++ b/test/site/external-urls.ts @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 + +// Each attribute that makes the browser fetch something when the page loads. An only +// fetches when the reader follows it, so it may point at another site. So may a that +// names the page itself or the sitemap, because the browser does not fetch those. +const FETCHING_TAG = /<(script|link|img|source|iframe|video|audio|embed|object|track)\b[^>]*>/gi; +const NAMING_LINK = /^]*\srel="(?:canonical|alternate|sitemap)"/i; +const FETCHING_ATTRIBUTE = /\s(?:src|href|srcset|poster|data)="([^"]*)"/gi; +const STYLE_BODY = /]*>([\s\S]*?)<\/style>/gi; +const STYLE_ATTRIBUTE = /\sstyle="([^"]*)"/gi; +const SCRIPT_BODY = /]*>([\s\S]*?)<\/script>/gi; + +// An XML namespace names a kind of document. The browser never fetches it. +const NAMESPACE = /^https?:\/\/www\.w3\.org\//; + +// Pagefind builds a URL against a dummy host to parse a path, and it never fetches the host. Its +// translation files also credit each translator as `Name `, which is text. +const DUMMY_BASE = /^https:\/\/(?:p|example\.com(?:\$\{.*)?)$/; +const CREDIT_LINK = />$/; + +// A URL that the site serves itself: a path, a relative URL, a fragment or inline data. +export function isOwnUrl(url: string): boolean { + return !/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(url) || url.startsWith('data:'); +} + +function matches(text: string, pattern: RegExp): readonly string[] { + return Array.from(text.matchAll(pattern), ([, group]) => group ?? ''); +} + +/** + * Lists the URLs in a style sheet that point at another site. + * + * @param css - The text of a style sheet, a `
    ', + '', + ].join(''), + ); + expect(foreignPageUrls(html)).toEqual([]); + }); + + it('reports an @import in an inline style element', () => { + const html = page(''); + expect(foreignPageUrls(html)).toEqual(['https://fonts.googleapis.com/css']); + }); + + it('reports a url() in a style attribute, also when the quotes are escaped', () => { + const plain = page('
    '); + const escaped = page('
    '); + expect(foreignPageUrls(plain)).toEqual(['https://example.com/x.png']); + expect(foreignPageUrls(escaped)).toEqual(['//example.com/y.png']); + }); + + it('reports a host in an inline script', () => { + const html = page(''); + expect(foreignPageUrls(html)).toEqual(['https://api.postcode.gov.ng/x']); + }); + + it('still reports a script file, an image and a srcset on another site', () => { + const html = page( + '', + ); + expect(foreignPageUrls(html)).toEqual(['//cdn.example.com/a.js', 'https://x.example/b.png']); + }); +}); + +describe('foreignScriptUrls', () => { + it('reports an absolute and a protocol-relative URL in a script file', () => { + expect(foreignScriptUrls('fetch(`https://a.example/x`);import("//b.example/y.js")')).toEqual([ + 'https://a.example/x', + '//b.example/y.js', + ]); + }); + + it('ignores a comment slash pair and a regular expression', () => { + expect(foreignScriptUrls('const a = 1; // note\nconst b = /a\\/\\/b/;')).toEqual([]); + }); +}); + +describe('foreignCssUrls', () => { + it('reports url() and @import to another site, and accepts own and inline data', () => { + const css = "@import 'https://a.example/a.css';a{background:url(data:image/png;base64,AA)}"; + expect(foreignCssUrls(css)).toEqual(['https://a.example/a.css']); + expect(foreignCssUrls('a{background:url(/docs/a.png)}')).toEqual([]); + }); +}); diff --git a/test/unit/network-guard.test.ts b/test/unit/network-guard.test.ts new file mode 100644 index 0000000..9dec301 --- /dev/null +++ b/test/unit/network-guard.test.ts @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { describe, expect, it } from 'vitest'; + +describe('the fetch of the unit tests', () => { + it.each([ + // The .invalid names never resolve, so a request that escapes the guard still reaches no host. + 'https://gateway.example.invalid/v1/lookup', + 'http://example.invalid/', + new URL('https://platform.example.invalid/'), + ])('refuses a request to a host other than this machine: %s', async (target) => { + await expect(fetch(target)).rejects.toThrow(/other than this machine/); + }); + + it('refuses a request that a Request object holds', async () => { + await expect(fetch(new Request('https://gateway.example.invalid/'))).rejects.toThrow( + /other than this machine/, + ); + }); +}); diff --git a/test/unit/pages-workflow.test.ts b/test/unit/pages-workflow.test.ts new file mode 100644 index 0000000..b4fc4b8 --- /dev/null +++ b/test/unit/pages-workflow.test.ts @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; +import { pagesProblems } from './pages-workflow.ts'; + +const REAL = readFileSync(new URL('../../.github/workflows/pages.yml', import.meta.url), 'utf8'); + +describe('pages.yml', () => { + it('follows the publishing rules', () => { + expect(pagesProblems(REAL)).toEqual([]); + }); + + it('fails when a pull request can start it', () => { + const text = REAL.replace(' workflow_dispatch:', ' workflow_dispatch:\n pull_request:'); + expect(pagesProblems(text)).toEqual([ + 'The triggers are pull_request, push, workflow_dispatch. ' + + 'Use push and workflow_dispatch only.', + ]); + }); + + it('fails when the push trigger names another branch', () => { + const text = REAL.replace('branches: [main]', 'branches: [main, next]'); + expect(pagesProblems(text)).toEqual([ + 'The push trigger must name the branch main and no other.', + ]); + }); + + it('fails when the deploy job loses its guard', () => { + const text = REAL.replace(/ {4}if: .*\n/, ''); + expect(pagesProblems(text)).toEqual([ + "The deploy job needs the guard if: github.ref == 'refs/heads/main'.", + ]); + }); + + it('fails when a checkout keeps its credentials', () => { + const text = REAL.replace('persist-credentials: false', 'persist-credentials: true'); + expect(pagesProblems(text)).toEqual([ + 'The checkout in build must set persist-credentials to false.', + ]); + }); +}); diff --git a/test/unit/pages-workflow.ts b/test/unit/pages-workflow.ts new file mode 100644 index 0000000..a4507f3 --- /dev/null +++ b/test/unit/pages-workflow.ts @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { parse } from 'yaml'; + +interface Step { + readonly uses?: string; + readonly with?: Readonly>; +} + +interface Workflow { + readonly on?: Readonly>; + readonly jobs?: Readonly< + Record + >; +} + +const MAIN_ONLY = "github.ref == 'refs/heads/main'"; + +function triggerProblems(workflow: Workflow): readonly string[] { + const triggers = Object.keys(workflow.on ?? {}).sort(); + const problems: string[] = []; + if (triggers.join() !== 'push,workflow_dispatch') { + problems.push(`The triggers are ${triggers.join(', ')}. Use push and workflow_dispatch only.`); + } + const push = workflow.on?.['push'] as { readonly branches?: readonly string[] } | null; + if (push?.branches?.join() !== 'main') { + problems.push('The push trigger must name the branch main and no other.'); + } + return problems; +} + +function checkoutProblems(workflow: Workflow): readonly string[] { + return Object.entries(workflow.jobs ?? {}).flatMap(([name, job]) => + (job?.steps ?? []) + .filter((step) => step.uses?.startsWith('actions/checkout@') === true) + .filter((step) => step.with?.['persist-credentials'] !== false) + .map(() => `The checkout in ${name} must set persist-credentials to false.`), + ); +} + +/** + * Checks the rules that keep the publishing workflow safe. Only a push to main and a manual run + * may start it, the deploy job runs only on main, and no checkout leaves a token in the clone. + * + * @param text - The YAML text of the workflow. + * @returns One line for each rule that the workflow breaks. + */ +export function pagesProblems(text: string): readonly string[] { + const workflow = parse(text) as Workflow; + const guard = + workflow.jobs?.['deploy']?.if === MAIN_ONLY + ? [] + : [`The deploy job needs the guard if: ${MAIN_ONLY}.`]; + return [...triggerProblems(workflow), ...guard, ...checkoutProblems(workflow)]; +} diff --git a/test/unit/php-reference.test.ts b/test/unit/php-reference.test.ts new file mode 100644 index 0000000..90f957f --- /dev/null +++ b/test/unit/php-reference.test.ts @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { execFile } from 'node:child_process'; +import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const run = promisify(execFile); +const SCRIPT = 'scripts/php-reference.php'; +let folder: string; + +function page(name: string): string { + return readFileSync(join(folder, `${name}.md`), 'utf8'); +} + +beforeAll(async () => { + folder = mkdtempSync(join(tmpdir(), 'php-reference-')); + await run('php', [SCRIPT, 'php', folder]); +}); + +afterAll(() => { + rmSync(folder, { recursive: true, force: true }); +}); + +describe('the PHP reference', () => { + it('has one page for each class and enum of the API dump, and an index', () => { + const dump = readFileSync('php/docs/api-dump.md', 'utf8'); + const names = Array.from(dump.matchAll(/^(?:final class|enum) (\w+)/gm), ([, name]) => name); + const pages = readdirSync(folder).map((file) => file.replace(/\.md$/, '')); + expect(pages.map((slug) => slug.replace(/^client-/, '')).sort()).toEqual( + ['index', ...names.map((name) => name?.toLowerCase())].sort(), + ); + }); + + it('shows each method with its signature, its doc comment and its parameters', () => { + const postcode = page('postcode'); + expect(postcode).toContain('### parse()\n\n```php\n'); + expect(postcode).toContain( + 'public static function parse(string $input, bool $allowPartial = false): ParseResult', + ); + expect(postcode).toContain("echo Postcode::normalize(' ek-01 a03.fk-01 '); // EK01A03FK01"); + expect(postcode).toMatch(/\| `\$allowPartial` \| Also accept a code that stops after/); + }); + + it('leaves out the test seam of the client, which has an internal type', () => { + const client = page('client-postcodeclient'); + expect(client).toContain('?Psr\\SimpleCache\\CacheInterface $cache = null,\n)'); + expect(client).not.toContain('Timer'); + }); + + it('lists the cases of an enum with their values', () => { + expect(page('client-errorcode')).toContain("- `RateLimited = 'rate_limited'`"); + }); + + it('fails with a message when the package folder is missing', async () => { + await expect(run('php', [SCRIPT, join(folder, 'absent'), folder])).rejects.toMatchObject({ + code: 1, + stderr: expect.stringContaining('has no src folder') as unknown, + }); + }); + + it('fails with a message when the package holds no public class', async () => { + const empty = mkdtempSync(join(tmpdir(), 'php-empty-')); + mkdirSync(join(empty, 'src')); + try { + await expect(run('php', [SCRIPT, empty, folder])).rejects.toMatchObject({ + code: 1, + stderr: expect.stringContaining('holds no public class') as unknown, + }); + } finally { + rmSync(empty, { recursive: true, force: true }); + } + }); + + it('refuses a call without two folders', async () => { + await expect(run('php', [SCRIPT])).rejects.toMatchObject({ code: 2 }); + }); +}); diff --git a/test/unit/release-state.test.ts b/test/unit/release-state.test.ts new file mode 100644 index 0000000..c567f6f --- /dev/null +++ b/test/unit/release-state.test.ts @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; +import { installLine, pageExamples } from '../support/examples.ts'; +import { trackedPages } from '../../scripts/check-prose.ts'; + +// The sentence that a guide holds while its package is not on a registry. One commit removes it +// from each guide and sets the state in src/release-state.json to "published". +const NOTICE = 'The first alpha is not published yet.'; + +const { state } = JSON.parse(readFileSync('src/release-state.json', 'utf8')) as { state: string }; + +const INSTALL_PAGES = [ + ...new Set( + pageExamples() + .filter( + ({ language, source }) => + language === 'sh' && source.split('\n').some((line) => installLine(line) !== undefined), + ) + .map(({ page }) => page), + ), +]; + +describe('the release state', () => { + it('is one of the two known values', () => { + expect(['published', 'not published']).toContain(state); + }); + + it('has guides with an install command, so the checks below cannot pass on no page', () => { + expect(INSTALL_PAGES.length).toBeGreaterThanOrEqual(3); + }); + + it.each(INSTALL_PAGES)('matches the install notice of %s', (page) => { + const text = readFileSync(page, 'utf8'); + expect( + text.includes(NOTICE), + state === 'not published' + ? 'the guide gives an install command and must say that the package is not published' + : 'the guide still says that the package is not published', + ).toBe(state === 'not published'); + }); + + it('keeps the notice off every page that gives no install command', () => { + const others = trackedPages().filter((page) => !INSTALL_PAGES.includes(page)); + expect(others.filter((page) => readFileSync(page, 'utf8').includes(NOTICE))).toEqual([]); + }); +}); diff --git a/test/unit/spec-pages.test.ts b/test/unit/spec-pages.test.ts new file mode 100644 index 0000000..bf5518c --- /dev/null +++ b/test/unit/spec-pages.test.ts @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { readFileSync } from 'node:fs'; +import { SPEC_VERSION as CLIENT_SPEC_VERSION } from '@gatepost/client'; +import { SPEC_VERSION as CORE_SPEC_VERSION } from '@gatepost/core'; +import { describe, expect, it } from 'vitest'; +import { specPages, wrapTables } from '../../scripts/spec-pages.ts'; + +const SPEC_VERSION = readFileSync('spec/VERSION', 'utf8').trim(); +const PAGES = specPages('spec', 'abc123'); + +describe('specPages', () => { + it('builds the grammar, the client contract and the glossary', () => { + expect(PAGES.map(({ slug }) => slug)).toEqual(['grammar', 'client', 'glossary']); + }); + + it('gives each page a title and drops the first heading of its file', () => { + const grammar = PAGES[0]?.text ?? ''; + expect(grammar).toMatch(/^---\ntitle: Postcode grammar\n/); + expect(grammar).not.toContain('\n# Postcode grammar\n'); + expect(grammar).toContain('\n## Segments\n'); + }); + + it('links each page to its file at the commit of the submodule, and names the version', () => { + expect(PAGES[1]?.text).toContain( + `[\`client.md\`](https://github.com/gatepost-dev/spec/blob/abc123/client.md) from spec ${SPEC_VERSION}.`, + ); + }); +}); + +describe('wrapTables', () => { + it('puts each table in a labelled box with a focus stop, named after the heading above', () => { + const text = '## Errors\n\nIntro.\n\n| a | b |\n| - | - |\n| 1 | 2 |\n\nAfter.\n'; + expect(wrapTables(text)).toBe( + [ + '## Errors', + '', + 'Intro.', + '', + '
    ', + '', + '| a | b |', + '| - | - |', + '| 1 | 2 |', + '', + '
    ', + '', + 'After.', + '', + ].join('\n'), + ); + }); + + it('labels a table that has no heading above it, and keeps text without a table', () => { + expect(wrapTables('| a |\n| - |\n')).toContain('aria-label="Table"'); + expect(wrapTables('Plain text.\n')).toBe('Plain text.\n'); + }); + + it('wraps every table of the spec pages that the site builds', () => { + for (const { text } of PAGES) { + expect(text.match(/^\|/gm)?.length ?? 0).toBe( + text.split('\n').filter((line) => line.startsWith('|')).length, + ); + expect(text.split('
    line.startsWith('|') && !all[i - 1]?.startsWith('|')).length + + 1, + ); + } + }); +}); + +describe('the spec that the site shows', () => { + it('is the spec that each SDK of the site implements', () => { + const php = readFileSync('php/src/Postcode.php', 'utf8'); + const phpVersion = /SPEC_VERSION = '([^']+)'/.exec(php)?.[1]; + expect([CORE_SPEC_VERSION, CLIENT_SPEC_VERSION, phpVersion]).toEqual([ + SPEC_VERSION, + SPEC_VERSION, + SPEC_VERSION, + ]); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66aad84 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "astro/tsconfigs/strictest", + "compilerOptions": { + "types": ["node"], + "allowImportingTsExtensions": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "noPropertyAccessFromIndexSignature": true + }, + "include": [".astro/types.d.ts", "src", "scripts", "test"], + "exclude": ["dist", "src/content/docs/reference"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4a21136 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2026 The Gatepost authors +// SPDX-License-Identifier: Apache-2.0 +import { defineConfig } from 'vitest/config'; + +// The unit tests run before the build. The site tests read the built pages in dist/. +export default defineConfig({ + test: { + projects: [ + { + test: { + name: 'unit', + include: ['test/unit/**/*.test.ts'], + setupFiles: ['test/support/block-network.ts'], + }, + }, + { test: { name: 'site', include: ['test/site/**/*.test.ts'] } }, + ], + // The unit tests measure the logic of the playground. The browser tests cover its DOM module, + // view.ts. + coverage: { + include: ['src/playground/describe.ts'], + thresholds: { branches: 80 }, + }, + }, +});