diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 273667b..e6eaab5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,50 +1,128 @@ name: CI -on: [push, pull_request] + +on: + push: + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + jobs: - build: - name: test deno v2.x ${{ matrix.os }} + test: + name: Test on Node ${{ matrix.node }} (${{ matrix.os }}) runs-on: ${{ matrix.os }} - timeout-minutes: 5 + timeout-minutes: 10 strategy: + fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - fail-fast: true + include: + - { os: ubuntu-latest, node: 22 } + - { os: ubuntu-latest, node: 24, coverage: true } + - { os: ubuntu-latest, node: 26 } + - { os: windows-latest, node: 24 } + - { os: macos-latest, node: 24 } steps: - name: Clone repository - uses: actions/checkout@v4 - - name: Setup deno - uses: denoland/setup-deno@main + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Setup pnpm + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - name: Setup Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - deno-version: v2.x - - name: Check formatting - if: matrix.os == 'ubuntu-latest' - run: deno fmt --check - - name: Check linting - if: matrix.os == 'ubuntu-latest' - run: deno lint + node-version: ${{ matrix.node }} + cache: pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Check lint, formatting, and types + run: pnpm check - name: Run tests - run: deno test -A --coverage + if: ${{ !matrix.coverage }} + run: pnpm test + - name: Run tests with coverage + if: ${{ matrix.coverage }} + run: pnpm test:coverage - name: Upload coverage - if: | - matrix.os == 'ubuntu-latest' - uses: codecov/codecov-action@v4 + if: ${{ matrix.coverage }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6.0.2 with: - fail_ci_if_error: true files: coverage/lcov.info - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - name: Release info - if: | - github.repository == 'udibo/http-error' && - matrix.os == 'ubuntu-latest' && - startsWith(github.ref, 'refs/tags/') - shell: bash + fail_ci_if_error: ${{ github.event_name == 'push' }} + token: ${{ secrets.CODECOV_TOKEN }} + + package: + name: Build and smoke-test the package on Node ${{ matrix.node }} + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + node: [22, 24, 26] + steps: + - name: Clone repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Setup pnpm + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - name: Setup Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version: ${{ matrix.node }} + cache: pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Build the npm package + run: pnpm build + - name: Install the packed tarball and run it from Node + run: pnpm smoke + - name: Verify the package publishes to JSR (dry run) + if: matrix.node == 24 + run: pnpm jsr:dry-run + + release: + name: Release to npm and JSR + needs: [test, package] + if: >- + github.event_name == 'push' + && github.ref == 'refs/heads/main' + && vars.RELEASE_ENABLED == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + id-token: write + issues: write + pull-requests: write + steps: + - name: Clone repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + - name: Setup pnpm + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + # No `registry-url` on purpose: it makes setup-node write an .npmrc whose + # auth line is a placeholder, which @semantic-release/npm then reads + # instead of exchanging the OIDC token. + - name: Setup Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version: 24 + cache: pnpm + # npm trusted publishing (OIDC) needs npm 11.5.1 or later. + - name: Ensure npm supports trusted publishing run: | - echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV - - name: Release - uses: softprops/action-gh-release@v1 - if: env.RELEASE_VERSION != '' + npm install -g npm@latest + npm --version + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Build the npm package + run: pnpm build + # NPM_TOKEN exists only for the first publish, before the package has a + # trusted publisher; delete the secret afterwards (see PUBLISHING.md). + - name: Run semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - draft: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: pnpm exec semantic-release diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 9e7cc8a..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: publish - -on: - push: - branches: - - main - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - uses: actions/checkout@v4 - - run: npx jsr publish diff --git a/.gitignore b/.gitignore index 4ebc8ae..8d0aa9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ +node_modules +dist coverage +*.tgz diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..6f4247a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +26 diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..7b0e1db --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,5 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 80, + "ignorePatterns": ["dist", "coverage", "pnpm-lock.yaml", "node_modules"] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..8b5fea7 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,17 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "oxc", "import", "vitest"], + "categories": { + "correctness": "error", + "suspicious": "warn", + "perf": "warn" + }, + "rules": { + "typescript/consistent-type-imports": "error", + "typescript/no-explicit-any": "error", + "eqeqeq": "error", + "no-console": "off", + "typescript/no-unsafe-type-assertion": "off" + }, + "ignorePatterns": ["dist", "coverage", "node_modules"] +} diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..d61c724 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,35 @@ +{ + "branches": ["main"], + "tagFormat": "${version}", + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { "breaking": true, "release": "minor" }, + { "revert": true, "release": "patch" }, + { "type": "feat", "release": "minor" }, + { "type": "fix", "release": "patch" }, + { "type": "perf", "release": "patch" }, + { "type": "build", "scope": "deps", "release": "patch" }, + { "type": "chore", "scope": "deps", "release": "patch" } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { "preset": "conventionalcommits" } + ], + "@sebbo2002/semantic-release-jsr", + "@semantic-release/npm", + "@semantic-release/github", + [ + "@semantic-release/git", + { + "assets": ["package.json", "jsr.json"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ] + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 943caaa..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "deno run", - "type": "pwa-node", - "request": "launch", - "cwd": "${workspaceFolder}", - "runtimeExecutable": "deno", - "runtimeArgs": ["run", "--inspect", "${file}"], - "attachSimplePort": 9229 - }, - { - "name": "deno test", - "type": "pwa-node", - "request": "launch", - "cwd": "${workspaceFolder}", - "runtimeExecutable": "deno", - "runtimeArgs": ["test", "--inspect", "${file}"], - "attachSimplePort": 9229 - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3da0161..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "deno.enable": true, - "deno.lint": true, - "deno.config": "./deno.json", - "editor.formatOnSave": true, - "editor.defaultFormatter": "denoland.vscode-deno", - "editor.quickSuggestions": { - "strings": true - } -} diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 index 0000000..d8be1ca --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,75 @@ +# Publishing @udibo/http-error + +The package is published to [npm](https://www.npmjs.com/package/@udibo/http-error) +and [JSR](https://jsr.io/@udibo/http-error) from one semantic-release run on +every push to `main` whose [Conventional Commits](https://www.conventionalcommits.org) +call for a release (`feat` is a minor release, `fix`, `perf`, and `chore(deps)` +are patch releases). While the version is 0.x, breaking changes also release a +minor version, so a `BREAKING CHANGE` footer never produces 1.0.0 on its own. + +The release job in [`.github/workflows/ci.yml`](.github/workflows/ci.yml) only +runs when the repository variable `RELEASE_ENABLED` is `true`. Until it is set, +merging to `main` runs the checks and publishes nothing. The first release after +the Node migration is **0.12.0** (the migration PR is a `feat`, and `0.11.1` is +the last tag). + +## One-time setup + +1. **JSR.** The `@udibo/http-error` package already exists in the `@udibo` + scope. In its settings on jsr.io, confirm it is linked to the GitHub + repository `udibo/http-error`. The release job publishes with GitHub OIDC; + no JSR token is stored. +2. **npm, first publish.** The package does not exist on npm yet, and npm cannot + configure a trusted publisher for a package that does not exist. For the + first release only: + 1. Create a granular npm access token that can publish new packages in the + `@udibo` scope (read and write, scoped to `@udibo/*`, short expiry). + 2. Save it as the repository secret `NPM_TOKEN`. +3. **Enable releases.** Set the repository variable + `RELEASE_ENABLED=true` (Settings, Secrets and variables, Actions, Variables, + or `gh variable set RELEASE_ENABLED --body true --repo udibo/http-error`). +4. **Merge the migration PR** (squash merge with a `feat:` title). The release + job publishes 0.12.0 to npm (with a provenance attestation) and JSR, pushes + the `chore(release)` commit that updates `package.json` and `jsr.json`, tags + `0.12.0`, and creates the GitHub release. +5. **npm trusted publishing.** Once the package exists on npm, open + npmjs.com, then `@udibo/http-error`, then Settings, then Trusted Publisher, + and add a GitHub Actions publisher: organization `udibo`, repository + `http-error`, workflow filename `ci.yml`, environment empty. +6. **Remove the bootstrap token.** Delete the `NPM_TOKEN` repository secret and + revoke the token on npm. A stored token takes precedence over the OIDC + exchange, so leaving it in place keeps the long-lived credential alive. + Later releases authenticate with the job's OIDC token only. + +If `main` gains a ruleset that requires pull requests, the release commit needs +a bypass. Generate an SSH deploy key with write access, store the private half +as a secret, and pass it to `actions/checkout` as `ssh-key`. + +## Validate locally + +```sh +pnpm install --frozen-lockfile +pnpm check +pnpm test:coverage +pnpm build +pnpm smoke +pnpm jsr:dry-run +``` + +`pnpm smoke` packs the package, installs the tarball into an empty project, type +checks a consumer under NodeNext resolution, and runs it on Node. CI runs the +same steps on Node 22, 24, and 26. + +## What the release does + +- Computes the next version from the commits since the last tag. +- Updates the version in `package.json` and `jsr.json`. +- Publishes `dist/` to npm (`pnpm build` runs before semantic-release so npm + authentication is verified before anything is pushed) and the TypeScript + sources in `src/` to JSR. +- Commits the version files, tags the release, and creates the GitHub release. + +A release is not a transaction across Git, npm, and JSR. If publication fails +partway, inspect all three before re-running. npm and JSR versions are +immutable, so a version that already exists on either registry cannot be +overwritten. diff --git a/README.md b/README.md index 584a230..ef61d8a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Http Error +[![npm](https://img.shields.io/npm/v/@udibo/http-error)](https://www.npmjs.com/package/@udibo/http-error) [![JSR](https://jsr.io/badges/@udibo/http-error)](https://jsr.io/@udibo/http-error) [![JSR Score](https://jsr.io/badges/@udibo/http-error/score)](https://jsr.io/@udibo/http-error) [![CI](https://github.com/udibo/http-error/workflows/CI/badge.svg)](https://github.com/udibo/http-error/actions?query=workflow%3ACI) @@ -15,6 +16,68 @@ This package was inspired by - Framework agnostic - RFC 9457 Problem Details compliant error responses +- Zero runtime dependencies +- Written in TypeScript and published as ESM with type declarations + +## Installation + +The package is published to [npm](https://www.npmjs.com/package/@udibo/http-error) +and [JSR](https://jsr.io/@udibo/http-error). It requires Node.js 22 or later, +and also works in browsers and other runtimes that provide the standard +`Headers` and `Response` classes. + +```sh +# npm +npm install @udibo/http-error + +# pnpm +pnpm add @udibo/http-error + +# pnpm, from JSR (pnpm 10.9 or later) +pnpm add jsr:@udibo/http-error + +# npm, from JSR +npx jsr add @udibo/http-error +``` + +The package is ESM only. Import it from an ES module (or a TypeScript project +using `"module": "NodeNext"`, `"Bundler"`, or another ESM-aware setting): + +```ts +import { HttpError } from "@udibo/http-error"; +``` + +### Node.js + +Use it in any Node.js ES module. `HttpError` extends `Error`, so it works with +`throw`, `try`/`catch`, and `error.cause` like any other error. + +```ts +import { HttpError } from "@udibo/http-error"; + +try { + throw new HttpError(404, "file not found"); +} catch (error) { + const httpError = HttpError.from(error); + console.log(httpError.status); // 404 + console.log(JSON.stringify(httpError)); // {"status":404,"title":"Not Found","detail":"file not found"} +} +``` + +### Browsers + +The package does not import any Node.js modules, so it can be bundled for +browsers (Vite, esbuild, webpack, and similar) or loaded directly as a module. +It is useful on the client for turning a failed `fetch` response into an error: + +```ts +import { HttpError } from "@udibo/http-error"; + +const response = await fetch("/api/items/1"); +if (!response.ok) { + throw await HttpError.from(response); +} +``` ## Usage @@ -80,10 +143,7 @@ name in the options. import { HttpError, type HttpErrorOptions } from "@udibo/http-error"; class CustomError extends HttpError { - constructor( - message?: string, - options?: HttpErrorOptions, - ) { + constructor(message?: string, options?: HttpErrorOptions) { super(message, { name: "CustomError", status: 420, ...options }); } } @@ -318,11 +378,14 @@ console.log(anotherError.extensions.errorCode); // "API_GENERAL_FAILURE" (from d #### Hono Here's an example of how to use `HttpError` with Hono, utilizing its -`app.onError` handler to return Problem Details JSON responses. +`app.onError` handler to return Problem Details JSON responses. It is also +available as [`examples/hono.ts`](examples/hono.ts), with tests in +[`examples/hono.test.ts`](examples/hono.test.ts) that call the app with +`app.request()`. ```ts import { Hono } from "hono"; -import { HTTPException } from "hono/http-exception"; // For comparison +import { HTTPException } from "hono/http-exception"; import { HttpError } from "@udibo/http-error"; const app = new Hono(); @@ -332,7 +395,6 @@ app.get("/error", () => { }); app.get("/hono-error", () => { - // Hono's native error, for comparison throw new HTTPException(400, { message: "This is an example of an error from hono", }); @@ -348,77 +410,26 @@ app.get("/http-error", () => { }); }); -// Global error handler -app.onError(async (cause, c) => { // c is Hono's context - console.log("Hono onError caught:", cause); - +app.onError((cause) => { // Converts non-HttpError instances to HttpError instances - // For Response objects (e.g. from fetch), HttpError.from is async - const error = cause instanceof Response - ? await HttpError.from(cause) - : HttpError.from(cause); + const error = HttpError.from(cause); - console.error(error); // Log the full HttpError + // Log the full internal error, including the cause + console.error(error); - return error.getResponse(); // Return a Response object directly + // The response body only contains the safe `exposedMessage` + return error.getResponse(); }); -console.log("Hono server running on http://localhost:8000"); -Deno.serve(app.fetch); +export default app; ``` -#### Oak - -Here is an example of how an Oak server could have middleware that converts any -thrown error into an `HttpError` and returns a Problem Details JSON response. +To run it on Node.js, serve the app with +[`@hono/node-server`](https://github.com/honojs/node-server): ```ts -import { Application, Router } from "@oak/oak"; -import { HttpError } from "@udibo/http-error"; - -const app = new Application(); - -// Error handling middleware -app.use(async (context, next) => { - try { - await next(); - } catch (cause) { - // Converts non-HttpError instances to HttpError instances - // For Response objects (e.g. from fetch), HttpError.from is async - const error = cause instanceof Response - ? await HttpError.from(cause) - : HttpError.from(cause); - - console.error(error); // Log the full error - - const { response } = context; - response.status = error.status; - error.headers.forEach((value, key) => { // Set custom headers from HttpError - response.headers.set(key, value); - }); - // Set Content-Type if not already set by error.headers - if (!response.headers.has("Content-Type")) { - response.headers.set("Content-Type", "application/problem+json"); - } - response.body = error.toJSON(); // Send Problem Details as JSON - } -}); - -const router = new Router(); -router.get("/test-error", () => { - // Will be caught and transformed by the middleware - throw new Error("A generic error occurred!"); -}); -router.get("/test-http-error", () => { - throw new HttpError(400, "This is a specific HttpError.", { - type: "/errors/my-custom-error", - extensions: { info: "some detail" }, - }); -}); - -app.use(router.routes()); -app.use(router.allowedMethods()); +import { serve } from "@hono/node-server"; +import app from "./app.ts"; -console.log("Oak server running on http://localhost:8000"); -await app.listen({ port: 8000 }); +serve({ fetch: app.fetch, port: 8000 }); ``` diff --git a/deno.json b/deno.json deleted file mode 100644 index 597f42c..0000000 --- a/deno.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@udibo/http-error", - "version": "0.11.1", - "exports": { - ".": "./mod.ts" - }, - "publish": { - "include": [ - "LICENSE", - "README.md", - "**/*.ts" - ], - "exclude": ["**/*.test.ts"] - }, - "imports": { - "@udibo/http-error": "./mod.ts", - "@std/assert": "jsr:@std/assert@^1.0.16", - "@std/http": "jsr:@std/http@^1.0.22", - "@std/testing": "jsr:@std/testing@^1.0.16", - "@std/streams": "jsr:@std/streams@^1.0.14", - "@std/path": "jsr:@std/path@^1.1.3", - "@oak/oak": "jsr:@oak/oak@^17.2.0", - "hono": "npm:hono@^4.10.7" - }, - "tasks": { - "check": { - "description": "Checks the formatting and runs the linter.", - "command": "deno lint && deno fmt --check" - }, - "git-rebase": { - "description": "Gets your branch up to date with master after a squash merge.", - "command": "git fetch origin main && git rebase --onto origin/main HEAD" - } - } -} diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 50dd24f..0000000 --- a/deno.lock +++ /dev/null @@ -1,156 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@oak/commons@1": "1.0.1", - "jsr:@oak/oak@^17.2.0": "17.2.0", - "jsr:@std/assert@1": "1.0.16", - "jsr:@std/assert@^1.0.15": "1.0.16", - "jsr:@std/assert@^1.0.16": "1.0.16", - "jsr:@std/bytes@1": "1.0.6", - "jsr:@std/bytes@^1.0.6": "1.0.6", - "jsr:@std/cli@^1.0.24": "1.0.24", - "jsr:@std/crypto@1": "1.0.5", - "jsr:@std/data-structures@^1.0.9": "1.0.9", - "jsr:@std/encoding@1": "1.0.10", - "jsr:@std/encoding@^1.0.10": "1.0.10", - "jsr:@std/fmt@^1.0.8": "1.0.8", - "jsr:@std/fs@^1.0.19": "1.0.20", - "jsr:@std/fs@^1.0.20": "1.0.20", - "jsr:@std/html@^1.0.5": "1.0.5", - "jsr:@std/http@1": "1.0.22", - "jsr:@std/http@^1.0.22": "1.0.22", - "jsr:@std/internal@^1.0.12": "1.0.12", - "jsr:@std/media-types@1": "1.1.0", - "jsr:@std/media-types@^1.1.0": "1.1.0", - "jsr:@std/net@^1.0.6": "1.0.6", - "jsr:@std/path@1": "1.1.3", - "jsr:@std/path@^1.1.2": "1.1.3", - "jsr:@std/path@^1.1.3": "1.1.3", - "jsr:@std/streams@^1.0.14": "1.0.14", - "jsr:@std/testing@^1.0.16": "1.0.16", - "npm:hono@^4.10.7": "4.10.7", - "npm:path-to-regexp@^6.3.0": "6.3.0" - }, - "jsr": { - "@oak/commons@1.0.1": { - "integrity": "889ff210f0b4292591721be07244ecb1b5c118742f5273c70cf30d7cd4184d0c", - "dependencies": [ - "jsr:@std/assert@1", - "jsr:@std/bytes@1", - "jsr:@std/crypto", - "jsr:@std/encoding@1", - "jsr:@std/http@1", - "jsr:@std/media-types@1" - ] - }, - "@oak/oak@17.2.0": { - "integrity": "938537a92fc7922a46a9984696c65fb189c9baad164416ac3e336768a9ff0cd1", - "dependencies": [ - "jsr:@oak/commons", - "jsr:@std/assert@1", - "jsr:@std/bytes@1", - "jsr:@std/http@1", - "jsr:@std/media-types@1", - "jsr:@std/path@1", - "npm:path-to-regexp" - ] - }, - "@std/assert@1.0.16": { - "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/bytes@1.0.6": { - "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" - }, - "@std/cli@1.0.24": { - "integrity": "b655a5beb26aa94f98add6bc8889f5fb9bc3ee2cc3fc954e151201f4c4200a5e" - }, - "@std/crypto@1.0.5": { - "integrity": "0dcfbb319fe0bba1bd3af904ceb4f948cde1b92979ec1614528380ed308a3b40" - }, - "@std/data-structures@1.0.9": { - "integrity": "033d6e17e64bf1f84a614e647c1b015fa2576ae3312305821e1a4cb20674bb4d" - }, - "@std/encoding@1.0.10": { - "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" - }, - "@std/fmt@1.0.8": { - "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" - }, - "@std/fs@1.0.20": { - "integrity": "e953206aae48d46ee65e8783ded459f23bec7dd1f3879512911c35e5484ea187", - "dependencies": [ - "jsr:@std/path@^1.1.3" - ] - }, - "@std/html@1.0.5": { - "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" - }, - "@std/http@1.0.22": { - "integrity": "53f0bb70e23a2eec3e17c4240a85bb23d185b2e20635adb37ce0f03cc4ca012a", - "dependencies": [ - "jsr:@std/cli", - "jsr:@std/encoding@^1.0.10", - "jsr:@std/fmt", - "jsr:@std/fs@^1.0.20", - "jsr:@std/html", - "jsr:@std/media-types@^1.1.0", - "jsr:@std/net", - "jsr:@std/path@^1.1.3", - "jsr:@std/streams" - ] - }, - "@std/internal@1.0.12": { - "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" - }, - "@std/media-types@1.1.0": { - "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" - }, - "@std/net@1.0.6": { - "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" - }, - "@std/path@1.1.3": { - "integrity": "b015962d82a5e6daea980c32b82d2c40142149639968549c649031a230b1afb3", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/streams@1.0.14": { - "integrity": "c0df6cdd73bd4bbcbe4baa89e323b88418c90ceb2d926f95aa99bdcdbfca2411", - "dependencies": [ - "jsr:@std/bytes@^1.0.6" - ] - }, - "@std/testing@1.0.16": { - "integrity": "a917ffdeb5924c9be436dc78bc32e511760e14d3a96e49c607fc5ecca86d0092", - "dependencies": [ - "jsr:@std/assert@^1.0.15", - "jsr:@std/data-structures", - "jsr:@std/fs@^1.0.19", - "jsr:@std/internal", - "jsr:@std/path@^1.1.2" - ] - } - }, - "npm": { - "hono@4.10.7": { - "integrity": "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw==" - }, - "path-to-regexp@6.3.0": { - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" - } - }, - "workspace": { - "dependencies": [ - "jsr:@oak/oak@^17.2.0", - "jsr:@std/assert@^1.0.16", - "jsr:@std/http@^1.0.22", - "jsr:@std/path@^1.1.3", - "jsr:@std/streams@^1.0.14", - "jsr:@std/testing@^1.0.16", - "npm:hono@^4.10.7" - ] - } -} diff --git a/examples/hono.test.ts b/examples/hono.test.ts index 589dcf7..bed3064 100644 --- a/examples/hono.test.ts +++ b/examples/hono.test.ts @@ -1,65 +1,44 @@ -import { afterAll, beforeAll, describe, it } from "@std/testing/bdd"; -import { assertEquals } from "@std/assert"; -import { resolve } from "@std/path"; -import { mergeReadableStreams, TextLineStream } from "@std/streams"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -describe("hono error handling", () => { - let server: Deno.ChildProcess; - - beforeAll(async () => { - const command = new Deno.Command(Deno.execPath(), { - args: [ - "run", - "-A", - resolve(import.meta.dirname!, "./hono.ts"), - ], - stdout: "piped", - stderr: "piped", - }); - server = command.spawn(); - const stdout = mergeReadableStreams(server.stdout, server.stderr) - .pipeThrough(new TextDecoderStream()) - .pipeThrough(new TextLineStream()); +import { app } from "./hono.ts"; - for await (const line of stdout.values({ preventCancel: true })) { - if (line.includes("Listening on") && line.includes(":8000")) { - break; - } - } +describe("hono error handling", () => { + beforeEach(() => { + vi.spyOn(console, "log").mockImplementation(() => {}); + vi.spyOn(console, "error").mockImplementation(() => {}); }); - afterAll(async () => { - server.kill(); - await server.status; + afterEach(() => { + vi.restoreAllMocks(); }); - it("plain error converted to http error response", async () => { - const res = await fetch("http://localhost:8000/error"); - assertEquals(res.status, 500); - assertEquals(res.headers.get("content-type"), "application/problem+json"); - assertEquals(await res.json(), { + it("converts a plain error to a problem details response", async () => { + const res = await app.request("/error"); + expect(res.status).toBe(500); + expect(res.headers.get("content-type")).toBe("application/problem+json"); + expect(await res.json()).toEqual({ status: 500, title: "Internal Server Error", detail: "The server encountered an unexpected condition.", }); }); - it("hono HttpException converted to http error response", async () => { - const res = await fetch("http://localhost:8000/hono-error"); - assertEquals(res.status, 400); - assertEquals(res.headers.get("content-type"), "application/problem+json"); - assertEquals(await res.json(), { + it("converts a Hono HTTPException to a problem details response", async () => { + const res = await app.request("/hono-error"); + expect(res.status).toBe(400); + expect(res.headers.get("content-type")).toBe("application/problem+json"); + expect(await res.json()).toEqual({ status: 400, title: "Error", detail: "This is an example of an error from hono", }); }); - it("http error response", async () => { - const res = await fetch("http://localhost:8000/http-error"); - assertEquals(res.status, 400); - assertEquals(res.headers.get("content-type"), "application/problem+json"); - assertEquals(await res.json(), { + it("returns an HttpError as a problem details response with its extensions", async () => { + const res = await app.request("/http-error"); + expect(res.status).toBe(400); + expect(res.headers.get("content-type")).toBe("application/problem+json"); + expect(await res.json()).toEqual({ status: 400, title: "Bad Request", detail: "This is an example of an HttpError", @@ -68,4 +47,10 @@ describe("hono error handling", () => { customField: "customValue", }); }); + + it("logs the converted error and the request", async () => { + await app.request("/http-error"); + expect(console.log).toHaveBeenCalledWith("GET", "/http-error"); + expect(console.error).toHaveBeenCalledOnce(); + }); }); diff --git a/examples/hono.ts b/examples/hono.ts index aa8427b..9435214 100644 --- a/examples/hono.ts +++ b/examples/hono.ts @@ -2,7 +2,7 @@ import { Hono } from "hono"; import { HTTPException } from "hono/http-exception"; import { HttpError } from "@udibo/http-error"; -const app = new Hono(); +export const app: Hono = new Hono(); app.use(async (context, next) => { const { req } = context; @@ -37,4 +37,4 @@ app.onError((cause) => { return error.getResponse(); }); -Deno.serve(app.fetch); +export default app; diff --git a/examples/oak.test.ts b/examples/oak.test.ts deleted file mode 100644 index c1f5160..0000000 --- a/examples/oak.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { afterAll, beforeAll, describe, it } from "@std/testing/bdd"; -import { assertEquals } from "@std/assert"; -import { resolve } from "@std/path"; -import { mergeReadableStreams, TextLineStream } from "@std/streams"; - -describe("oak error handling", () => { - let server: Deno.ChildProcess; - - beforeAll(async () => { - const command = new Deno.Command(Deno.execPath(), { - args: [ - "run", - "-A", - resolve(import.meta.dirname!, "./oak.ts"), - ], - stdout: "piped", - stderr: "piped", - }); - server = command.spawn(); - const stdout = mergeReadableStreams(server.stdout, server.stderr) - .pipeThrough(new TextDecoderStream()) - .pipeThrough(new TextLineStream()); - - for await (const line of stdout.values({ preventCancel: true })) { - if (line.includes("Listening on") && line.includes(":8000")) { - break; - } - } - }); - - afterAll(async () => { - server.kill(); - await server.status; - }); - - it("plain error converted to http error response", async () => { - const res = await fetch("http://localhost:8000/error"); - assertEquals(res.status, 500); - assertEquals(res.headers.get("content-type"), "application/problem+json"); - assertEquals(await res.json(), { - status: 500, - title: "Internal Server Error", - detail: "The server encountered an unexpected condition.", - }); - }); - - it("oak error converted to http error response", async () => { - const res = await fetch("http://localhost:8000/oak-error"); - assertEquals(res.status, 400); - assertEquals(res.headers.get("content-type"), "application/problem+json"); - assertEquals(await res.json(), { - status: 400, - title: "Bad Request", - detail: "This is an example of an error from oak", - }); - }); - - it("http error response", async () => { - const res = await fetch("http://localhost:8000/http-error"); - assertEquals(res.status, 400); - assertEquals(res.headers.get("content-type"), "application/problem+json"); - assertEquals(await res.json(), { - status: 400, - title: "Bad Request", - detail: "This is an example of an HttpError", - type: "/errors/http-error", - instance: "/errors/http-error/instance/123", - customField: "customValue", - }); - }); -}); diff --git a/examples/oak.ts b/examples/oak.ts deleted file mode 100644 index e00c944..0000000 --- a/examples/oak.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Application, Router } from "@oak/oak"; -import { HttpError } from "@udibo/http-error"; - -const router = new Router(); - -router.get("/error", () => { - throw new Error("This is an example of a plain Error"); -}); - -router.get("/http-error", () => { - throw new HttpError(400, "This is an example of an HttpError", { - type: "/errors/http-error", - instance: "/errors/http-error/instance/123", - extensions: { - customField: "customValue", - }, - }); -}); - -router.get("/oak-error", (context) => { - context.throw(400, "This is an example of an error from oak"); -}); - -const app = new Application(); -app.use(async (context, next) => { - const { request, response } = context; - try { - console.log(request.method, request.url.pathname); - await next(); - } catch (cause) { - // Converts non HttpError instances to HttpError instances - const error = HttpError.from(cause); - console.error(error); - response.status = error.status; - error.headers.forEach((value, key) => { - response.headers.set(key, value); - }); - response.body = error.toJSON(); - } -}); - -app.use(router.routes()); -app.use(router.allowedMethods()); - -app.addEventListener("listen", ({ port }) => { - console.log(`Listening on http://localhost:${port}/`); -}); - -app.addEventListener("error", (event) => { - console.error("event.error", event.error); -}); - -try { - await app.listen({ port: 8000 }); -} catch (error) { - console.error(error); -} diff --git a/jsr.json b/jsr.json new file mode 100644 index 0000000..c5cdbdd --- /dev/null +++ b/jsr.json @@ -0,0 +1,9 @@ +{ + "name": "@udibo/http-error", + "version": "0.11.1", + "exports": "./src/mod.ts", + "publish": { + "include": ["src/**/*.ts", "README.md", "LICENSE"], + "exclude": ["src/**/*.test.ts"] + } +} diff --git a/mod.test.ts b/mod.test.ts deleted file mode 100644 index 399063c..0000000 --- a/mod.test.ts +++ /dev/null @@ -1,1233 +0,0 @@ -import { STATUS_TEXT, type StatusCode } from "@std/http/status"; -import { - assert, - assertEquals, - assertStrictEquals, - assertThrows, -} from "@std/assert"; -import { describe, it } from "@std/testing/bdd"; - -import { - createHttpErrorClass, - HttpError, - type HttpErrorOptions, - isHttpErrorLike, -} from "./mod.ts"; - -const httpErrorTests = describe("HttpError"); - -it(httpErrorTests, "without args", () => { - const error = new HttpError(); - assertEquals(error.toString(), "Internal Server Error"); - assertEquals(error.name, "Internal Server Error"); - assertEquals(error.message, ""); - assertEquals(error.status, 500); - assertEquals(error.expose, false); - assertEquals(error.cause, undefined); - assertEquals( - error.exposedMessage, - "The server encountered an unexpected condition.", - ); -}); - -it(httpErrorTests, "with status", () => { - function assertWithStatus(error: HttpError): void { - assertEquals(error.status, 400); - assertEquals(error.expose, true); - } - assertWithStatus(new HttpError(400)); - assertWithStatus(new HttpError({ status: 400 })); - assertWithStatus(new HttpError(undefined, { status: 400 })); -}); - -it(httpErrorTests, "with message", () => { - function assertWithMessage(error: HttpError): void { - assertEquals( - error.toString(), - "Internal Server Error: something went wrong", - ); - assertEquals(error.message, "something went wrong"); - // expose is false for 500 errors, so exposedMessage uses default - assertEquals( - error.exposedMessage, - "The server encountered an unexpected condition.", - ); - } - assertWithMessage(new HttpError("something went wrong")); - assertWithMessage(new HttpError({ message: "something went wrong" })); - assertWithMessage(new HttpError(undefined, "something went wrong")); - assertWithMessage( - new HttpError(undefined, { message: "something went wrong" }), - ); -}); - -it( - httpErrorTests, - "prefer status/message args over status/messagee options", - () => { - const names = ["Bad Request", "Bad Gateway"]; - const messages = ["something went wrong", "failed"]; - const statuses = [400, 502]; - const options = { message: messages[1], status: statuses[1] }; - - function assertPreferArgs( - error: HttpError, - expectedStatus: number, - expectedMessage: string, - ): void { - const expectedName = names[expectedStatus === statuses[0] ? 0 : 1]; - assertEquals(error.toString(), `${expectedName}: ${expectedMessage}`); - assertEquals(error.name, expectedName); - assertEquals(error.message, expectedMessage); - assertEquals(error.status, expectedStatus); - assertEquals(error.expose, expectedStatus < 500); - } - assertPreferArgs( - new HttpError(statuses[0], messages[0], options), - statuses[0], - messages[0], - ); - assertPreferArgs( - new HttpError(statuses[0], options), - statuses[0], - messages[1], - ); - assertPreferArgs( - new HttpError(messages[0], options), - statuses[1], - messages[0], - ); - assertPreferArgs(new HttpError(options), statuses[1], messages[1]); - }, -); - -it(httpErrorTests, "with cause", () => { - const cause = new Error("fail"); - function assertWithCause(error: HttpError): void { - assertEquals(error.cause, cause); - } - assertWithCause(new HttpError({ cause })); - assertWithCause(new HttpError(undefined, { cause })); - assertWithCause(new HttpError(undefined, undefined, { cause })); -}); - -it(httpErrorTests, "invalid status", () => { - assertThrows( - () => new HttpError(-500), - RangeError, - "invalid error status", - ); - assertThrows(() => new HttpError(0), RangeError, "invalid error status"); - assertThrows( - () => new HttpError(399), - RangeError, - "invalid error status", - ); - assertThrows( - () => new HttpError(600), - RangeError, - "invalid error status", - ); -}); - -function expectedDefaultErrorName(status: number): string { - if (STATUS_TEXT[status as StatusCode]) { - return STATUS_TEXT[status as StatusCode]; - } - return status < 500 ? "Unknown Client Error" : "Unknown Server Error"; -} - -function assertName( - error: HttpError, - expectedStatus: number, - expectedMessage: string, - expectedName?: string, -): void { - if (!expectedName) expectedName = expectedDefaultErrorName(expectedStatus); - assertEquals( - [ - error.status, - error.name, - error.message, - error.toString(), - ], - [ - expectedStatus, - expectedName, - expectedMessage, - `${expectedName}: ${expectedMessage}`, - ], - ); -} - -it(httpErrorTests, "default name", () => { - const message = "something went wrong"; - for (let status = 400; status < 600; status++) { - assertName(new HttpError(status, message), status, message); - } -}); - -it(httpErrorTests, "override name", () => { - const message = "something went wrong"; - for (let status = 400; status < 600; status++) { - assertName( - new HttpError(status, message, { name: "CustomError" }), - status, - message, - "CustomError", - ); - } -}); - -function assertExpose( - error: HttpError, - expectedStatus: number, - expectedExpose?: boolean, -): void { - assertEquals( - [ - error.status, - error.expose, - ], - [ - expectedStatus, - expectedExpose ?? (expectedStatus < 500), - ], - ); -} - -it(httpErrorTests, "default expose", () => { - for (let status = 400; status < 600; status++) { - assertExpose(new HttpError(status), status); - } -}); - -it(httpErrorTests, "override expose", () => { - for (let status = 400; status < 600; status++) { - const expose = status >= 500; - assertExpose(new HttpError(status, { expose }), status, expose); - } -}); - -const withAllOptionsTests = describe(httpErrorTests, "with all options"); - -it( - withAllOptionsTests, - "constructor with status, message, options signature", - () => { - const cause = new Error("fail"); - const extensionsData = { key: "value" }; - const customHeaders = new Headers({ "X-Custom-Header": "TestValue" }); - customHeaders.set("content-type", "application/custom+json"); - - function assertAllOptions(error: HttpError) { - assertEquals(error.name, "CustomErrorName"); - assertEquals(error.message, "A very specific error occurred."); - assertEquals(error.status, 418); - assertEquals(error.statusText, "I'm a teapot"); - assertEquals(error.expose, false); - assertEquals(error.cause, cause); - assertEquals(error.type, "/errors/custom-operation-failed"); - assertEquals( - error.instance, - "/errors/custom-operation-failed/instance/123xyz", - ); - assertEquals(error.extensions, extensionsData); - assertEquals(error.headers.get("X-Custom-Header"), "TestValue"); - assertEquals( - error.headers.get("content-type"), - "application/custom+json", - ); - assertEquals( - error.toString(), - "CustomErrorName: A very specific error occurred.", - ); - } - - const allOpts: HttpErrorOptions = { - name: "CustomErrorName", - message: "A very specific error occurred.", - status: 418, - statusText: "I'm a teapot", - expose: false, - cause, - type: "/errors/custom-operation-failed", - instance: "/errors/custom-operation-failed/instance/123xyz", - extensions: extensionsData, - headers: customHeaders, - }; - - assertAllOptions( - new HttpError( - 418, - "A very specific error occurred.", - allOpts, - ), - ); - }, -); - -it(withAllOptionsTests, "constructor with status, options signature", () => { - const cause = new Error("fail"); - const extensionsData = { key: "value" }; - const customHeaders = new Headers({ "X-Custom-Header": "TestValue" }); - customHeaders.set("content-type", "application/custom+json"); - - function assertAllOptions(error: HttpError) { - assertEquals(error.name, "CustomErrorName"); - assertEquals(error.message, "A very specific error occurred."); - assertEquals(error.status, 418); - assertEquals(error.statusText, "I'm a teapot"); - assertEquals(error.expose, false); - assertEquals(error.cause, cause); - assertEquals(error.type, "/errors/custom-operation-failed"); - assertEquals( - error.instance, - "/errors/custom-operation-failed/instance/123xyz", - ); - assertEquals(error.extensions, extensionsData); - assertEquals(error.headers.get("X-Custom-Header"), "TestValue"); - assertEquals(error.headers.get("content-type"), "application/custom+json"); - assertEquals( - error.toString(), - "CustomErrorName: A very specific error occurred.", - ); - } - - const allOpts: HttpErrorOptions = { - name: "CustomErrorName", - message: "A very specific error occurred.", - status: 418, - statusText: "I'm a teapot", - expose: false, - cause, - type: "/errors/custom-operation-failed", - instance: "/errors/custom-operation-failed/instance/123xyz", - extensions: extensionsData, - headers: customHeaders, - }; - assertAllOptions(new HttpError(418, allOpts)); -}); - -it(withAllOptionsTests, "constructor with message, options signature", () => { - const cause = new Error("fail"); - const extensionsData = { key: "value" }; - const customHeaders = new Headers({ "X-Custom-Header": "TestValue" }); - customHeaders.set("content-type", "application/custom+json"); - - function assertAllOptions(error: HttpError) { - assertEquals(error.name, "CustomErrorName"); - assertEquals(error.message, "A very specific error occurred."); - assertEquals(error.status, 418); - assertEquals(error.statusText, "I'm a teapot"); - assertEquals(error.expose, false); - assertEquals(error.cause, cause); - assertEquals(error.type, "/errors/custom-operation-failed"); - assertEquals( - error.instance, - "/errors/custom-operation-failed/instance/123xyz", - ); - assertEquals(error.extensions, extensionsData); - assertEquals(error.headers.get("X-Custom-Header"), "TestValue"); - assertEquals(error.headers.get("content-type"), "application/custom+json"); - assertEquals( - error.toString(), - "CustomErrorName: A very specific error occurred.", - ); - } - const allOpts: HttpErrorOptions = { - name: "CustomErrorName", - message: "A very specific error occurred.", - status: 418, - statusText: "I'm a teapot", - expose: false, - cause, - type: "/errors/custom-operation-failed", - instance: "/errors/custom-operation-failed/instance/123xyz", - extensions: extensionsData, - headers: customHeaders, - }; - - const optsForMessageFirst = { ...allOpts }; - delete optsForMessageFirst.message; - assertAllOptions( - new HttpError("A very specific error occurred.", { - ...optsForMessageFirst, - status: 418, - }), - ); -}); - -it(withAllOptionsTests, "constructor with options signature", () => { - const cause = new Error("fail"); - const extensionsData = { key: "value" }; - const customHeaders = new Headers({ "X-Custom-Header": "TestValue" }); - customHeaders.set("content-type", "application/custom+json"); - - function assertAllOptions(error: HttpError) { - assertEquals(error.name, "CustomErrorName"); - assertEquals(error.message, "A very specific error occurred."); - assertEquals(error.status, 418); - assertEquals(error.statusText, "I'm a teapot"); - assertEquals(error.expose, false); - assertEquals(error.cause, cause); - assertEquals(error.type, "/errors/custom-operation-failed"); - assertEquals( - error.instance, - "/errors/custom-operation-failed/instance/123xyz", - ); - assertEquals(error.extensions, extensionsData); - assertEquals(error.headers.get("X-Custom-Header"), "TestValue"); - assertEquals(error.headers.get("content-type"), "application/custom+json"); - assertEquals( - error.toString(), - "CustomErrorName: A very specific error occurred.", - ); - } - const allOpts: HttpErrorOptions = { - name: "CustomErrorName", - message: "A very specific error occurred.", - status: 418, - statusText: "I'm a teapot", - expose: false, - cause, - type: "/errors/custom-operation-failed", - instance: "/errors/custom-operation-failed/instance/123xyz", - extensions: extensionsData, - headers: customHeaders, - }; - assertAllOptions(new HttpError(allOpts)); -}); - -it(httpErrorTests, "with headers as object literal", () => { - const error = new HttpError({ - headers: { - "X-Custom-Test": "Value", - "X-Another-Header": "AnotherValue", - }, - }); - assertEquals(error.headers.get("X-Custom-Test"), "Value"); - assertEquals(error.headers.get("X-Another-Header"), "AnotherValue"); - assertEquals( - error.headers.get("content-type"), - "application/problem+json", - ); - - const errorWithContentType = new HttpError({ - headers: { - "X-Custom-Test": "Value", - "content-type": "application/custom+json", - }, - }); - assertEquals(errorWithContentType.headers.get("X-Custom-Test"), "Value"); - assertEquals( - errorWithContentType.headers.get("content-type"), - "application/custom+json", - ); -}); - -it(httpErrorTests, "with other data", () => { - const cause = new Error("fail"); - const data = { x: 2, y: 3 }; - function assertAllOptions(error: HttpError) { - assertEquals(error.toString(), "CustomError: something went wrong"); - assertEquals(error.name, "CustomError"); - assertEquals(error.message, "something went wrong"); - assertEquals(error.status, 400); - assertEquals(error.expose, false); - assertEquals(error.cause, cause); - assertEquals(error.extensions, data); - } - assertAllOptions( - new HttpError(400, "something went wrong", { - name: "CustomError", - expose: false, - cause, - extensions: data, - }), - ); - assertAllOptions( - new HttpError(400, { - name: "CustomError", - message: "something went wrong", - expose: false, - cause, - extensions: data, - }), - ); - assertAllOptions( - new HttpError("something went wrong", { - name: "CustomError", - status: 400, - expose: false, - cause, - extensions: data, - }), - ); - assertAllOptions( - new HttpError({ - name: "CustomError", - message: "something went wrong", - status: 400, - expose: false, - cause, - extensions: data, - }), - ); -}); - -const jsonTests = describe(httpErrorTests, "json"); - -it(jsonTests, "HttpError.from(Error).toJSON()", () => { - const cause = new Error("fail"); - const errorFromCause = HttpError.from(cause); - assertEquals( - errorFromCause.toJSON(), - { - title: "Internal Server Error", - status: 500, - detail: "The server encountered an unexpected condition.", - }, - ); -}); - -it(jsonTests, "Exposed client error with extensions toJSON()", () => { - const cause = new Error("fail"); - const data = { x: 2, y: 3 }; - const clientErrorWithOptions = new HttpError( - 400, - "something went wrong", - { - name: "CustomError", - cause, - extensions: data, - }, - ); - assertEquals( - clientErrorWithOptions.toJSON(), - { - ...data, - title: "CustomError", - detail: "something went wrong", - status: 400, - }, - ); -}); - -it(jsonTests, "Non-exposed server error with extensions toJSON()", () => { - const cause = new Error("fail"); - const data = { x: 2, y: 3 }; - const serverErrorWithOptions = new HttpError( - 500, - "something went wrong", - { - name: "CustomError", - cause, - extensions: data, - }, - ); - assertEquals( - serverErrorWithOptions.toJSON(), - { - ...data, - title: "CustomError", - status: 500, - detail: "The server encountered an unexpected condition.", - }, - ); -}); - -it( - jsonTests, - "Client error, explicitly not exposed, with extensions toJSON()", - () => { - const cause = new Error("fail"); - const data = { x: 2, y: 3 }; - const clientErrorNotExposed = new HttpError( - 400, - "something went wrong", - { - name: "CustomError", - expose: false, - cause, - extensions: data, - }, - ); - assertEquals( - clientErrorNotExposed.toJSON(), - { - ...data, - title: "CustomError", - status: 400, - detail: "The server cannot process the request due to a client error.", - }, - ); - }, -); - -it(jsonTests, "exposedMessage with expose=true uses message", () => { - const error = new HttpError(400, "Invalid email format"); - assertEquals(error.message, "Invalid email format"); - assertEquals(error.exposedMessage, "Invalid email format"); - assertEquals(error.toJSON().detail, "Invalid email format"); -}); - -it(jsonTests, "exposedMessage with expose=false uses default", () => { - const error = new HttpError(500, "SQL syntax error near 'DROP TABLE'"); - assertEquals( - error.exposedMessage, - "The server encountered an unexpected condition.", - ); - assertEquals( - error.toJSON().detail, - "The server encountered an unexpected condition.", - ); -}); - -it(jsonTests, "explicit exposedMessage takes priority", () => { - // Even with expose=true, explicit exposedMessage is used - const error1 = new HttpError(400, "Internal validation details", { - exposedMessage: "Please check your input.", - }); - assertEquals(error1.exposedMessage, "Please check your input."); - assertEquals(error1.toJSON().detail, "Please check your input."); - - // With expose=false, explicit exposedMessage is also used - const error2 = new HttpError(500, "Database connection refused", { - exposedMessage: "Service temporarily unavailable.", - }); - assertEquals(error2.exposedMessage, "Service temporarily unavailable."); - assertEquals(error2.toJSON().detail, "Service temporarily unavailable."); -}); - -const getResponseTests = describe(httpErrorTests, "getResponse"); - -it( - getResponseTests, - "Client error, exposed, with extensions and custom type/instance", - async () => { - const error1Extensions = { extra: "data1" }; - const error1 = new HttpError(400, "Client Issue", { - name: "MyClientError", - extensions: error1Extensions, - type: "/err/client", - instance: "/err/client/1", - }); - const response1 = error1.getResponse(); - assertEquals(response1.status, 400); - assertEquals( - response1.headers.get("content-type"), - "application/problem+json", - ); - const body1 = await response1.json(); - assertEquals(body1, { - ...error1Extensions, - status: 400, - title: "MyClientError", - detail: "Client Issue", - type: "/err/client", - instance: "/err/client/1", - }); - }, -); - -it( - getResponseTests, - "Server error, not exposed, with custom statusText and headers", - async () => { - const error2Extensions = { extra: "data2" }; - const customHeaders = new Headers({ "X-RateLimit-Limit": "100" }); - customHeaders.set("content-type", "application/vnd.api+json"); - - const error2 = new HttpError(503, "Server Down", { - name: "CustomServerErr", - statusText: "Service Really Unavailable", - expose: false, - extensions: error2Extensions, - headers: customHeaders, - }); - const response2 = error2.getResponse(); - assertEquals(response2.status, 503); - if (response2.statusText === "Service Really Unavailable") { - assertEquals(response2.statusText, "Service Really Unavailable"); - } - assertEquals( - response2.headers.get("content-type"), - "application/vnd.api+json", - ); - assertEquals(response2.headers.get("X-RateLimit-Limit"), "100"); - const body2 = await response2.json(); - assertEquals(body2, { - ...error2Extensions, - status: 503, - title: "CustomServerErr", - detail: "The server is currently unavailable.", - }); - }, -); - -it( - getResponseTests, - "Default headers (content-type) - No extensions", - async () => { - const error3 = new HttpError(404, "Not Found"); - const response3 = error3.getResponse(); - assertEquals( - response3.headers.get("content-type"), - "application/problem+json", - ); - const body3 = await response3.json(); - assertEquals(body3, { - status: 404, - title: "Not Found", - detail: "Not Found", - }); - }, -); - -const fromTests = describe(httpErrorTests, "from"); - -it(fromTests, "non HttpError", () => { - const cause = new Error("fail"); - const error = HttpError.from(cause); - assertEquals(error.toString(), "Internal Server Error: fail"); - assertEquals(error.name, "Internal Server Error"); - assertEquals(error.message, "fail"); - assertEquals(error.status, 500); - assertEquals(error.expose, false); - assertEquals(error.cause, cause); - assertEquals( - error.exposedMessage, - "The server encountered an unexpected condition.", - ); -}); - -it(fromTests, "Error with status", () => { - const originalError = new Error("fail"); - (originalError as HttpError).status = 400; - const error = HttpError.from(originalError); - assertEquals(error.toString(), "Error: fail"); - assertEquals(error.name, "Error"); - assertEquals(error.message, "fail"); - assertEquals(error.status, 400); - assertEquals(error.expose, true); - assertEquals(error.cause, undefined); -}); - -it(fromTests, "passthrough HttpError instances", () => { - const cause = new Error("fail"); - const originalError = new HttpError({ - name: "CustomError", - message: "something went wrong", - cause, - extensions: { x: 2, y: 3 }, - }); - const error = HttpError.from(originalError); - assertStrictEquals(error, originalError); -}); - -it(fromTests, "with various unexpected input types", () => { - const testInputs: unknown[] = [ - null, - undefined, - 12345, - true, - Symbol("test-symbol"), - { completely: "unrelated object" }, - () => {}, - ]; - - for (const inputValue of testInputs) { - const error = HttpError.from(inputValue); - assert( - error instanceof HttpError, - `Expected HttpError for input: ${String(inputValue)}`, - ); - assertEquals(error.status, 500, `Status for input: ${String(inputValue)}`); - assertEquals( - error.message, - "unexpected error type", - `Message for input: ${String(inputValue)}`, - ); - assertStrictEquals( - error.cause, - inputValue, - `Cause for input: ${String(inputValue)}`, - ); - } -}); - -it(fromTests, "empty object", () => { - const emptyObjError = HttpError.from({}); - assert(emptyObjError instanceof HttpError); - assertEquals(emptyObjError.status, 500); - assertEquals(emptyObjError.message, "unexpected error type"); - assertEquals(emptyObjError.cause, {}); -}); - -const fromResponseTests = describe(fromTests, "Response"); - -it(fromResponseTests, "with ProblemDetails JSON", async () => { - const problemDetails = { - status: 403, - title: "ForbiddenError", - detail: "Access denied", - type: "/errors/forbidden", - instance: "/errors/forbidden/123", - customField: "customValue", - }; - const response = new Response(JSON.stringify(problemDetails), { - status: 403, - headers: { "Content-Type": "application/problem+json" }, - }); - const error = await HttpError.from(response); - assertEquals(error.status, 403); - assertEquals(error.name, "ForbiddenError"); - assertEquals(error.message, "Access denied"); - assertEquals(error.type, "/errors/forbidden"); - assertEquals(error.instance, "/errors/forbidden/123"); - assertEquals(error.extensions, { customField: "customValue" }); - assertEquals(error.expose, true); -}); - -it( - fromResponseTests, - "with status in JSON preferred over response status", - async () => { - const problemDetails = { - status: 403, - title: "ForbiddenError", - detail: "Access denied", - }; - const response = new Response(JSON.stringify(problemDetails), { - status: 400, - headers: { "Content-Type": "application/problem+json" }, - }); - const error = await HttpError.from(response); - assertEquals(error.status, 403); - assertEquals(error.name, "ForbiddenError"); - assertEquals(error.message, "Access denied"); - }, -); - -it( - fromResponseTests, - "without status in JSON uses response status", - async () => { - const problemDetails = { - title: "BadRequest", - detail: "Bad input", - }; - const response = new Response(JSON.stringify(problemDetails), { - status: 400, - headers: { "Content-Type": "application/problem+json" }, - }); - const error = await HttpError.from(response); - assertEquals(error.status, 400); - assertEquals(error.name, "BadRequest"); - assertEquals(error.message, "Bad input"); - }, -); - -it(fromResponseTests, "with invalid JSON", async () => { - const response = new Response("not json {{{{", { status: 500 }); - const error = await HttpError.from(response); - assertEquals(error.status, 500); - assertEquals(error.message, "could not parse problem details response"); - assert(error.cause instanceof SyntaxError); - assertEquals( - error.cause.message, - `Unexpected token 'o', "not json {{{{" is not valid JSON`, - ); -}); - -it( - fromResponseTests, - "with valid JSON that is not ProblemDetails", - async () => { - const nonProblemDetailsPayload = { data: "some other structure" }; - const response = new Response(JSON.stringify(nonProblemDetailsPayload), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - const error = await HttpError.from(response); - assertEquals(error.status, 500); - assertEquals(error.message, "invalid problem details response"); - assert(error.cause, "Cause should be defined"); - assertEquals(error.cause, nonProblemDetailsPayload); - }, -); - -it(fromTests, "from ProblemDetails object", () => { - const problemDetails = { - status: 404, - title: "ResourceNotFound", - detail: "The requested resource was not found.", - type: "/errors/not-found", - instance: "/items/123", - anotherKey: "anotherValue", - }; - type MyExt = { anotherKey: string }; - const error = HttpError.from(problemDetails); - assertEquals(error.status, 404); - assertEquals(error.name, "ResourceNotFound"); - assertEquals(error.message, "The requested resource was not found."); - assertEquals(error.type, "/errors/not-found"); - assertEquals(error.instance, "/items/123"); - assertEquals(error.extensions, { anotherKey: "anotherValue" }); - assertEquals(error.expose, true); -}); - -const createHttpErrorClassTests = describe("createHttpErrorClass"); - -it( - createHttpErrorClassTests, - "creates a basic custom error class without default options", - () => { - const BasicError = createHttpErrorClass(); - const error = new BasicError(400, "test message"); - assert(error instanceof HttpError, "Should be instance of HttpError"); - assert(error instanceof BasicError, "Should be instance of BasicError"); - assertEquals(error.status, 400); - assertEquals(error.message, "test message"); - assertEquals(error.name, "Bad Request"); - - const error2 = new BasicError(); - assertEquals(error2.status, 500); - assertEquals(error2.message, ""); - assertEquals(error2.name, "Internal Server Error"); - }, -); - -it( - createHttpErrorClassTests, - "creates a custom error class with default name and status", - () => { - const DefaultNameStatusError = createHttpErrorClass({ - name: "MyTestError", - status: 499, - }); - - const error1 = new DefaultNameStatusError("A message"); - assertEquals(error1.status, 499); - assertEquals(error1.name, "MyTestError"); - assertEquals(error1.message, "A message"); - - const error2 = new DefaultNameStatusError(498, "Override status"); - assertEquals(error2.status, 498); - assertEquals(error2.name, "MyTestError"); - assertEquals(error2.message, "Override status"); - - const error3 = new DefaultNameStatusError({ - message: "Via options", - name: "OverriddenName", - }); - assertEquals(error3.status, 499); - assertEquals(error3.name, "OverriddenName"); - assertEquals(error3.message, "Via options"); - - const error4 = new DefaultNameStatusError({ status: 497 }); - assertEquals(error4.status, 497); - assertEquals(error4.name, "MyTestError"); - assertEquals(error4.message, ""); - }, -); - -it( - createHttpErrorClassTests, - "creates a custom error class with default extensions", - () => { - interface MyExt { - defaultKey: string; - anotherKey?: number; - yetAnotherKey?: string; - } - const ExtError = createHttpErrorClass({ - extensions: { defaultKey: "defaultValue", yetAnotherKey: "stillHere" }, - }); - - const error1 = new ExtError("msg"); - assertEquals(error1.extensions.defaultKey, "defaultValue"); - assertEquals(error1.extensions.yetAnotherKey, "stillHere"); - assertEquals(error1.extensions.anotherKey, undefined); - - const error2 = new ExtError("msg", { - extensions: { defaultKey: "override", anotherKey: 123 }, - }); - assertEquals(error2.extensions.defaultKey, "override"); - assertEquals(error2.extensions.anotherKey, 123); - assertEquals((error2.extensions as MyExt).yetAnotherKey, "stillHere"); - - const error3 = new ExtError({ - extensions: { - anotherKey: 456, - defaultKey: "defaultValue", - yetAnotherKey: "stillHere", - }, - }); - assertEquals(error3.extensions.defaultKey, "defaultValue"); - assertEquals(error3.extensions.anotherKey, 456); - assertEquals(error3.extensions.yetAnotherKey, "stillHere"); - assertEquals(error3.message, ""); - assertEquals(error3.status, 500); - - const ErrorWithDefaultStatus = createHttpErrorClass({ - status: 420, - extensions: { defaultKey: "val" }, - }); - const error4 = new ErrorWithDefaultStatus(); - assertEquals(error4.status, 420); - assertEquals((error4.extensions as MyExt).defaultKey, "val"); - }, -); - -const allDefaultsAndOverridesTests = describe( - createHttpErrorClassTests, - "custom error class with all possible default options and allows overrides", -); - -interface AllExt { - id: string; - type?: string; - newProp?: string; -} -const defaultHeaders = new Headers({ "X-Default": "true" }); -const defaultCause = new Error("Default cause"); - -const AllDefaultsError = createHttpErrorClass({ - name: "AllDefaultsErrorName", - status: 488, - message: "Default message for error", - expose: true, - statusText: "Default Status Text Here", - type: "/errors/all-defaults-type", - instance: "/errors/all-defaults-instance/0", - extensions: { id: "default-id", type: "default-ext-type" }, - headers: defaultHeaders, - cause: defaultCause, -}); - -it( - allDefaultsAndOverridesTests, - "instantiates with no overrides, checking all defaults", - () => { - const error1 = new AllDefaultsError(); - assertEquals(error1.name, "AllDefaultsErrorName"); - assertEquals(error1.status, 488); - assertEquals(error1.message, "Default message for error"); - assertEquals(error1.expose, true); - assertEquals(error1.statusText, "Default Status Text Here"); - assertEquals(error1.type, "/errors/all-defaults-type"); - assertEquals(error1.instance, "/errors/all-defaults-instance/0"); - assertEquals(error1.extensions.id, "default-id"); - assertEquals(error1.extensions.type, "default-ext-type"); - assertEquals(error1.headers.get("X-Default"), "true"); - assertEquals(error1.cause, defaultCause); - }, -); - -it( - allDefaultsAndOverridesTests, - "instantiates with status and message args overriding defaults", - () => { - const error2all = new AllDefaultsError(489, "New message from args"); - assertEquals(error2all.status, 489); - assertEquals(error2all.message, "New message from args"); - assertEquals(error2all.name, "AllDefaultsErrorName"); - assertEquals((error2all.extensions as AllExt).id, "default-id"); - }, -); - -it( - allDefaultsAndOverridesTests, - "instantiates with options object overriding some defaults", - () => { - const instanceHeaders = new Headers({ "X-Instance": "live" }); - const instanceCause = new Error("Instance cause here"); - const error3all = new AllDefaultsError({ - name: "InstanceNameOverride", - message: "Instance message in options", - expose: false, - extensions: { id: "instance-id-override", newProp: "instanceValue" }, - headers: instanceHeaders, - cause: instanceCause, - type: "/errors/instance-type-override", - }); - assertEquals(error3all.name, "InstanceNameOverride"); - assertEquals(error3all.status, 488); - assertEquals(error3all.message, "Instance message in options"); - assertEquals(error3all.expose, false); - assertEquals(error3all.statusText, "Default Status Text Here"); - assertEquals(error3all.type, "/errors/instance-type-override"); - assertEquals(error3all.instance, "/errors/all-defaults-instance/0"); - assertEquals(error3all.extensions.id, "instance-id-override"); - assertEquals((error3all.extensions as AllExt).type, "default-ext-type"); - assertEquals(error3all.extensions.newProp, "instanceValue"); - assertEquals(error3all.headers.get("X-Instance"), "live"); - assertEquals(error3all.headers.has("X-Default"), false); - assertEquals(error3all.cause, instanceCause); - }, -); - -const statusTextOverridesTests = describe( - allDefaultsAndOverridesTests, - "statusText specific overrides", -); - -it( - statusTextOverridesTests, - "prioritizes statusText in constructor options", - () => { - const errorWithStatusTextInOptions = new AllDefaultsError({ - statusText: "Overridden Status Text Via Options", - }); - assertEquals( - errorWithStatusTextInOptions.statusText, - "Overridden Status Text Via Options", - ); - assertEquals(errorWithStatusTextInOptions.status, 488); - assertEquals(errorWithStatusTextInOptions.name, "AllDefaultsErrorName"); - }, -); - -it( - statusTextOverridesTests, - "prioritizes statusText in options when status arg is present", - () => { - const errorWithStatusTextAndStatusArg = new AllDefaultsError(490, { - statusText: "Custom StatusText with Status Arg", - }); - assertEquals(errorWithStatusTextAndStatusArg.status, 490); - assertEquals( - errorWithStatusTextAndStatusArg.statusText, - "Custom StatusText with Status Arg", - ); - }, -); - -it( - statusTextOverridesTests, - "prioritizes statusText in options when message arg is present", - () => { - const errorWithStatusTextAndMessageArg = new AllDefaultsError("A message", { - statusText: "Custom StatusText with Message Arg", - }); - assertEquals(errorWithStatusTextAndMessageArg.message, "A message"); - assertEquals( - errorWithStatusTextAndMessageArg.statusText, - "Custom StatusText with Message Arg", - ); - assertEquals(errorWithStatusTextAndMessageArg.status, 488); - }, -); - -it( - statusTextOverridesTests, - "prioritizes statusText in options when status and message args are present", - () => { - const errorWithStatusTextAndFullArgs = new AllDefaultsError( - 491, - "A full message", - { statusText: "Custom StatusText with Full Args" }, - ); - assertEquals(errorWithStatusTextAndFullArgs.status, 491); - assertEquals(errorWithStatusTextAndFullArgs.message, "A full message"); - assertEquals( - errorWithStatusTextAndFullArgs.statusText, - "Custom StatusText with Full Args", - ); - }, -); - -it( - statusTextOverridesTests, - "uses class default statusText if not in constructor options", - () => { - const errorUsingDefaultStatusText = new AllDefaultsError({ - message: "Message without status text override", - }); - assertEquals( - errorUsingDefaultStatusText.statusText, - "Default Status Text Here", - ); - const error1 = new AllDefaultsError(); - assertEquals(error1.statusText, "Default Status Text Here"); - }, -); - -it( - createHttpErrorClassTests, - "custom error class instances behave like HttpError (toJSON, getResponse)", - async () => { - interface RespExt { - code: number; - field?: string; - defaultInfo?: string; - } - const RespError = createHttpErrorClass({ - name: "RespTestError", - status: 422, - extensions: { code: 100, defaultInfo: "from_default" }, - expose: true, - }); - - const error = new RespError("Validation failed", { - extensions: { code: 101, field: "email" }, - type: "/errors/validation", - instance: "/user/123/email", - }); - - const json = error.toJSON(); - assertEquals(json, { - status: 422, - title: "RespTestError", - detail: "Validation failed", - type: "/errors/validation", - instance: "/user/123/email", - ...error.extensions, - }); - - const response = error.getResponse(); - assertEquals(response.status, 422); - assertEquals( - response.headers.get("content-type"), - "application/problem+json", - ); - const body = await response.json(); - assertEquals(body, json); - - const NonExposedError = createHttpErrorClass({ - name: "SecretError", - status: 500, - expose: false, - }); - const secretError = new NonExposedError("This is a secret detail", { - extensions: { secretCode: "xyz" }, - }); - const secretJson = secretError.toJSON(); - assertEquals(secretJson, { - status: 500, - title: "SecretError", - detail: "The server encountered an unexpected condition.", - ...secretError.extensions, - }); - - const secretResponse = secretError.getResponse(); - const secretBody = await secretResponse.json(); - assertEquals(secretBody, secretJson); - }, -); - -it("isHttpErrorLike", () => { - const httpError = new HttpError(400, "Bad Request"); - assertEquals(isHttpErrorLike(httpError), true); - assertEquals(isHttpErrorLike(new Error("Bad Request")), false); - assertEquals(isHttpErrorLike({ status: 400, message: "Bad Request" }), false); - const error = new Error("Bad Request"); - assertEquals(isHttpErrorLike(error), false); - (error as HttpError).status = 400; - assertEquals(isHttpErrorLike(error), true); - if (isHttpErrorLike(error)) { - assertEquals(error.status, 400); - } -}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..97da415 --- /dev/null +++ b/package.json @@ -0,0 +1,74 @@ +{ + "name": "@udibo/http-error", + "version": "0.11.1", + "description": "Utilities for creating and working with HTTP errors that produce RFC 9457 Problem Details responses.", + "keywords": [ + "error", + "hono", + "http", + "problem-details", + "rfc9457" + ], + "homepage": "https://github.com/udibo/http-error#readme", + "bugs": { + "url": "https://github.com/udibo/http-error/issues" + }, + "license": "MIT", + "author": "Udibo", + "repository": { + "type": "git", + "url": "git+https://github.com/udibo/http-error.git" + }, + "files": [ + "dist", + "!dist/**/*.test.*", + "README.md", + "LICENSE" + ], + "type": "module", + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/mod.d.ts", + "default": "./dist/mod.js" + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "scripts": { + "build": "tsc -p tsconfig.build.json", + "check": "pnpm lint && pnpm fmt:check && tsc --noEmit", + "fmt": "oxfmt", + "fmt:check": "oxfmt --check", + "lint": "oxlint --type-aware --type-check", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "smoke": "node scripts/npm-smoke.ts", + "jsr:dry-run": "jsr publish --dry-run" + }, + "devDependencies": { + "@sebbo2002/semantic-release-jsr": "3.2.1", + "@semantic-release/exec": "7.1.0", + "@semantic-release/git": "10.0.1", + "@semantic-release/github": "12.0.8", + "@semantic-release/npm": "13.1.5", + "@types/node": "^26.0.0", + "@vitest/coverage-v8": "^5.0.3", + "conventional-changelog-conventionalcommits": "9.3.1", + "hono": "^4.13.13", + "jsr": "0.14.3", + "oxfmt": "^0.72.0", + "oxlint": "^1.87.0", + "oxlint-tsgolint": "^7.0.2003", + "semantic-release": "25.0.5", + "typescript": "^7.0.2", + "vitest": "^5.0.3" + }, + "engines": { + "node": ">=22" + }, + "packageManager": "pnpm@12.10.1" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..dc35ee2 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4118 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.10.1 + version: 12.10.1 + +packages: + + '@pnpm/exe.android-arm64@12.10.1': + resolution: {integrity: sha512-CVTlHPwNvadQCJD9bGSMzWIhFX8QpjtqC2c27dhdnn/9iKzEjZ3qL62o3gHpKbIuo6qn4WxTKRoaokgNdReBig==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.10.1': + resolution: {integrity: sha512-NFiYLIaodY7da0XIebwcU3JoP7IK/lzF7zgKSSP+9H+QcFnQ1GDfBkuBKCYJzZ+Xu0WH54neIPLBBCaOjaaauA==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.10.1': + resolution: {integrity: sha512-0O8tdQ8ijlPJudXspLMTcitMkdBEz6uUflazL5uQE0WMs85oQIp0U+5hfEktMFB64QqEhjqlvvujTwbzdr9oMg==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.10.1': + resolution: {integrity: sha512-ud+1/uZOKEo8P/AsPynOJc/LNwfI0j6WwCElibj71rU9l4nIBnsSn+wWTIPZPkZ4BBSH5+82azSnAARMyJ1lNA==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.10.1': + resolution: {integrity: sha512-mj9PE5rf8uAfQQ2K66HSATMSut+lNiB4eSqFd5+azFVCMnxNSy/32xpZQJT21PhnN3Y4qbK+iKHe5nWAxSuRhw==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.10.1': + resolution: {integrity: sha512-xba+00wZJ8Um6rBJJjWl88uJj0tydB8dtomvjlCLvi0uL32vpa9CRP8Vv26FIXvbiZ1bo2RCzYt/eChxJVV52w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.10.1': + resolution: {integrity: sha512-oBdMp69ScC9UnniG4qZAQVr2fBDSXjm4yk/72vkEVyrlwS23yMxcRQl7Qdo3sgXPAygY6TWnPRKm4rXzjoyW7w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.10.1': + resolution: {integrity: sha512-Yyb3wu09h3c7mLme270uHFiVaAIUHC411Ru9LIU2BKUIstrGlXa9OqqVWoXaRgYRdFdOFknkwG7GNn6ygwLbnw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.10.1': + resolution: {integrity: sha512-PdkeUFFkybknnGkWP0H22eMpIiS4OZXL5BcQzF4lh+vmrW3IaC2HOkbANKgot/PKO0Kl77pzA/6PKFxgbgfXIQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.10.1': + resolution: {integrity: sha512-1okKuHKJwJPAbofDxfhZWAUxEWtee4ZSf7Pah8kcvocaJkf2WxFJ5KVTiNIvDml5GLidDQsgf7Pcf/WHbCak7Q==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.10.1': + resolution: {integrity: sha512-e6IqzVNkPa7srFKJKnY23uWJ+qz0LaRWiTUo/sHfFXRRMRzB5uNOr6qWWip/jqhdGwU91n9J7LrEYZ6e0tRczw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.10.1': + resolution: {integrity: sha512-ZByV0DRgWzGaDSjT5ddRBASdwz+r/qPMYh7o6RGmPQjtRGNFbSl94xY2DcAM8mqCOZJUUDtAXQ+wpBXcdpKlkg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.10.1': + resolution: {integrity: sha512-I7QZHcjioG7orFeCRUFUHByJ1G0zFPnAlaGGyrUvb6MGkv5oeNwNPl7t+Gp+7zHfnMcgLGJ1a+Wfim0IDw3lOw==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.10.1': + resolution: {integrity: sha512-4WJFdp58EKlCj0zLobrXU0vpszSZIr4X/3y/uNbAQqGW0zE+VdruZB0gFc3L2kzsUzVwTYbaCn+XEhnCYSAlTg==} + cpu: [x64] + os: [win32] + + pnpm@12.10.1: + resolution: {integrity: sha512-ukCjfrGjcNYP6oxM8dNkwTvMzvUY6YrSvO6VT1OdONo3RY5YJho/pV4GUjRQ6g//+fSXsTW+QfLBXBZCusrObA==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.10.1': + optional: true + + '@pnpm/exe.android-x64@12.10.1': + optional: true + + '@pnpm/exe.darwin-arm64@12.10.1': + optional: true + + '@pnpm/exe.darwin-x64@12.10.1': + optional: true + + '@pnpm/exe.freebsd-x64@12.10.1': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.10.1': + optional: true + + '@pnpm/exe.linux-arm64@12.10.1': + optional: true + + '@pnpm/exe.linux-ppc64@12.10.1': + optional: true + + '@pnpm/exe.linux-riscv64@12.10.1': + optional: true + + '@pnpm/exe.linux-s390x@12.10.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.10.1': + optional: true + + '@pnpm/exe.linux-x64@12.10.1': + optional: true + + '@pnpm/exe.win32-arm64@12.10.1': + optional: true + + '@pnpm/exe.win32-x64@12.10.1': + optional: true + + pnpm@12.10.1: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.10.1 + '@pnpm/exe.android-x64': 12.10.1 + '@pnpm/exe.darwin-arm64': 12.10.1 + '@pnpm/exe.darwin-x64': 12.10.1 + '@pnpm/exe.freebsd-x64': 12.10.1 + '@pnpm/exe.linux-arm64': 12.10.1 + '@pnpm/exe.linux-arm64-musl': 12.10.1 + '@pnpm/exe.linux-ppc64': 12.10.1 + '@pnpm/exe.linux-riscv64': 12.10.1 + '@pnpm/exe.linux-s390x': 12.10.1 + '@pnpm/exe.linux-x64': 12.10.1 + '@pnpm/exe.linux-x64-musl': 12.10.1 + '@pnpm/exe.win32-arm64': 12.10.1 + '@pnpm/exe.win32-x64': 12.10.1 + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@sebbo2002/semantic-release-jsr': + specifier: 3.2.1 + version: 3.2.1 + '@semantic-release/exec': + specifier: 7.1.0 + version: 7.1.0(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0) + '@semantic-release/git': + specifier: 10.0.1 + version: 10.0.1(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0) + '@semantic-release/github': + specifier: 12.0.8 + version: 12.0.8(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0) + '@semantic-release/npm': + specifier: 13.1.5 + version: 13.1.5(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2)) + '@types/node': + specifier: ^26.0.0 + version: 26.6.4 + '@vitest/coverage-v8': + specifier: ^5.0.3 + version: 5.0.3(vitest@5.0.3) + conventional-changelog-conventionalcommits: + specifier: 9.3.1 + version: 9.3.1 + hono: + specifier: ^4.13.13 + version: 4.13.13 + jsr: + specifier: 0.14.3 + version: 0.14.3 + oxfmt: + specifier: ^0.72.0 + version: 0.72.0 + oxlint: + specifier: ^1.87.0 + version: 1.87.0(oxlint-tsgolint@7.0.2003) + oxlint-tsgolint: + specifier: ^7.0.2003 + version: 7.0.2003 + semantic-release: + specifier: 25.0.5 + version: 25.0.5(supports-color@7.2.0)(typescript@7.0.2) + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vitest: + specifier: ^5.0.3 + version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.3(@types/node@26.6.4)) + +packages: + + '@actions/core@3.0.1': + resolution: {integrity: sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==} + + '@actions/exec@3.0.0': + resolution: {integrity: sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==} + + '@actions/http-client@4.0.1': + resolution: {integrity: sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==} + + '@actions/io@3.0.2': + resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==} + + '@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'} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@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==} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.8': + resolution: {integrity: sha512-L7y8eYc+AwxGr2PWI4WFt1VG4TiJ66c26BD16mXpYIlXxG0SMigM1+m4aTSlYyBr5BlQsGAlz8uDCoZN4SEMcg==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.5': + resolution: {integrity: sha512-iXa654H3yFafF/ieHkukfbgWo2rmXD2ceD0ZOtrPhw1bc3FDch1d9N/TNs0FQ1/cIbwb7kspUX8jzIs8nzb9DQ==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.5': + resolution: {integrity: sha512-bt/hm03LeU6Vy7FwTrkkC9p3XGT/lBwClglMqxBSe5/q0E5CdJTXeAqEI0vlw89/LF/G6tryTIH8HirZ3prMVg==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/openapi-types@28.0.0': + resolution: {integrity: sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==} + + '@octokit/openapi-types@29.0.1': + resolution: {integrity: sha512-9qWOMFNxxLokERcms42rU0PTLqQmVs7g5E41TI4mCOxmpFayD1rfC7XxOL55cG9MBZLFlC31BrR37myMKardwg==} + + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@8.1.1': + resolution: {integrity: sha512-VCVvZ/R1+u3WuiBWpNavZ0mY4aaJNAsENrpBP9aLSR2QyOpQgd7DhM5j4AW7z4MQpnJYgwBPf0XqPQoNBRdQwg==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=7' + + '@octokit/plugin-throttling@11.0.5': + resolution: {integrity: sha512-LIdrkrUv+DWbKeg/49rGuFJ3SU0d3hUS+B4MhNZLepBoNUFXms8Ic9edJjrlx+zycqJHjrMRudVpVb/bAXM2Lw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': ^7.0.0 + + '@octokit/request-error@7.1.2': + resolution: {integrity: sha512-XZRuT3xZ84D3gYErI1DZvhJ33dCWVV6uzBtWkaBB4TvA/L6eOeTZodxLFVB44bBEEo3vEx7y00UfX1tBLrtLRg==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.16': + resolution: {integrity: sha512-A0zWGjHzISIb+9ccG8s0dq7LKO5zVpJLRICjgUb+sJxEWqn8RUHB1rD3AE51+PECvXHIxqZ1VVvs4fHTSD9nUQ==} + engines: {node: '>= 20'} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + + '@octokit/types@17.0.0': + resolution: {integrity: sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==} + + '@octokit/types@18.0.0': + resolution: {integrity: sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==} + + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + + '@oxfmt/binding-android-arm-eabi@0.72.0': + resolution: {integrity: sha512-u4U1uaDOVBuorGNC/07NLm48nECHOiJJka002YGML16Md2DRvf38RGBt01lH60EOGvNlFVmbLoksQedNBoZHKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.72.0': + resolution: {integrity: sha512-GCnM+Ae2mWOKC+92y3FOb6eZJeVnBAVz82e3lITUUg+kxukYKzBNAvJ/hhHIfa2A0md55SYYhdWPddSSy3KPVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.72.0': + resolution: {integrity: sha512-LjQ8tlevdtwraFlWC6AnQ1Forzde0clRmnsP3QmK+ydSElDt/J+5Jc+m+//Ly1G97aP7WCNUw110QMIUw2K7xA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.72.0': + resolution: {integrity: sha512-+ZbcxhB1C34RQR5IldSyY6Z/yalJIxHXhP36PGE4sEq0f/lgL5jCLmOKoDeVm8dqd9gFGDsd1bNWHSGCAwdSkw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.72.0': + resolution: {integrity: sha512-yGPhA8sePEBFV9iGcprhyOk3Bz3fZshswMH3DpYIPZFLT1DG5LjiZ0j6AJQpSueaL6bUK0av1K+5+fe+ldy5EQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': + resolution: {integrity: sha512-/g5axuqFMcAtoQrFLR5EJEMDjR5Ea4GuzpdDmMpPWAgrF1DmL4kD1NPNVlpXEOxpFJnD6lPvLKYj1lzdlrfifA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': + resolution: {integrity: sha512-InVitdHbS09PM+w+WinIiyX37rC9R3L7lUP9mKy9uKTEDSUFaLjWsLoreRIxKivnv7Ndqc6Trvi+K+b6ZzhiJg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.72.0': + resolution: {integrity: sha512-WKjlyRAxpPjXoqeuwFBim5LscgvY+RNEiZ971C9JlRQZONhJTsXDki/oHFYQE+fzzRpzXh3owZvOdT3kNL2g+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.72.0': + resolution: {integrity: sha512-poafnbsPpJhR+5Mbwpf8CscugTyiRAzjTOxRVn8ra3X2jnURs2xL5Du2xl5g2iW85lICMglpmLf8jDjZEbNgzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': + resolution: {integrity: sha512-zNUi0gKRsO7S3IfPzsTbnm88OedPuX32hmZFWZRWHvWJlAoZRwVbeO6Nb3hng4vDgL7qUKp4TGqzPz1mhsycYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': + resolution: {integrity: sha512-qTY7z/iyF/rWI2HWwWMevXqZW5PZUOofUy2afK7v6rseXnpld3yjgyIk6pCux3DqWQ0lB+f5atvLJNBwzLvFsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.72.0': + resolution: {integrity: sha512-w18qfo8vEMD6EKEHtzTvjc6jH8yCyXIT1Bn7PFWJd0PpZyzH/VQDx4xvjCh0LGz6GRzqiezbisTdG9AoXtpkZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.72.0': + resolution: {integrity: sha512-grW8HwEir+vK7fCcq1ZgL3NHQCfZ1PidiERE9Hbf+24xGys+DoDaoqTht4ccfn0PcBwbViLkwkda/KNpJu2KRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.72.0': + resolution: {integrity: sha512-wQJRQfWBRIn88Pkr449EUViwRayrYUScg7ty+Ihiem21mQ9BUA6S9e/4RAgyNk9lMag0KBouyKskjq61rOC0WQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.72.0': + resolution: {integrity: sha512-T1B0uQzafxuGPjjaPrZJ497nxoi1BiI65JfsYc56nNpmev3msfb/iaTmH2kQuNFqa8PKxpMlf3GhWrWBvBPORQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.72.0': + resolution: {integrity: sha512-OKxxgTurl7+hypVplO8QRAbbQlwEs0j1Y6FZv+ZN2qNbdxQk2muMlzuJNBGTCWwxQ0rkkOD1uK/DW1fvzoUSJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.72.0': + resolution: {integrity: sha512-L78TKzURqvjuZOCo88ixMbPXf/RP7u31Y0cmjJ/KEmrHmfYhmhEvsDaTYPiyTRne64jUgCbADfY0YeoQk4/Xyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.72.0': + resolution: {integrity: sha512-gQtFW+Ii8BWgtJTAD17TY/WsdNS5gUfAPov8iI1jeq4r9lRRYImmxPL08UU00/RLaCfBJwh2UgINnDY0iOiZ+A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.72.0': + resolution: {integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2003': + resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@7.0.2003': + resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2003': + resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2003': + resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@7.0.2003': + resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.87.0': + resolution: {integrity: sha512-22tVIHlB9898OYsBKmYB6He1MCSqdTmK1BVWuSNUTVrpS0KWNege+U9uEYqGJ+KatQag0r/oU44i3OOs46z7HQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.87.0': + resolution: {integrity: sha512-JfZQ3MWf74n8ov0DojZpkOduIubYJvZL5tbTMMhDutsal6u1xH1QuNpN+lK+EQwF8AeAfgwTR2fgh5U2br27XA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.87.0': + resolution: {integrity: sha512-rkx6Xvvw93z2GImVOyvbZwAZcoY0Z6MJW177XNsSB+i/CBsH7ANyelTp01VZyIkM3iSXtqKVb4+ptBgPHap5pQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.87.0': + resolution: {integrity: sha512-cw5nYw7L/pQ2SgGBgOcg/Qo89llQre3vIc/kuBLhSICzu1r5s8L76H3ug6AIFsD98WpROj8/z5JVzkf4daxHaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.87.0': + resolution: {integrity: sha512-x/SuznE6loRZK9gz9AS4ZmzNzSu+5KdeoCS+sVHhnrE8HM6AD8UyTjwldQTTqlXGvkwHASgB0NiTNOc3o3PdAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': + resolution: {integrity: sha512-+JEDmCAXiiFmKhAjZhgAOwaHWY6WENOQ5QA+lFuY59tgsUCDOTRi46B+lENK6CWvtxsXpwkZXNOgpjAQcpIpnA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.87.0': + resolution: {integrity: sha512-/uC9XqXLAC9AHFLNycwmSQEvKqGrOCgU+uXALI7gNW0DG81TJ/3T2eHnKdRvcb5R1gFEfjxDwI4e4zIa29rICQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.87.0': + resolution: {integrity: sha512-FTRYPsBy/M728vnUv7l+KAwSAa3Qi4BQbhVvy7NYuMTZ0sUa/OWsbMx79HVFw6u/5KWcaYayDsoOiv7xbevzDg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.87.0': + resolution: {integrity: sha512-6sC+N7IY0h1egd8SrYUGNN7CUyKJZ4bIZakKa7V7hfinf4jfi8Kq4+klJvFp5DqXcwMn44I5MeBwmJluO1odFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.87.0': + resolution: {integrity: sha512-d5Mnn+ICbhoJPi37Ccjy+86T5qfH2+mSTrTMBXNPfB+wqSWOayBBLLIrCXdUy1YssluCVdGkl+DO9w5r5ODmUA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.87.0': + resolution: {integrity: sha512-sf5YWuUo1Dx9loqWCgF8P278MycfcD4JHjDC/LV5j11L3Lqa4PFiwCjD6B8kP4AjsgZZkLn3xbpJEtZBaccs4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.87.0': + resolution: {integrity: sha512-+aCraYbmK3uU3P0rFojjeaK9pGY6JvBDXh/0CM+gPyWKmNmXmnFe0qj4qj02U4ibFpbd9Pa9CmDvIdjJUd29rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.87.0': + resolution: {integrity: sha512-7rzbZQIljo6VLWVPqUyLa2P/WV3VFsJB4M52R4NCUJn1A50yjtUsHxiMHFL9opEKGFIiTbZ3VQzmtcJOPUq4NQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.87.0': + resolution: {integrity: sha512-Dry46Fa+AXE/xt9J/YriujU1pU9mzfNWHGQwtOYibAFH8HLrKWCi93AhIGnuUMiwn2JbuBkRyz51f50Ly4r4ow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.87.0': + resolution: {integrity: sha512-f/HMZez74gJvnRpGz+b5sdGyWrEgRLmFTXg9sNoqjPWogGxYHUhXKTKyMiYsu9rUGR2ciYqL3fk+Ua4JhMJbRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.87.0': + resolution: {integrity: sha512-lEC6ZfPZm/07JlB7FsOWX9o1RiHlXiBW594LSUGuXmekC8LZmvUPP9XzE2WZcNxJi5cHA/Fw56o02c3JxgMA5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.87.0': + resolution: {integrity: sha512-afBWKOD36dUiA9AenaiWpCg8xfyUVPYznyAAVXu413brK4RA5UI/Xoc4yfeGIT8G1a44MV+JR7hioryerBDzGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.87.0': + resolution: {integrity: sha512-daptNsEOuru4/nJTxV4DBHNJ5GepzmaBVQf+jjgKQGi9NRlZKik+RFq4hUL7ewsF1pwjKM+SkYaMbF82UwFGnw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.87.0': + resolution: {integrity: sha512-k+m/EShyXE29J/r1fjk0HlAzhN5JhIvj044oSJkXTEkCLrWZQgXb/3r4q5LTw/eaKW/4GuMXG2xmiJs3s5rPNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@3.0.3': + resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} + engines: {node: '>=12'} + + '@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==} + + '@sebbo2002/semantic-release-jsr@3.2.1': + resolution: {integrity: sha512-5Xlw9TNvOsyWUUzYvvR9h7KsPAXnhjLOpZfMbLGJetItFK+fZlheV2qgJXqwSW97Vaqu0cu82ZIj8noGxyZBXA==} + engines: {node: 20 || >=22.0.0} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@semantic-release/commit-analyzer@13.0.1': + resolution: {integrity: sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==} + engines: {node: '>=20.8.1'} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/error@3.0.0': + resolution: {integrity: sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==} + engines: {node: '>=14.17'} + + '@semantic-release/error@4.0.0': + resolution: {integrity: sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==} + engines: {node: '>=18'} + + '@semantic-release/exec@7.1.0': + resolution: {integrity: sha512-4ycZ2atgEUutspPZ2hxO6z8JoQt4+y/kkHvfZ1cZxgl9WKJId1xPj+UadwInj+gMn2Gsv+fLnbrZ4s+6tK2TFQ==} + engines: {node: '>=20.8.1'} + peerDependencies: + semantic-release: '>=24.1.0' + + '@semantic-release/git@10.0.1': + resolution: {integrity: sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==} + engines: {node: '>=14.17'} + peerDependencies: + semantic-release: '>=18.0.0' + + '@semantic-release/github@12.0.8': + resolution: {integrity: sha512-tej5AAgK5X9wHRoDmYhecMXEHEkFeGOY1XsEblKxu8pIQwahzf1STYyr7iPU6Lpbg6C5I3N2w/ocXrBo+L7jhw==} + engines: {node: ^22.14.0 || >= 24.10.0} + peerDependencies: + semantic-release: '>=24.1.0' + + '@semantic-release/npm@13.1.5': + resolution: {integrity: sha512-Hq5UxzoatN3LHiq2rTsWS54nCdqJHlsssGERCo8WlvdfFA9LoN0vO+OuKVSjtNapIc/S8C2LBj206wKLHg62mg==} + engines: {node: ^22.14.0 || >= 24.10.0} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/release-notes-generator@14.1.1': + resolution: {integrity: sha512-Pbd2e2XRMUD0OxehHpgd5/YghsE76cddkRHSoDvKLK+OCy4Ewxn49rWR631MEUU01lgwF/uyVXvbnVuu6+Z6VA==} + engines: {node: '>=20.8.1'} + peerDependencies: + semantic-release: '>=20.1.0' + + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} + engines: {node: '>=18'} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@26.6.4': + resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@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==} + + agent-base@9.0.0: + resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} + engines: {node: '>= 20'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + aggregate-error@5.0.0: + resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} + engines: {node: '>=18'} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.4.0: + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + argv-formatter@1.0.0: + resolution: {integrity: sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} + + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + clean-stack@5.3.0: + resolution: {integrity: sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==} + engines: {node: '>=14.16'} + + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + content-type@3.1.1: + resolution: {integrity: sha512-GW4qUsfFo59d0HbUibDlWv5wPz+vAAcaTWbKIuKCf0JkC7wWkSyf8f13IpXn5JkeMlB2P8iTSSIjwXljorg2vA==} + engines: {node: '>=22'} + + conventional-changelog-angular@8.3.1: + resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==} + engines: {node: '>=18'} + + conventional-changelog-conventionalcommits@9.3.1: + resolution: {integrity: sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==} + engines: {node: '>=18'} + + conventional-changelog-writer@8.4.0: + resolution: {integrity: sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==} + engines: {node: '>=18'} + hasBin: true + + conventional-commits-filter@5.0.0: + resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} + engines: {node: '>=18'} + + conventional-commits-parser@6.4.0: + resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} + engines: {node: '>=18'} + hasBin: true + + convert-hrtime@5.0.0: + resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} + engines: {node: '>=12'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + 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'} + + crypto-random-string@4.0.0: + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + + env-ci@11.2.0: + resolution: {integrity: sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==} + engines: {node: ^18.17 || >=20.6.1} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@2.0.0: + resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} + engines: {node: '>=4'} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + + find-versions@6.0.0: + resolution: {integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==} + engines: {node: '>=18'} + + fs-extra@11.4.1: + resolution: {integrity: sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==} + engines: {node: '>=14.14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-timeout@1.0.2: + resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} + engines: {node: '>=18'} + + 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-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + git-log-parser@1.2.1: + resolution: {integrity: sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==} + + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + handlebars@4.7.10: + resolution: {integrity: sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hono@4.13.13: + resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==} + engines: {node: '>=16.9.0'} + + hook-std@4.0.0: + resolution: {integrity: sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==} + engines: {node: '>=20'} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + + http-proxy-agent@9.1.0: + resolution: {integrity: sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==} + engines: {node: '>= 20'} + + https-proxy-agent@9.1.0: + resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} + engines: {node: '>= 20'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-from-esm@2.0.0: + resolution: {integrity: sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==} + engines: {node: '>=18.20'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + issue-parser@7.0.2: + resolution: {integrity: sha512-7atWPjhGEIX3JEtMrOYd8TKzboYlq+5sNbdl9POiLYOI14G5HZiQbZP0Xj5EZdrufQVXfJlpTV0hys0CuxwxZw==} + engines: {node: ^18.17 || >=20.6.1} + + java-properties@1.0.2: + resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} + engines: {node: '>= 0.6.0'} + + 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-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-with-bigint@3.5.12: + resolution: {integrity: sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + jsr@0.14.3: + resolution: {integrity: sha512-PGxnDepx7vwJoZQe2SHbyBiFfpGwsOKmX4kn/wZZqfMafV7fjXqTxSaX6lp9QHYkSTLKkER+P/wmrZY3gVJNzg==} + hasBin: true + + 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==} + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash.capitalize@4.2.1: + resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.uniqby@4.7.0: + resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + + magicast@0.5.5: + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} + + make-asynchronous@1.1.0: + resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} + engines: {node: '>=18'} + + marked-terminal@7.3.0: + resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} + engines: {node: '>=16.0.0'} + peerDependencies: + marked: '>=1 <16' + + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime@4.1.0: + resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} + engines: {node: '>=16'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nerf-dart@1.0.0: + resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==} + + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + + node-stream-zip@1.16.0: + resolution: {integrity: sha512-ObaRrRoR8T68wF6suxHd7R4XQNamij6ZQHrwG7Dx1D2zeHcDNLsIOBcWrIwtDm7AsCXBguaPHgXhcjxDa2szrg==} + engines: {node: '>=0.12.0'} + + normalize-package-data@6.0.2: + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} + + normalize-package-data@8.0.0: + resolution: {integrity: sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + normalize-url@9.0.1: + resolution: {integrity: sha512-ARftfC5HdUNu9jJeL8pHj8debUIHA2b91FizCoMzY4lG6dDX13jdvTK0TBe24IBDRf2HvJSzzwEPvmbkQWHRSg==} + engines: {node: '>=20'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + npm@11.21.0: + resolution: {integrity: sha512-Zov8KhamNneiLdELtj5YALtNmJW4L4fCLTzjfpzXG2w6MSHcf0UxgdlK5uloCuksWT+7mGUU7wi79cO6RqivPg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + bundledDependencies: + - '@isaacs/string-locale-compare' + - '@npmcli/arborist' + - '@npmcli/config' + - '@npmcli/fs' + - '@npmcli/map-workspaces' + - '@npmcli/metavuln-calculator' + - '@npmcli/package-json' + - '@npmcli/promise-spawn' + - '@npmcli/redact' + - '@npmcli/run-script' + - '@sigstore/tuf' + - abbrev + - archy + - cacache + - chalk + - ci-info + - fastest-levenshtein + - fs-minipass + - glob + - graceful-fs + - hosted-git-info + - ini + - init-package-json + - is-cidr + - json-parse-even-better-errors + - libnpmaccess + - libnpmdiff + - libnpmexec + - libnpmfund + - libnpmorg + - libnpmpack + - libnpmpublish + - libnpmsearch + - libnpmteam + - libnpmversion + - make-fetch-happen + - minimatch + - minipass + - minipass-pipeline + - ms + - node-gyp + - nopt + - npm-audit-report + - npm-install-checks + - npm-package-arg + - npm-pick-manifest + - npm-profile + - npm-registry-fetch + - npm-user-validate + - p-map + - pacote + - parse-conflict-json + - proc-log + - qrcode-terminal + - read + - semver + - spdx-expression-parse + - ssri + - supports-color + - tar + - text-table + - tiny-relative-date + - treeverse + - validate-npm-package-name + - which + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + oxfmt@0.72.0: + resolution: {integrity: sha512-16OQeL0uhPZbfmT9S9WWM0Nv9+BmVi8Ra1uTYaSKj/qSCs6FB30KOhz9JoNYw6thT+K5bVxsk/tSEOu497i34g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint-tsgolint@7.0.2003: + resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} + hasBin: true + + oxlint@1.87.0: + resolution: {integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2003' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + + p-each-series@3.0.0: + resolution: {integrity: sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==} + engines: {node: '>=12'} + + p-event@6.0.1: + resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==} + engines: {node: '>=16.17'} + + p-filter@4.1.0: + resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==} + engines: {node: '>=18'} + + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + + p-map@7.0.8: + resolution: {integrity: sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==} + engines: {node: '>=18'} + + p-reduce@2.1.0: + resolution: {integrity: sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==} + engines: {node: '>=8'} + + p-reduce@3.0.0: + resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} + engines: {node: '>=12'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + 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'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pkg-conf@2.1.0: + resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} + engines: {node: '>=4'} + + postcss@8.5.29: + resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==} + engines: {node: ^10 || ^12 || >=14} + + pretty-ms@9.3.1: + resolution: {integrity: sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==} + engines: {node: '>=18'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + proxy-agent-negotiate@1.1.0: + resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} + engines: {node: '>= 20'} + peerDependencies: + kerberos: ^2.0.0 + peerDependenciesMeta: + kerberos: + optional: true + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + read-package-up@11.0.0: + resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} + engines: {node: '>=18'} + + read-package-up@12.0.0: + resolution: {integrity: sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==} + engines: {node: '>=20'} + + read-pkg@10.1.0: + resolution: {integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==} + engines: {node: '>=20'} + + read-pkg@9.0.1: + resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} + engines: {node: '>=18'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} + engines: {node: '>=14'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + 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'} + + rolldown@1.2.12: + resolution: {integrity: sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + semantic-release@25.0.5: + resolution: {integrity: sha512-mn61SUJwtM8ThrWn2WmgLVpwVJeG/hPSupua1psdMoufmwRIPyvRLkRkL0JDXkP67OntlLWUYnBnfVc8EDO3/g==} + engines: {node: ^22.14.0 || >= 24.10.0} + hasBin: true + + semiver@1.1.0: + resolution: {integrity: sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==} + engines: {node: '>=6'} + + semver-regex@4.0.5: + resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} + engines: {node: '>=12'} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: 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'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + signale@1.4.0: + resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} + engines: {node: '>=6'} + + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spawn-error-forwarder@1.0.0: + resolution: {integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.24: + resolution: {integrity: sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==} + + split2@1.0.0: + resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + stream-combiner2@1.1.1: + resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + 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'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + super-regex@1.1.0: + resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} + engines: {node: '>=18'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + temp-dir@3.0.0: + resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} + engines: {node: '>=14.16'} + + tempy@3.2.0: + resolution: {integrity: sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==} + engines: {node: '>=14.16'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + time-span@5.1.0: + resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} + engines: {node: '>=12'} + + tinybench@6.2.1: + resolution: {integrity: sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==} + engines: {node: '>=20.0.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'} + + tinypool@2.2.0: + resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==} + engines: {node: ^20.0.0 || >=22.0.0} + + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + traverse@0.6.8: + resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} + engines: {node: '>= 0.4'} + + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-fest@5.10.0: + resolution: {integrity: sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==} + engines: {node: '>=20'} + + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + + undici@6.29.0: + resolution: {integrity: sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==} + engines: {node: '>=18.17'} + + undici@7.30.0: + resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==} + engines: {node: '>=20.18.1'} + + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unicorn-magic@0.4.1: + resolution: {integrity: sha512-lzlXPoVB0Uy/FvTaUgX39RbixYiSKoc3JSoSf01+0c2B6FR3qdYIPN7Qjo/AV7n6sqLCk0509cT3LRj1oSZ1+A==} + engines: {node: '>=20'} + + unique-string@3.0.0: + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + url-join@5.0.0: + resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vite@8.3.3: + resolution: {integrity: sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg==} + 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 + + 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 + + web-worker@1.5.0: + resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + 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@16.2.2: + resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==} + engines: {node: '>=10'} + + yargs@18.2.0: + resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yoctocolors@2.2.0: + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} + engines: {node: '>=18'} + +snapshots: + + '@actions/core@3.0.1': + dependencies: + '@actions/exec': 3.0.0 + '@actions/http-client': 4.0.1 + + '@actions/exec@3.0.0': + dependencies: + '@actions/io': 3.0.2 + + '@actions/http-client@4.0.1': + dependencies: + tunnel: 0.0.6 + undici: 6.29.0 + + '@actions/io@3.0.2': {} + + '@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': {} + + '@colors/colors@1.5.0': + 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 + + '@octokit/auth-token@6.0.0': {} + + '@octokit/core@7.0.8': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.5 + '@octokit/request': 10.0.16 + '@octokit/request-error': 7.1.2 + '@octokit/types': 18.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.5': + dependencies: + '@octokit/types': 18.0.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.5': + dependencies: + '@octokit/request': 10.0.16 + '@octokit/types': 18.0.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@27.0.0': {} + + '@octokit/openapi-types@28.0.0': {} + + '@octokit/openapi-types@29.0.1': {} + + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.8)': + dependencies: + '@octokit/core': 7.0.8 + '@octokit/types': 16.0.0 + + '@octokit/plugin-retry@8.1.1(@octokit/core@7.0.8)': + dependencies: + '@octokit/core': 7.0.8 + '@octokit/request-error': 7.1.2 + '@octokit/types': 17.0.0 + bottleneck: 2.19.5 + + '@octokit/plugin-throttling@11.0.5(@octokit/core@7.0.8)': + dependencies: + '@octokit/core': 7.0.8 + '@octokit/types': 17.0.0 + bottleneck: 2.19.5 + + '@octokit/request-error@7.1.2': + dependencies: + '@octokit/types': 18.0.0 + + '@octokit/request@10.0.16': + dependencies: + '@octokit/endpoint': 11.0.5 + '@octokit/request-error': 7.1.2 + '@octokit/types': 18.0.0 + content-type: 3.1.1 + json-with-bigint: 3.5.12 + universal-user-agent: 7.0.3 + + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + + '@octokit/types@17.0.0': + dependencies: + '@octokit/openapi-types': 28.0.0 + + '@octokit/types@18.0.0': + dependencies: + '@octokit/openapi-types': 29.0.1 + + '@oxc-project/types@0.152.0': {} + + '@oxfmt/binding-android-arm-eabi@0.72.0': + optional: true + + '@oxfmt/binding-android-arm64@0.72.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.72.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.72.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.72.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.72.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.72.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.72.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.72.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.72.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.72.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.72.0': + optional: true + + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + optional: true + + '@oxlint-tsgolint/darwin-x64@7.0.2003': + optional: true + + '@oxlint-tsgolint/linux-arm64@7.0.2003': + optional: true + + '@oxlint-tsgolint/linux-x64@7.0.2003': + optional: true + + '@oxlint-tsgolint/win32-arm64@7.0.2003': + optional: true + + '@oxlint-tsgolint/win32-x64@7.0.2003': + optional: true + + '@oxlint/binding-android-arm-eabi@1.87.0': + optional: true + + '@oxlint/binding-android-arm64@1.87.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.87.0': + optional: true + + '@oxlint/binding-darwin-x64@1.87.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.87.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.87.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.87.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.87.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.87.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.87.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.87.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.87.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.87.0': + optional: true + + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@3.0.3': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + + '@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': {} + + '@sebbo2002/semantic-release-jsr@3.2.1': + dependencies: + '@semantic-release/error': 4.0.0 + jsr: 0.14.3 + + '@sec-ant/readable-stream@0.4.1': {} + + '@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0)': + dependencies: + conventional-changelog-angular: 8.3.1 + conventional-changelog-writer: 8.4.0 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.4.0 + debug: 4.4.3(supports-color@7.2.0) + import-from-esm: 2.0.0(supports-color@7.2.0) + lodash-es: 4.18.1 + micromatch: 4.0.8 + semantic-release: 25.0.5(supports-color@7.2.0)(typescript@7.0.2) + transitivePeerDependencies: + - supports-color + + '@semantic-release/error@3.0.0': {} + + '@semantic-release/error@4.0.0': {} + + '@semantic-release/exec@7.1.0(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0)': + dependencies: + '@semantic-release/error': 4.0.0 + aggregate-error: 3.1.0 + debug: 4.4.3(supports-color@7.2.0) + execa: 9.6.1 + lodash-es: 4.18.1 + parse-json: 8.3.0 + semantic-release: 25.0.5(supports-color@7.2.0)(typescript@7.0.2) + transitivePeerDependencies: + - supports-color + + '@semantic-release/git@10.0.1(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0)': + dependencies: + '@semantic-release/error': 3.0.0 + aggregate-error: 3.1.0 + debug: 4.4.3(supports-color@7.2.0) + dir-glob: 3.0.1 + execa: 5.1.1 + lodash: 4.18.1 + micromatch: 4.0.8 + p-reduce: 2.1.0 + semantic-release: 25.0.5(supports-color@7.2.0)(typescript@7.0.2) + transitivePeerDependencies: + - supports-color + + '@semantic-release/github@12.0.8(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0)': + dependencies: + '@octokit/core': 7.0.8 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.8) + '@octokit/plugin-retry': 8.1.1(@octokit/core@7.0.8) + '@octokit/plugin-throttling': 11.0.5(@octokit/core@7.0.8) + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + debug: 4.4.3(supports-color@7.2.0) + dir-glob: 3.0.1 + http-proxy-agent: 9.1.0(supports-color@7.2.0) + https-proxy-agent: 9.1.0(supports-color@7.2.0) + issue-parser: 7.0.2 + lodash-es: 4.18.1 + mime: 4.1.0 + p-filter: 4.1.0 + semantic-release: 25.0.5(supports-color@7.2.0)(typescript@7.0.2) + tinyglobby: 0.2.17 + undici: 7.30.0 + url-join: 5.0.0 + transitivePeerDependencies: + - kerberos + - supports-color + + '@semantic-release/npm@13.1.5(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))': + dependencies: + '@actions/core': 3.0.1 + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + env-ci: 11.2.0 + execa: 9.6.1 + fs-extra: 11.4.1 + lodash-es: 4.18.1 + nerf-dart: 1.0.0 + normalize-url: 9.0.1 + npm: 11.21.0 + rc: 1.2.8 + read-pkg: 10.1.0 + registry-auth-token: 5.1.1 + semantic-release: 25.0.5(supports-color@7.2.0)(typescript@7.0.2) + semver: 7.8.5 + tempy: 3.2.0 + + '@semantic-release/release-notes-generator@14.1.1(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0)': + dependencies: + conventional-changelog-angular: 8.3.1 + conventional-changelog-writer: 8.4.0 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.4.0 + debug: 4.4.3(supports-color@7.2.0) + import-from-esm: 2.0.0(supports-color@7.2.0) + lodash-es: 4.18.1 + read-package-up: 11.0.0 + semantic-release: 25.0.5(supports-color@7.2.0)(typescript@7.0.2) + transitivePeerDependencies: + - supports-color + + '@simple-libs/stream-utils@1.2.0': {} + + '@sindresorhus/is@4.6.0': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@26.6.4': + dependencies: + undici-types: 8.9.0 + + '@types/normalize-package-data@2.4.4': {} + + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@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@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.3(@types/node@26.6.4)) + + '@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.3(@types/node@26.6.4))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 8.3.3(@types/node@26.6.4) + + '@vitest/spy@5.0.3': {} + + agent-base@9.0.0: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + aggregate-error@5.0.0: + dependencies: + clean-stack: 5.3.0 + indent-string: 5.0.0 + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.4.0: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + argparse@2.0.1: {} + + argv-formatter@1.0.0: {} + + array-ify@1.0.0: {} + + 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 + + before-after-hook@4.0.0: {} + + bottleneck@2.19.5: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + callsites@3.1.0: {} + + chai@6.3.0: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + char-regex@1.0.2: {} + + clean-stack@2.2.0: {} + + clean-stack@5.3.0: + dependencies: + escape-string-regexp: 5.0.0 + + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.2 + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + content-type@3.1.1: {} + + conventional-changelog-angular@8.3.1: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-conventionalcommits@9.3.1: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-writer@8.4.0: + dependencies: + '@simple-libs/stream-utils': 1.2.0 + conventional-commits-filter: 5.0.0 + handlebars: 4.7.10 + meow: 13.2.0 + semver: 7.8.5 + + conventional-commits-filter@5.0.0: {} + + conventional-commits-parser@6.4.0: + dependencies: + '@simple-libs/stream-utils': 1.2.0 + meow: 13.2.0 + + convert-hrtime@5.0.0: {} + + core-util-is@1.0.3: {} + + cosmiconfig@9.0.2(typescript@7.0.2): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + parse-json: 5.2.0 + optionalDependencies: + typescript: 7.0.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-random-string@4.0.0: + dependencies: + type-fest: 1.4.0 + + debug@4.4.3(supports-color@7.2.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 + + deep-extend@0.6.0: {} + + detect-libc@2.1.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emojilib@2.4.0: {} + + env-ci@11.2.0: + dependencies: + execa: 8.0.1 + java-properties: 1.0.2 + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-module-lexer@2.3.2: {} + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@5.0.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.1 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.2.0 + + expect-type@1.4.0: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + figures@2.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up-simple@1.0.1: {} + + find-up@2.1.0: + dependencies: + locate-path: 2.0.0 + + find-versions@6.0.0: + dependencies: + semver-regex: 4.0.5 + super-regex: 1.1.0 + + fs-extra@11.4.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-timeout@1.0.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.7.0: {} + + get-stream@6.0.1: {} + + get-stream@8.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + git-log-parser@1.2.1: + dependencies: + argv-formatter: 1.0.0 + spawn-error-forwarder: 1.0.0 + split2: 1.0.0 + stream-combiner2: 1.1.1 + through2: 2.0.5 + traverse: 0.6.8 + + graceful-fs@4.2.10: {} + + graceful-fs@4.2.11: {} + + handlebars@4.7.10: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + highlight.js@10.7.3: {} + + hono@4.13.13: {} + + hook-std@4.0.0: {} + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.5.3 + + http-proxy-agent@9.1.0(supports-color@7.2.0): + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@7.2.0) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + + https-proxy-agent@9.1.0(supports-color@7.2.0): + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@7.2.0) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + + human-signals@2.1.0: {} + + human-signals@5.0.0: {} + + human-signals@8.0.1: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-from-esm@2.0.0(supports-color@7.2.0): + dependencies: + debug: 4.4.3(supports-color@7.2.0) + import-meta-resolve: 4.2.0 + transitivePeerDependencies: + - supports-color + + import-meta-resolve@4.2.0: {} + + indent-string@4.0.0: {} + + indent-string@5.0.0: {} + + index-to-position@1.2.0: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + is-arrayish@0.2.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.1.0: {} + + isarray@1.0.0: {} + + isexe@2.0.0: {} + + issue-parser@7.0.2: + dependencies: + lodash.capitalize: 4.2.1 + lodash.escaperegexp: 4.1.2 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.uniqby: 4.7.0 + + java-properties@1.0.2: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-with-bigint@3.5.12: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsr@0.14.3: + dependencies: + node-stream-zip: 1.16.0 + semiver: 1.1.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: {} + + load-json-file@4.0.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + + locate-path@2.0.0: + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + + lodash-es@4.18.1: {} + + lodash.capitalize@4.2.1: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.uniqby@4.7.0: {} + + lodash@4.18.1: {} + + lru-cache@10.4.3: {} + + lru-cache@11.5.3: {} + + magic-string@1.4.3: + 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 + + make-asynchronous@1.1.0: + dependencies: + p-event: 6.0.1 + type-fest: 4.41.0 + web-worker: 1.5.0 + + marked-terminal@7.3.0(marked@15.0.12): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.4.0 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 15.0.12 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + + marked@15.0.12: {} + + meow@13.2.0: {} + + merge-stream@2.0.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime@4.1.0: {} + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + minimist@1.2.8: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.20: {} + + neo-async@2.6.2: {} + + nerf-dart@1.0.0: {} + + node-emoji@2.2.0: + dependencies: + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 + + node-stream-zip@1.16.0: {} + + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.8.5 + validate-npm-package-license: 3.0.4 + + normalize-package-data@8.0.0: + dependencies: + hosted-git-info: 9.0.3 + semver: 7.8.5 + validate-npm-package-license: 3.0.4 + + normalize-url@9.0.1: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + npm@11.21.0: {} + + object-assign@4.1.1: {} + + obug@2.2.1: {} + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + oxfmt@0.72.0: + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + + oxlint-tsgolint@7.0.2003: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2003 + '@oxlint-tsgolint/darwin-x64': 7.0.2003 + '@oxlint-tsgolint/linux-arm64': 7.0.2003 + '@oxlint-tsgolint/linux-x64': 7.0.2003 + '@oxlint-tsgolint/win32-arm64': 7.0.2003 + '@oxlint-tsgolint/win32-x64': 7.0.2003 + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + + p-each-series@3.0.0: {} + + p-event@6.0.1: + dependencies: + p-timeout: 6.1.4 + + p-filter@4.1.0: + dependencies: + p-map: 7.0.8 + + p-limit@1.3.0: + dependencies: + p-try: 1.0.0 + + p-locate@2.0.0: + dependencies: + p-limit: 1.3.0 + + p-map@7.0.8: {} + + p-reduce@2.1.0: {} + + p-reduce@3.0.0: {} + + p-timeout@6.1.4: {} + + p-try@1.0.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + 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 + + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.7 + index-to-position: 1.2.0 + type-fest: 4.41.0 + + parse-ms@4.0.0: {} + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + + path-exists@3.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + pify@3.0.0: {} + + pkg-conf@2.1.0: + dependencies: + find-up: 2.1.0 + load-json-file: 4.0.0 + + postcss@8.5.29: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + pretty-ms@9.3.1: + dependencies: + parse-ms: 4.0.0 + + process-nextick-args@2.0.1: {} + + proto-list@1.2.4: {} + + proxy-agent-negotiate@1.1.0: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + read-package-up@11.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 9.0.1 + type-fest: 4.41.0 + + read-package-up@12.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 10.1.0 + type-fest: 5.10.0 + + read-pkg@10.1.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 8.0.0 + parse-json: 8.3.0 + type-fest: 5.10.0 + unicorn-magic: 0.4.1 + + read-pkg@9.0.1: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 6.0.2 + parse-json: 8.3.0 + type-fest: 4.41.0 + unicorn-magic: 0.1.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + registry-auth-token@5.1.1: + dependencies: + '@pnpm/npm-conf': 3.0.3 + + require-directory@2.1.1: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.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 + + safe-buffer@5.1.2: {} + + semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2): + dependencies: + '@semantic-release/commit-analyzer': 13.0.1(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0) + '@semantic-release/error': 4.0.0 + '@semantic-release/github': 12.0.8(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0) + '@semantic-release/npm': 13.1.5(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2)) + '@semantic-release/release-notes-generator': 14.1.1(semantic-release@25.0.5(supports-color@7.2.0)(typescript@7.0.2))(supports-color@7.2.0) + aggregate-error: 5.0.0 + cosmiconfig: 9.0.2(typescript@7.0.2) + debug: 4.4.3(supports-color@7.2.0) + env-ci: 11.2.0 + execa: 9.6.1 + figures: 6.1.0 + find-versions: 6.0.0 + get-stream: 6.0.1 + git-log-parser: 1.2.1 + hook-std: 4.0.0 + hosted-git-info: 9.0.3 + import-from-esm: 2.0.0(supports-color@7.2.0) + lodash-es: 4.18.1 + marked: 15.0.12 + marked-terminal: 7.3.0(marked@15.0.12) + micromatch: 4.0.8 + p-each-series: 3.0.0 + p-reduce: 3.0.0 + read-package-up: 12.0.0 + resolve-from: 5.0.0 + semver: 7.8.5 + signale: 1.4.0 + yargs: 18.2.0 + transitivePeerDependencies: + - kerberos + - supports-color + - typescript + + semiver@1.1.0: {} + + semver-regex@4.0.5: {} + + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + signale@1.4.0: + dependencies: + chalk: 2.4.2 + figures: 2.0.0 + pkg-conf: 2.1.0 + + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 + + source-map-js@1.2.2: {} + + source-map@0.6.1: {} + + spawn-error-forwarder@1.0.0: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.24 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.24 + + spdx-license-ids@3.0.24: {} + + split2@1.0.0: + dependencies: + through2: 2.0.5 + + std-env@4.3.0: {} + + stream-combiner2@1.1.1: + dependencies: + duplexer2: 0.1.4 + readable-stream: 2.3.8 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + 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 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.4.0 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-final-newline@4.0.0: {} + + strip-json-comments@2.0.1: {} + + super-regex@1.1.0: + dependencies: + function-timeout: 1.0.2 + make-asynchronous: 1.1.0 + time-span: 5.1.0 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + tagged-tag@1.0.0: {} + + temp-dir@3.0.0: {} + + tempy@3.2.0: + dependencies: + is-stream: 3.0.0 + temp-dir: 3.0.0 + type-fest: 2.19.0 + unique-string: 3.0.0 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + time-span@5.1.0: + dependencies: + convert-hrtime: 5.0.0 + + tinybench@6.2.1: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinypool@2.2.0: {} + + tinyrainbow@3.2.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + traverse@0.6.8: {} + + tunnel@0.0.6: {} + + type-fest@1.4.0: {} + + type-fest@2.19.0: {} + + type-fest@4.41.0: {} + + type-fest@5.10.0: + dependencies: + tagged-tag: 1.0.0 + + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + uglify-js@3.19.3: + optional: true + + undici-types@8.9.0: {} + + undici@6.29.0: {} + + undici@7.30.0: {} + + unicode-emoji-modifier-base@1.0.0: {} + + unicorn-magic@0.1.0: {} + + unicorn-magic@0.3.0: {} + + unicorn-magic@0.4.1: {} + + unique-string@3.0.0: + dependencies: + crypto-random-string: 4.0.0 + + universal-user-agent@7.0.3: {} + + universalify@2.0.1: {} + + url-join@5.0.0: {} + + util-deprecate@1.0.2: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vite@8.3.3(@types/node@26.6.4): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.12 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.6.4 + fsevents: 2.3.3 + + vitest@5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.3(@types/node@26.6.4)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@8.3.3(@types/node@26.6.4)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.1 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.3(@types/node@26.6.4) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 26.6.4 + '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) + transitivePeerDependencies: + - msw + + web-worker@1.5.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@3.2.1: {} + + wordwrap@1.0.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} + + yargs-parser@22.0.0: {} + + yargs@16.2.2: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + 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 + + yoctocolors@2.2.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..8119d9a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +packages: + - "." +minimumReleaseAge: 4320 +minimumReleaseAgeExclude: + - "@udibo/*" diff --git a/scripts/npm-smoke.ts b/scripts/npm-smoke.ts new file mode 100644 index 0000000..b5e7a81 --- /dev/null +++ b/scripts/npm-smoke.ts @@ -0,0 +1,184 @@ +/** + * Consumes the packed npm artifact the way an adopter would: packs the + * package, installs the tarball into an empty project, type-checks a consumer + * under NodeNext resolution, and runs it on Node. + * + * Run `pnpm build` first, then `pnpm smoke`. + */ +import { execFileSync } from "node:child_process"; +import { + mkdirSync, + mkdtempSync, + readdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const packageDir = fileURLToPath(new URL("../", import.meta.url)); +const tscPath = join(packageDir, "node_modules", "typescript", "bin", "tsc"); +const isWindows = process.platform === "win32"; + +function fail(message: string): never { + console.error(`npm smoke: ${message}`); + process.exit(1); +} + +function run(command: string, args: string[], cwd: string): string { + return execFileSync(command, args, { + cwd, + encoding: "utf8", + shell: isWindows && command !== process.execPath, + stdio: ["ignore", "pipe", "inherit"], + env: { ...process.env, npm_config_ignore_scripts: "true" }, + }); +} + +function main(): void { + const workDir = mkdtempSync(join(tmpdir(), "http-error-smoke-")); + try { + const packDir = join(workDir, "pack"); + const consumerDir = join(workDir, "consumer"); + mkdirSync(packDir); + mkdirSync(consumerDir); + + run("pnpm", ["pack", "--pack-destination", packDir], packageDir); + const tarballs = readdirTarballs(packDir); + if (tarballs.length !== 1) + fail(`expected one tarball, found ${tarballs.length}`); + const tarball = join(packDir, tarballs[0]!); + + writeFileSync( + join(consumerDir, "package.json"), + JSON.stringify({ + name: "http-error-smoke-consumer", + private: true, + type: "module", + }), + ); + run( + "npm", + [ + "install", + "--no-audit", + "--no-fund", + "--ignore-scripts", + "--offline", + tarball, + ], + consumerDir, + ); + + const installedDir = join( + consumerDir, + "node_modules", + "@udibo", + "http-error", + ); + const listing = readdirSync(installedDir, { recursive: true }) + .map(String) + .map((entry) => entry.replaceAll("\\", "/")); + for (const required of [ + "dist/mod.js", + "dist/mod.d.ts", + "README.md", + "LICENSE", + "package.json", + ]) { + if (!listing.includes(required)) + fail(`the tarball is missing ${required}`); + } + const unexpected = listing.filter( + (entry) => entry.includes(".test.") || entry.startsWith("src/"), + ); + if (unexpected.length > 0) + fail(`the tarball contains ${unexpected.join(", ")}`); + + const installed = JSON.parse( + readFileSync(join(installedDir, "package.json"), "utf8"), + ) as { dependencies?: Record }; + if ( + installed.dependencies && + Object.keys(installed.dependencies).length > 0 + ) { + fail("the package must have no runtime dependencies"); + } + + writeFileSync( + join(consumerDir, "tsconfig.json"), + JSON.stringify({ + compilerOptions: { + target: "ES2022", + lib: ["ES2023", "DOM"], + module: "NodeNext", + moduleResolution: "NodeNext", + types: [], + strict: true, + noEmit: true, + skipLibCheck: false, + }, + include: ["main.ts"], + }), + ); + writeFileSync(join(consumerDir, "main.ts"), CONSUMER_SOURCE); + run(process.execPath, [tscPath, "-p", "."], consumerDir); + + const output = run(process.execPath, ["main.ts"], consumerDir); + if (!output.includes("npm smoke consumer ok")) + fail(`unexpected output: ${output}`); + console.log( + `npm smoke: ${tarballs[0]} type-checked and ran on Node ${process.version}`, + ); + } finally { + rmSync(workDir, { recursive: true, force: true }); + } +} + +function readdirTarballs(directory: string): string[] { + return readdirSync(directory).filter((name) => name.endsWith(".tgz")); +} + +const CONSUMER_SOURCE = `import { + createHttpErrorClass, + HttpError, + isHttpErrorLike, + type HttpErrorOptions, + type ProblemDetails, +} from "@udibo/http-error"; + +function equal(actual: T, expected: T): void { + if (actual !== expected) { + throw new Error("expected " + String(expected) + ", received " + String(actual)); + } +} + +function ok(value: unknown): void { + if (!value) throw new Error("expected a truthy value"); +} + +const options: HttpErrorOptions<{ field: string }> = { extensions: { field: "email" } }; +const error = new HttpError(422, "bad field", options); +const details: ProblemDetails<{ field: string }> = error.toJSON(); +equal(details.status, 422); +equal(details.title, "Unprocessable Entity"); +equal(details.field, "email"); +ok(isHttpErrorLike(error)); + +const NotFound = createHttpErrorClass({ status: 404, name: "MissingError" }); +const missing = new NotFound("gone"); +ok(missing instanceof HttpError); +equal(missing.name, "MissingError"); + +const response = error.getResponse(); +equal(response.status, 422); +equal(response.headers.get("content-type"), "application/problem+json"); +const roundTripped = await HttpError.from(response); +equal(roundTripped.message, "bad field"); + +console.log("npm smoke consumer ok"); +`; + +main(); diff --git a/src/mod.test.ts b/src/mod.test.ts new file mode 100644 index 0000000..4a7556e --- /dev/null +++ b/src/mod.test.ts @@ -0,0 +1,1260 @@ +import { describe, expect, it } from "vitest"; + +import { + createHttpErrorClass, + HttpError, + type HttpErrorOptions, + isHttpErrorLike, +} from "./mod.ts"; + +const DEFAULT_ERROR_NAMES: Readonly> = { + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Auth Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Content Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", + 421: "Misdirected Request", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required", +}; + +const DEFAULT_EXPOSED_MESSAGES: Readonly> = { + 400: "The server cannot process the request due to a client error.", + 401: "Authentication is required to access this resource.", + 402: "Payment is required to access this resource.", + 403: "You do not have permission to access this resource.", + 404: "The requested resource could not be found.", + 405: "The request method is not supported for this resource.", + 406: "The server cannot produce a response matching the acceptable values.", + 407: "Proxy authentication is required.", + 408: "The server timed out waiting for the request.", + 409: "The request conflicts with the current state of the resource.", + 410: "The requested resource is no longer available.", + 411: "The request requires a Content-Length header.", + 412: "A precondition in the request headers was not met.", + 413: "The request body is larger than the server is willing to process.", + 414: "The request URI is longer than the server is willing to process.", + 415: "The request uses a media type that is not supported.", + 416: "The requested range cannot be satisfied.", + 417: "The expectation in the Expect header cannot be met.", + 418: "The server refuses to brew coffee because it is a teapot.", + 421: "The request was directed at a server unable to produce a response.", + 422: "The request was well-formed but contained semantic errors.", + 423: "The requested resource is locked.", + 424: "The request failed due to a previous request failure.", + 425: "The server is unwilling to process a request that might be replayed.", + 426: "The client must upgrade to a different protocol.", + 428: "The request must be conditional.", + 429: "Too many requests have been sent in a given amount of time.", + 431: "The request headers are too large.", + 451: "The resource is unavailable for legal reasons.", + 500: "The server encountered an unexpected condition.", + 501: "The server does not support the functionality required.", + 502: "The server received an invalid response from an upstream server.", + 503: "The server is currently unavailable.", + 504: "The server did not receive a timely response from an upstream server.", + 505: "The HTTP version used in the request is not supported.", + 506: "The server has an internal configuration error.", + 507: "The server has insufficient storage to complete the request.", + 508: "The server detected an infinite loop while processing the request.", + 510: "Further extensions to the request are required.", + 511: "Network authentication is required to access this resource.", +}; + +function defaultErrorName(status: number): string { + return ( + DEFAULT_ERROR_NAMES[status] ?? + (status < 500 ? "Unknown Client Error" : "Unknown Server Error") + ); +} + +function defaultExposedMessage(status: number): string { + return ( + DEFAULT_EXPOSED_MESSAGES[status] ?? + (status < 500 ? "A client error occurred." : "A server error occurred.") + ); +} + +const statusRange = Array.from({ length: 200 }, (_, index) => 400 + index); + +describe("HttpError", () => { + it("defaults to a non-exposed 500 error without args", () => { + const error = new HttpError(); + expect(error.toString()).toBe("Internal Server Error"); + expect(error.name).toBe("Internal Server Error"); + expect(error.message).toBe(""); + expect(error.status).toBe(500); + expect(error.expose).toBe(false); + expect(error.cause).toBeUndefined(); + expect(error.extensions).toEqual({}); + expect(error.statusText).toBeUndefined(); + expect(error.type).toBeUndefined(); + expect(error.instance).toBeUndefined(); + expect(error.exposedMessage).toBe( + "The server encountered an unexpected condition.", + ); + }); + + it("is an Error whose name is not enumerable", () => { + const error = new HttpError(404, "missing"); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(HttpError); + expect(Object.keys(error)).not.toContain("name"); + }); + + it.each([ + ["status", () => new HttpError(400)], + ["options", () => new HttpError({ status: 400 })], + [ + "undefined message and options", + () => new HttpError(undefined, { status: 400 }), + ], + ])("accepts a status through %s", (_label, create) => { + const error = create(); + expect(error.status).toBe(400); + expect(error.expose).toBe(true); + }); + + it.each([ + ["message", () => new HttpError("something went wrong")], + ["options", () => new HttpError({ message: "something went wrong" })], + [ + "undefined status and message", + () => new HttpError(undefined, "something went wrong"), + ], + [ + "undefined status and options", + () => new HttpError(undefined, { message: "something went wrong" }), + ], + ])("accepts a message through %s", (_label, create) => { + const error = create(); + expect(error.toString()).toBe( + "Internal Server Error: something went wrong", + ); + expect(error.message).toBe("something went wrong"); + expect(error.exposedMessage).toBe( + "The server encountered an unexpected condition.", + ); + }); + + describe("status and message arguments take priority over options", () => { + const options = { message: "failed", status: 502 }; + + it.each([ + { + label: "status, message, options", + create: () => new HttpError(400, "something went wrong", options), + status: 400, + name: "Bad Request", + message: "something went wrong", + }, + { + label: "status, options", + create: () => new HttpError(400, options), + status: 400, + name: "Bad Request", + message: "failed", + }, + { + label: "message, options", + create: () => new HttpError("something went wrong", options), + status: 502, + name: "Bad Gateway", + message: "something went wrong", + }, + { + label: "options", + create: () => new HttpError(options), + status: 502, + name: "Bad Gateway", + message: "failed", + }, + ])("$label", ({ create, status, name, message }) => { + const error = create(); + expect(error.toString()).toBe(`${name}: ${message}`); + expect(error.name).toBe(name); + expect(error.message).toBe(message); + expect(error.status).toBe(status); + expect(error.expose).toBe(status < 500); + }); + }); + + it.each([ + ["options", (cause: Error) => new HttpError({ cause })], + ["status undefined", (cause: Error) => new HttpError(undefined, { cause })], + [ + "status and message undefined", + (cause: Error) => new HttpError(undefined, undefined, { cause }), + ], + ])("keeps the cause passed through %s", (_label, create) => { + const cause = new Error("fail"); + expect(create(cause).cause).toBe(cause); + }); + + it("keeps a falsy cause", () => { + expect(new HttpError({ cause: 0 }).cause).toBe(0); + expect(new HttpError({ cause: null }).cause).toBeNull(); + }); + + it.each([-500, 0, 399, 600, 1000])( + "throws a RangeError for the invalid status %i", + (status) => { + expect(() => new HttpError(status)).toThrow( + new RangeError("invalid error status"), + ); + expect(() => new HttpError(status)).toThrow(RangeError); + }, + ); + + it("accepts the boundary statuses 400 and 599", () => { + expect(new HttpError(400).status).toBe(400); + expect(new HttpError(599).status).toBe(599); + }); + + it("derives the default name from the status for every error status", () => { + const message = "something went wrong"; + for (const status of statusRange) { + const name = defaultErrorName(status); + const error = new HttpError(status, message); + expect([ + error.status, + error.name, + error.message, + error.toString(), + ]).toEqual([status, name, message, `${name}: ${message}`]); + } + }); + + it("names unknown statuses by class of error", () => { + expect(new HttpError(499).name).toBe("Unknown Client Error"); + expect(new HttpError(599).name).toBe("Unknown Server Error"); + }); + + it("overrides the name for every error status", () => { + const message = "something went wrong"; + for (const status of statusRange) { + const error = new HttpError(status, message, { name: "CustomError" }); + expect([error.status, error.name, error.toString()]).toEqual([ + status, + "CustomError", + `CustomError: ${message}`, + ]); + } + }); + + it("exposes only client errors by default", () => { + for (const status of statusRange) { + expect(new HttpError(status).expose).toBe(status < 500); + } + }); + + it("lets expose be overridden for every error status", () => { + for (const status of statusRange) { + const expose = status >= 500; + expect(new HttpError(status, { expose }).expose).toBe(expose); + } + }); + + describe("with all options", () => { + const cause = new Error("fail"); + const extensions = { key: "value" }; + + function allOptions(): HttpErrorOptions { + const headers = new Headers({ "X-Custom-Header": "TestValue" }); + headers.set("content-type", "application/custom+json"); + return { + name: "CustomErrorName", + message: "A very specific error occurred.", + status: 418, + statusText: "I'm a teapot", + expose: false, + cause, + type: "/errors/custom-operation-failed", + instance: "/errors/custom-operation-failed/instance/123xyz", + extensions, + headers, + }; + } + + it.each([ + { + signature: "status, message, options", + create: (options: HttpErrorOptions) => + new HttpError(418, "A very specific error occurred.", options), + }, + { + signature: "status, options", + create: (options: HttpErrorOptions) => + new HttpError(418, options), + }, + { + signature: "message, options", + create: ({ + message, + ...options + }: HttpErrorOptions) => + new HttpError(message, { ...options, status: 418 }), + }, + { + signature: "options", + create: (options: HttpErrorOptions) => + new HttpError(options), + }, + ])("constructor with $signature signature", ({ create }) => { + const error = create(allOptions()); + expect(error.name).toBe("CustomErrorName"); + expect(error.message).toBe("A very specific error occurred."); + expect(error.status).toBe(418); + expect(error.statusText).toBe("I'm a teapot"); + expect(error.expose).toBe(false); + expect(error.cause).toBe(cause); + expect(error.type).toBe("/errors/custom-operation-failed"); + expect(error.instance).toBe( + "/errors/custom-operation-failed/instance/123xyz", + ); + expect(error.extensions).toEqual(extensions); + expect(error.headers.get("X-Custom-Header")).toBe("TestValue"); + expect(error.headers.get("content-type")).toBe("application/custom+json"); + expect(error.toString()).toBe( + "CustomErrorName: A very specific error occurred.", + ); + }); + }); + + describe("headers", () => { + it("accepts a plain object and defaults the content type", () => { + const error = new HttpError({ + headers: { + "X-Custom-Test": "Value", + "X-Another-Header": "AnotherValue", + }, + }); + expect(error.headers.get("X-Custom-Test")).toBe("Value"); + expect(error.headers.get("X-Another-Header")).toBe("AnotherValue"); + expect(error.headers.get("content-type")).toBe( + "application/problem+json", + ); + }); + + it("keeps a content type given in a plain object", () => { + const error = new HttpError({ + headers: { + "X-Custom-Test": "Value", + "content-type": "application/custom+json", + }, + }); + expect(error.headers.get("X-Custom-Test")).toBe("Value"); + expect(error.headers.get("content-type")).toBe("application/custom+json"); + }); + + it("uses a Headers instance as given", () => { + const headers = new Headers({ "X-Custom-Test": "Value" }); + const error = new HttpError({ headers }); + expect(error.headers === headers).toBe(true); + expect(headers.get("content-type")).toBe("application/problem+json"); + }); + + it("defaults the content type to problem+json", () => { + expect(new HttpError().headers.get("content-type")).toBe( + "application/problem+json", + ); + }); + }); + + it("keeps other data in extensions", () => { + const cause = new Error("fail"); + const data = { x: 2, y: 3 }; + const errors = [ + new HttpError(400, "something went wrong", { + name: "CustomError", + expose: false, + cause, + extensions: data, + }), + new HttpError(400, { + name: "CustomError", + message: "something went wrong", + expose: false, + cause, + extensions: data, + }), + new HttpError("something went wrong", { + name: "CustomError", + status: 400, + expose: false, + cause, + extensions: data, + }), + new HttpError({ + name: "CustomError", + message: "something went wrong", + status: 400, + expose: false, + cause, + extensions: data, + }), + ]; + for (const error of errors) { + expect(error.toString()).toBe("CustomError: something went wrong"); + expect(error.name).toBe("CustomError"); + expect(error.message).toBe("something went wrong"); + expect(error.status).toBe(400); + expect(error.expose).toBe(false); + expect(error.cause).toBe(cause); + expect(error.extensions).toEqual(data); + } + }); + + describe("exposedMessage", () => { + it("uses the message when the error is exposed", () => { + const error = new HttpError(400, "Invalid email format"); + expect(error.message).toBe("Invalid email format"); + expect(error.exposedMessage).toBe("Invalid email format"); + expect(error.toJSON().detail).toBe("Invalid email format"); + }); + + it("uses the status default when an exposed error has no message", () => { + expect(new HttpError(404).exposedMessage).toBe( + "The requested resource could not be found.", + ); + }); + + it("uses the status default when the error is not exposed", () => { + const error = new HttpError(500, "SQL syntax error near 'DROP TABLE'"); + expect(error.exposedMessage).toBe( + "The server encountered an unexpected condition.", + ); + expect(error.toJSON().detail).toBe( + "The server encountered an unexpected condition.", + ); + }); + + it("uses the status default for a client error with expose false", () => { + const error = new HttpError(400, "private detail", { expose: false }); + expect(error.exposedMessage).toBe( + "The server cannot process the request due to a client error.", + ); + }); + + it("exposes the message of a server error with expose true", () => { + const error = new HttpError(503, "Down for maintenance", { + expose: true, + }); + expect(error.exposedMessage).toBe("Down for maintenance"); + }); + + it("has a default message for every known error status", () => { + for (const status of statusRange) { + expect(new HttpError(status).exposedMessage).toBe( + defaultExposedMessage(status), + ); + } + }); + + it("falls back to a generic message for unknown statuses", () => { + expect(new HttpError(499).exposedMessage).toBe( + "A client error occurred.", + ); + expect(new HttpError(599).exposedMessage).toBe( + "A server error occurred.", + ); + }); + + it("prefers an explicit exposedMessage regardless of expose", () => { + const exposed = new HttpError(400, "Internal validation details", { + exposedMessage: "Please check your input.", + }); + expect(exposed.exposedMessage).toBe("Please check your input."); + expect(exposed.toJSON().detail).toBe("Please check your input."); + + const hidden = new HttpError(500, "Database connection refused", { + exposedMessage: "Service temporarily unavailable.", + }); + expect(hidden.exposedMessage).toBe("Service temporarily unavailable."); + expect(hidden.toJSON().detail).toBe("Service temporarily unavailable."); + }); + }); + + describe("toJSON", () => { + it("hides the message of an error converted from a plain Error", () => { + expect(HttpError.from(new Error("fail")).toJSON()).toEqual({ + title: "Internal Server Error", + status: 500, + detail: "The server encountered an unexpected condition.", + }); + }); + + it("exposes a client error with extensions", () => { + const data = { x: 2, y: 3 }; + const error = new HttpError(400, "something went wrong", { + name: "CustomError", + cause: new Error("fail"), + extensions: data, + }); + expect(error.toJSON()).toEqual({ + ...data, + title: "CustomError", + detail: "something went wrong", + status: 400, + }); + }); + + it("hides the message of a server error with extensions", () => { + const data = { x: 2, y: 3 }; + const error = new HttpError(500, "something went wrong", { + name: "CustomError", + cause: new Error("fail"), + extensions: data, + }); + expect(error.toJSON()).toEqual({ + ...data, + title: "CustomError", + status: 500, + detail: "The server encountered an unexpected condition.", + }); + }); + + it("hides the message of a client error that is explicitly not exposed", () => { + const data = { x: 2, y: 3 }; + const error = new HttpError(400, "something went wrong", { + name: "CustomError", + expose: false, + cause: new Error("fail"), + extensions: data, + }); + expect(error.toJSON()).toEqual({ + ...data, + title: "CustomError", + status: 400, + detail: "The server cannot process the request due to a client error.", + }); + }); + + it("omits type and instance when they are not set", () => { + const json = new HttpError(404, "missing").toJSON(); + expect(Object.keys(json).toSorted()).toEqual([ + "detail", + "status", + "title", + ]); + }); + + it("includes type and instance when they are set", () => { + const json = new HttpError(404, "missing", { + type: "/errors/missing", + instance: "/things/1", + }).toJSON(); + expect(json).toEqual({ + status: 404, + title: "Not Found", + detail: "missing", + type: "/errors/missing", + instance: "/things/1", + }); + }); + + it("lets the standard members override same-named extensions", () => { + const error = new HttpError(400, "real detail", { + extensions: { status: 200, title: "spoofed", detail: "spoofed" }, + }); + expect(error.toJSON()).toEqual({ + status: 400, + title: "Bad Request", + detail: "real detail", + }); + }); + + it("is used by JSON.stringify", () => { + const error = new HttpError(409, "conflict", { extensions: { id: 7 } }); + expect(JSON.parse(JSON.stringify(error))).toEqual({ + id: 7, + status: 409, + title: "Conflict", + detail: "conflict", + }); + }); + }); + + describe("getResponse", () => { + it("builds a response for an exposed client error with extensions, type, and instance", async () => { + const extensions = { extra: "data1" }; + const error = new HttpError(400, "Client Issue", { + name: "MyClientError", + extensions, + type: "/err/client", + instance: "/err/client/1", + }); + const response = error.getResponse(); + expect(response.status).toBe(400); + expect(response.headers.get("content-type")).toBe( + "application/problem+json", + ); + expect(await response.json()).toEqual({ + ...extensions, + status: 400, + title: "MyClientError", + detail: "Client Issue", + type: "/err/client", + instance: "/err/client/1", + }); + }); + + it("builds a response for a hidden server error with custom statusText and headers", async () => { + const extensions = { extra: "data2" }; + const headers = new Headers({ "X-RateLimit-Limit": "100" }); + headers.set("content-type", "application/vnd.api+json"); + const error = new HttpError(503, "Server Down", { + name: "CustomServerErr", + statusText: "Service Really Unavailable", + expose: false, + extensions, + headers, + }); + const response = error.getResponse(); + expect(response.status).toBe(503); + expect(response.statusText).toBe("Service Really Unavailable"); + expect(response.headers.get("content-type")).toBe( + "application/vnd.api+json", + ); + expect(response.headers.get("X-RateLimit-Limit")).toBe("100"); + expect(await response.json()).toEqual({ + ...extensions, + status: 503, + title: "CustomServerErr", + detail: "The server is currently unavailable.", + }); + }); + + it("defaults the content type and omits extensions", async () => { + const response = new HttpError(404, "Not Found").getResponse(); + expect(response.headers.get("content-type")).toBe( + "application/problem+json", + ); + expect(await response.json()).toEqual({ + status: 404, + title: "Not Found", + detail: "Not Found", + }); + }); + + it("leaves the response statusText empty when none is set", () => { + expect(new HttpError(404).getResponse().statusText).toBe(""); + }); + + it("round-trips through HttpError.from", async () => { + const error = new HttpError(422, "bad field", { + type: "/errors/validation", + instance: "/forms/1", + extensions: { field: "email" }, + }); + const roundTripped = await HttpError.from(error.getResponse()); + expect(roundTripped.status).toBe(422); + expect(roundTripped.name).toBe("Unprocessable Entity"); + expect(roundTripped.message).toBe("bad field"); + expect(roundTripped.type).toBe("/errors/validation"); + expect(roundTripped.instance).toBe("/forms/1"); + expect(roundTripped.extensions).toEqual({ field: "email" }); + }); + }); + + describe("from", () => { + it("wraps a plain Error in a 500 HttpError with the error as cause", () => { + const cause = new Error("fail"); + const error = HttpError.from(cause); + expect(error.toString()).toBe("Internal Server Error: fail"); + expect(error.name).toBe("Internal Server Error"); + expect(error.message).toBe("fail"); + expect(error.status).toBe(500); + expect(error.expose).toBe(false); + expect(error.cause).toBe(cause); + expect(error.exposedMessage).toBe( + "The server encountered an unexpected condition.", + ); + }); + + it("converts an Error with a status, keeping its name", () => { + const original = Object.assign(new Error("fail"), { status: 400 }); + const error = HttpError.from(original); + expect(error).toBeInstanceOf(HttpError); + expect(error.toString()).toBe("Error: fail"); + expect(error.name).toBe("Error"); + expect(error.message).toBe("fail"); + expect(error.status).toBe(400); + expect(error.expose).toBe(true); + expect(error.cause).toBeUndefined(); + }); + + it("converts the old status-text-plus-Error name to the human-readable name", () => { + const original = Object.assign(new Error("gone"), { + name: "NotFoundError", + status: 404, + }); + expect(HttpError.from(original).name).toBe("Not Found"); + }); + + it("keeps a custom name that does not match the old format for its status", () => { + const original = Object.assign(new Error("gone"), { + name: "NotFoundError", + status: 410, + }); + expect(HttpError.from(original).name).toBe("NotFoundError"); + }); + + it("keeps an empty Error name as is", () => { + const original = Object.assign(new Error("odd"), { + name: "", + status: 404, + }); + expect(HttpError.from(original).name).toBe(""); + }); + + it("keeps a name for a status without status text", () => { + const original = Object.assign(new Error("odd"), { + name: "Error", + status: 499, + }); + expect(HttpError.from(original).name).toBe("Error"); + }); + + it("carries over the HttpError-like fields of an Error with a status", () => { + const cause = new Error("root"); + const original = Object.assign(new Error("fail"), { + status: 429, + expose: false, + cause, + type: "/errors/rate-limit", + instance: "/limits/1", + extensions: { retryAfter: 30 }, + exposedMessage: "Slow down.", + headers: new Headers({ "Retry-After": "30" }), + }); + const error = HttpError.from(original); + expect(error.status).toBe(429); + expect(error.expose).toBe(false); + expect(error.cause).toBe(cause); + expect(error.type).toBe("/errors/rate-limit"); + expect(error.instance).toBe("/limits/1"); + expect(error.extensions).toEqual({ retryAfter: 30 }); + expect(error.exposedMessage).toBe("Slow down."); + expect(error.headers.get("Retry-After")).toBe("30"); + }); + + it("returns an HttpError instance as is", () => { + const original = new HttpError({ + name: "CustomError", + message: "something went wrong", + cause: new Error("fail"), + extensions: { x: 2, y: 3 }, + }); + expect(HttpError.from(original) === original).toBe(true); + }); + + it.each([ + ["null", null], + ["undefined", undefined], + ["a number", 12345], + ["a boolean", true], + ["a string", "boom"], + ["a symbol", Symbol("test-symbol")], + ["an unrelated object", { completely: "unrelated object" }], + ["a function", () => {}], + ["an empty object", {}], + ])("wraps %s as an unexpected error type", (_label, input) => { + const error = HttpError.from(input); + expect(error).toBeInstanceOf(HttpError); + expect(error.status).toBe(500); + expect(error.message).toBe("unexpected error type"); + expect(error.cause).toBe(input); + }); + + it("converts a ProblemDetails object", () => { + const problemDetails = { + status: 404, + title: "ResourceNotFound", + detail: "The requested resource was not found.", + type: "/errors/not-found", + instance: "/items/123", + anotherKey: "anotherValue", + }; + const error = HttpError.from<{ anotherKey: string }>(problemDetails); + expect(error.status).toBe(404); + expect(error.name).toBe("ResourceNotFound"); + expect(error.message).toBe("The requested resource was not found."); + expect(error.type).toBe("/errors/not-found"); + expect(error.instance).toBe("/items/123"); + expect(error.extensions).toEqual({ anotherKey: "anotherValue" }); + expect(error.expose).toBe(true); + }); + + it("recognizes a ProblemDetails object that only has a title or type", () => { + expect(HttpError.from({ title: "Only Title", status: 400 }).name).toBe( + "Only Title", + ); + const byType = HttpError.from({ type: "/errors/only-type", status: 400 }); + expect(byType.type).toBe("/errors/only-type"); + }); + + describe("Response", () => { + it("converts a ProblemDetails body", async () => { + const problemDetails = { + status: 403, + title: "ForbiddenError", + detail: "Access denied", + type: "/errors/forbidden", + instance: "/errors/forbidden/123", + customField: "customValue", + }; + const response = new Response(JSON.stringify(problemDetails), { + status: 403, + headers: { "Content-Type": "application/problem+json" }, + }); + const error = await HttpError.from(response); + expect(error.status).toBe(403); + expect(error.name).toBe("ForbiddenError"); + expect(error.message).toBe("Access denied"); + expect(error.type).toBe("/errors/forbidden"); + expect(error.instance).toBe("/errors/forbidden/123"); + expect(error.extensions).toEqual({ customField: "customValue" }); + expect(error.expose).toBe(true); + }); + + it("prefers the status in the body over the response status", async () => { + const response = new Response( + JSON.stringify({ + status: 403, + title: "ForbiddenError", + detail: "Access denied", + }), + { + status: 400, + headers: { "Content-Type": "application/problem+json" }, + }, + ); + const error = await HttpError.from(response); + expect(error.status).toBe(403); + expect(error.name).toBe("ForbiddenError"); + expect(error.message).toBe("Access denied"); + }); + + it("uses the response status when the body has none", async () => { + const response = new Response( + JSON.stringify({ title: "BadRequest", detail: "Bad input" }), + { + status: 400, + headers: { "Content-Type": "application/problem+json" }, + }, + ); + const error = await HttpError.from(response); + expect(error.status).toBe(400); + expect(error.name).toBe("BadRequest"); + expect(error.message).toBe("Bad input"); + }); + + it("reports a body that is not valid JSON", async () => { + const response = new Response("not json {{{{", { status: 500 }); + const error = await HttpError.from(response); + expect(error.status).toBe(500); + expect(error.message).toBe("could not parse problem details response"); + expect(error.cause).toBeInstanceOf(SyntaxError); + }); + + it("reports a body that was already consumed", async () => { + const response = new Response("{}", { status: 500 }); + await response.text(); + const error = await HttpError.from(response); + expect(error.status).toBe(500); + expect(error.message).toBe("could not parse problem details response"); + expect(error.cause).toBeInstanceOf(TypeError); + }); + + it("reports JSON that is not ProblemDetails", async () => { + const payload = { data: "some other structure" }; + const response = new Response(JSON.stringify(payload), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + const error = await HttpError.from(response); + expect(error.status).toBe(500); + expect(error.message).toBe("invalid problem details response"); + expect(error.cause).toEqual(payload); + }); + + it("reports a JSON body that is not an object", async () => { + const error = await HttpError.from( + new Response("null", { status: 502 }), + ); + expect(error.status).toBe(500); + expect(error.message).toBe("invalid problem details response"); + expect(error.cause).toBeNull(); + }); + }); + }); +}); + +describe("createHttpErrorClass", () => { + it("creates a basic custom error class without default options", () => { + const BasicError = createHttpErrorClass(); + const error = new BasicError(400, "test message"); + expect(error).toBeInstanceOf(HttpError); + expect(error).toBeInstanceOf(BasicError); + expect(error.status).toBe(400); + expect(error.message).toBe("test message"); + expect(error.name).toBe("Bad Request"); + + const defaults = new BasicError(); + expect(defaults.status).toBe(500); + expect(defaults.message).toBe(""); + expect(defaults.name).toBe("Internal Server Error"); + }); + + it("creates a custom error class with default name and status", () => { + const DefaultNameStatusError = createHttpErrorClass({ + name: "MyTestError", + status: 499, + }); + + const byMessage = new DefaultNameStatusError("A message"); + expect(byMessage.status).toBe(499); + expect(byMessage.name).toBe("MyTestError"); + expect(byMessage.message).toBe("A message"); + + const byStatus = new DefaultNameStatusError(498, "Override status"); + expect(byStatus.status).toBe(498); + expect(byStatus.name).toBe("MyTestError"); + expect(byStatus.message).toBe("Override status"); + + const byOptions = new DefaultNameStatusError({ + message: "Via options", + name: "OverriddenName", + }); + expect(byOptions.status).toBe(499); + expect(byOptions.name).toBe("OverriddenName"); + expect(byOptions.message).toBe("Via options"); + + const byStatusOption = new DefaultNameStatusError({ status: 497 }); + expect(byStatusOption.status).toBe(497); + expect(byStatusOption.name).toBe("MyTestError"); + expect(byStatusOption.message).toBe(""); + }); + + it("merges default extensions with instance extensions", () => { + interface MyExt { + defaultKey: string; + anotherKey?: number; + yetAnotherKey?: string; + } + const ExtError = createHttpErrorClass({ + extensions: { defaultKey: "defaultValue", yetAnotherKey: "stillHere" }, + }); + + const defaults = new ExtError("msg"); + expect(defaults.extensions.defaultKey).toBe("defaultValue"); + expect(defaults.extensions.yetAnotherKey).toBe("stillHere"); + expect(defaults.extensions.anotherKey).toBeUndefined(); + + const merged = new ExtError("msg", { + extensions: { defaultKey: "override", anotherKey: 123 }, + }); + expect(merged.extensions.defaultKey).toBe("override"); + expect(merged.extensions.anotherKey).toBe(123); + expect((merged.extensions as MyExt).yetAnotherKey).toBe("stillHere"); + + const byOptions = new ExtError({ + extensions: { + anotherKey: 456, + defaultKey: "defaultValue", + yetAnotherKey: "stillHere", + }, + }); + expect(byOptions.extensions.defaultKey).toBe("defaultValue"); + expect(byOptions.extensions.anotherKey).toBe(456); + expect(byOptions.extensions.yetAnotherKey).toBe("stillHere"); + expect(byOptions.message).toBe(""); + expect(byOptions.status).toBe(500); + + const WithStatus = createHttpErrorClass({ + status: 420, + extensions: { defaultKey: "val" }, + }); + const withStatus = new WithStatus(); + expect(withStatus.status).toBe(420); + expect(withStatus.extensions.defaultKey).toBe("val"); + }); + + it("does not share default extensions between instances", () => { + const SharedDefaults = createHttpErrorClass<{ + list: string[]; + id?: string; + }>({ + extensions: { list: [] }, + }); + const first = new SharedDefaults({ extensions: { list: [], id: "a" } }); + const second = new SharedDefaults(); + expect(second.extensions.id).toBeUndefined(); + expect(first.extensions).not.toBe(second.extensions); + }); + + describe("with all possible default options", () => { + interface AllExt { + id: string; + type?: string; + newProp?: string; + } + const defaultHeaders = new Headers({ "X-Default": "true" }); + const defaultCause = new Error("Default cause"); + + const AllDefaultsError = createHttpErrorClass({ + name: "AllDefaultsErrorName", + status: 488, + message: "Default message for error", + expose: true, + statusText: "Default Status Text Here", + type: "/errors/all-defaults-type", + instance: "/errors/all-defaults-instance/0", + extensions: { id: "default-id", type: "default-ext-type" }, + headers: defaultHeaders, + cause: defaultCause, + exposedMessage: "Default exposed message", + }); + + it("applies every default when instantiated without arguments", () => { + const error = new AllDefaultsError(); + expect(error.name).toBe("AllDefaultsErrorName"); + expect(error.status).toBe(488); + expect(error.message).toBe("Default message for error"); + expect(error.expose).toBe(true); + expect(error.statusText).toBe("Default Status Text Here"); + expect(error.type).toBe("/errors/all-defaults-type"); + expect(error.instance).toBe("/errors/all-defaults-instance/0"); + expect(error.extensions.id).toBe("default-id"); + expect(error.extensions.type).toBe("default-ext-type"); + expect(error.headers.get("X-Default")).toBe("true"); + expect(error.cause).toBe(defaultCause); + expect(error.exposedMessage).toBe("Default exposed message"); + }); + + it("lets status and message arguments override the defaults", () => { + const error = new AllDefaultsError(489, "New message from args"); + expect(error.status).toBe(489); + expect(error.message).toBe("New message from args"); + expect(error.name).toBe("AllDefaultsErrorName"); + expect(error.extensions.id).toBe("default-id"); + }); + + it("lets an options object override some defaults and keep the rest", () => { + const instanceHeaders = new Headers({ "X-Instance": "live" }); + const instanceCause = new Error("Instance cause here"); + const error = new AllDefaultsError({ + name: "InstanceNameOverride", + message: "Instance message in options", + expose: false, + extensions: { id: "instance-id-override", newProp: "instanceValue" }, + headers: instanceHeaders, + cause: instanceCause, + type: "/errors/instance-type-override", + exposedMessage: "Instance exposed message", + }); + expect(error.name).toBe("InstanceNameOverride"); + expect(error.status).toBe(488); + expect(error.message).toBe("Instance message in options"); + expect(error.expose).toBe(false); + expect(error.statusText).toBe("Default Status Text Here"); + expect(error.type).toBe("/errors/instance-type-override"); + expect(error.instance).toBe("/errors/all-defaults-instance/0"); + expect(error.extensions.id).toBe("instance-id-override"); + expect((error.extensions as AllExt).type).toBe("default-ext-type"); + expect(error.extensions.newProp).toBe("instanceValue"); + expect(error.headers.get("X-Instance")).toBe("live"); + expect(error.headers.has("X-Default")).toBe(false); + expect(error.cause).toBe(instanceCause); + expect(error.exposedMessage).toBe("Instance exposed message"); + }); + + it("lets an instance override the default instance", () => { + const error = new AllDefaultsError({ instance: "/instances/9" }); + expect(error.instance).toBe("/instances/9"); + }); + + describe("statusText overrides", () => { + it("uses statusText from the options", () => { + const error = new AllDefaultsError({ + statusText: "Overridden Status Text Via Options", + }); + expect(error.statusText).toBe("Overridden Status Text Via Options"); + expect(error.status).toBe(488); + expect(error.name).toBe("AllDefaultsErrorName"); + }); + + it("uses statusText from the options when a status argument is present", () => { + const error = new AllDefaultsError(490, { + statusText: "Custom StatusText with Status Arg", + }); + expect(error.status).toBe(490); + expect(error.statusText).toBe("Custom StatusText with Status Arg"); + }); + + it("uses statusText from the options when a message argument is present", () => { + const error = new AllDefaultsError("A message", { + statusText: "Custom StatusText with Message Arg", + }); + expect(error.message).toBe("A message"); + expect(error.statusText).toBe("Custom StatusText with Message Arg"); + expect(error.status).toBe(488); + }); + + it("uses statusText from the options when status and message arguments are present", () => { + const error = new AllDefaultsError(491, "A full message", { + statusText: "Custom StatusText with Full Args", + }); + expect(error.status).toBe(491); + expect(error.message).toBe("A full message"); + expect(error.statusText).toBe("Custom StatusText with Full Args"); + }); + + it("keeps the class default statusText when the options have none", () => { + const error = new AllDefaultsError({ + message: "Message without status text override", + }); + expect(error.statusText).toBe("Default Status Text Here"); + expect(new AllDefaultsError().statusText).toBe( + "Default Status Text Here", + ); + }); + }); + }); + + it("creates instances that behave like HttpError for toJSON and getResponse", async () => { + interface RespExt { + code: number; + field?: string; + defaultInfo?: string; + } + const RespError = createHttpErrorClass({ + name: "RespTestError", + status: 422, + extensions: { code: 100, defaultInfo: "from_default" }, + expose: true, + }); + + const error = new RespError("Validation failed", { + extensions: { code: 101, field: "email" }, + type: "/errors/validation", + instance: "/user/123/email", + }); + + const json = error.toJSON(); + expect(json).toEqual({ + status: 422, + title: "RespTestError", + detail: "Validation failed", + type: "/errors/validation", + instance: "/user/123/email", + code: 101, + field: "email", + defaultInfo: "from_default", + }); + + const response = error.getResponse(); + expect(response.status).toBe(422); + expect(response.headers.get("content-type")).toBe( + "application/problem+json", + ); + expect(await response.json()).toEqual(json); + + const NonExposedError = createHttpErrorClass({ + name: "SecretError", + status: 500, + expose: false, + }); + const secretError = new NonExposedError("This is a secret detail", { + extensions: { secretCode: "xyz" }, + }); + const secretJson = secretError.toJSON(); + expect(secretJson).toEqual({ + status: 500, + title: "SecretError", + detail: "The server encountered an unexpected condition.", + secretCode: "xyz", + }); + expect(await secretError.getResponse().json()).toEqual(secretJson); + }); + + it("produces classes that can be extended", () => { + const BaseError = createHttpErrorClass({ status: 409, name: "BaseError" }); + class ConflictError extends BaseError {} + const error = new ConflictError("clash"); + expect(error).toBeInstanceOf(BaseError); + expect(error).toBeInstanceOf(HttpError); + expect(error.status).toBe(409); + expect(error.toString()).toBe("BaseError: clash"); + }); +}); + +describe("isHttpErrorLike", () => { + it("is true for HttpError instances", () => { + expect(isHttpErrorLike(new HttpError(400, "Bad Request"))).toBe(true); + }); + + it("is true for an Error with a numeric status", () => { + const error = new Error("Bad Request"); + expect(isHttpErrorLike(error)).toBe(false); + Object.assign(error, { status: 400 }); + expect(isHttpErrorLike(error)).toBe(true); + }); + + it("is true for an Error subclass with a numeric status", () => { + class CustomHttpError extends Error { + status = 400; + } + expect(isHttpErrorLike(new CustomHttpError("bad request"))).toBe(true); + }); + + it("is false for an Error without a numeric status", () => { + expect(isHttpErrorLike(new Error("Bad Request"))).toBe(false); + expect( + isHttpErrorLike(Object.assign(new Error("x"), { status: "400" })), + ).toBe(false); + }); + + it.each([ + ["a plain object with a status", { status: 400, message: "Bad Request" }], + ["null", null], + ["undefined", undefined], + ["a string", "error"], + ["a number", 400], + ])("is false for %s", (_label, value) => { + expect(isHttpErrorLike(value)).toBe(false); + }); +}); diff --git a/mod.ts b/src/mod.ts similarity index 90% rename from mod.ts rename to src/mod.ts index b82248f..bbe0548 100644 --- a/mod.ts +++ b/src/mod.ts @@ -1,4 +1,4 @@ -import { STATUS_CODE, STATUS_TEXT, type StatusCode } from "@std/http/status"; +import { STATUS_CODE, STATUS_TEXT, type StatusCode } from "./status.ts"; /** * Default user-friendly messages for HTTP error status codes. @@ -54,8 +54,10 @@ const DEFAULT_EXPOSED_MESSAGES: Partial> = { * Falls back to generic client/server error messages for unknown status codes. */ function defaultExposedMessageForStatus(status: number): string { - return DEFAULT_EXPOSED_MESSAGES[status] ?? - (status < 500 ? "A client error occurred." : "A server error occurred."); + return ( + DEFAULT_EXPOSED_MESSAGES[status] ?? + (status < 500 ? "A client error occurred." : "A server error occurred.") + ); } /** Options for initializing an HttpError. */ @@ -117,13 +119,13 @@ export type ProblemDetails< Extensions extends object = Record, > = ProblemDetailsBase & Extensions; -function isProblemDetails< - Extensions extends object = Record, ->(value: unknown): value is ProblemDetails { - return typeof value === "object" && value !== null && ( - "status" in value || - "title" in value || - "type" in value +function isProblemDetails>( + value: unknown, +): value is ProblemDetails { + return ( + typeof value === "object" && + value !== null && + ("status" in value || "title" in value || "type" in value) ); } @@ -169,11 +171,9 @@ function isProblemDetails< * @param options - The options. * @returns The options object. */ -function optionsFromArgs< - Extensions extends object = Record, ->( - statusOrMessageOrOptions?: number | string | (HttpErrorOptions), - messageOrOptions?: string | (HttpErrorOptions), +function optionsFromArgs>( + statusOrMessageOrOptions?: number | string | HttpErrorOptions, + messageOrOptions?: string | HttpErrorOptions, options?: HttpErrorOptions, ): HttpErrorOptions { let status: number | undefined = undefined; @@ -200,7 +200,7 @@ function optionsFromArgs< message = init?.message; } - return { ...init, status, message } as HttpErrorOptions; + return { ...init, status, message }; } /** @@ -455,11 +455,10 @@ export class HttpError< if (statusText) this.statusText = statusText; if (type) this.type = type; if (instance) this.instance = instance; - this.expose = expose ?? (status < 500); - this.extensions = extensions ?? {} as Extensions; - this.headers = headers instanceof Headers - ? headers - : new Headers(headers ?? {}); + this.expose = expose ?? status < 500; + this.extensions = extensions ?? ({} as Extensions); + this.headers = + headers instanceof Headers ? headers : new Headers(headers ?? {}); if (!this.headers.has("content-type")) { this.headers.set("content-type", "application/problem+json"); } @@ -467,7 +466,8 @@ export class HttpError< // 1. Explicitly provided exposedMessage option // 2. If expose=true and message exists, use message // 3. Default message for status code - this.exposedMessage = exposedMessage ?? + this.exposedMessage = + exposedMessage ?? (this.expose && this.message ? this.message : defaultExposedMessageForStatus(status)); @@ -491,33 +491,18 @@ export class HttpError< * @param error - The error to convert. * @returns An HttpError object. */ - static from< - Extensions extends object = Record, - >( + static from>( error: HttpError | ProblemDetails, ): HttpError; - static from< - Extensions extends object = Record, - >( + static from>( error: Response, ): Promise>; - static from( - error: Error, - ): HttpError; - static from< - Extensions extends object = Record, - >( + static from(error: Error): HttpError; + static from>( error: unknown, ): HttpError; - static from< - Extensions extends object = Record, - >( - error: - | HttpError - | Error - | ProblemDetails - | Response - | unknown, + static from>( + error: unknown, ): HttpError | Promise> { if (error instanceof HttpError) { return error; @@ -556,7 +541,8 @@ export class HttpError< cause: error, }) as unknown as HttpError; } else if (error instanceof Response) { - return error.json() + return error + .json() .then((json: unknown) => { if (!isProblemDetails(json)) { return new HttpError( @@ -582,7 +568,9 @@ export class HttpError< return new HttpError( 500, "could not parse problem details response", - { cause: parseError }, + { + cause: parseError, + }, ); }); } else if (isProblemDetails(error)) { @@ -680,10 +668,13 @@ export function isHttpErrorLike< >( value: unknown, ): value is HttpError | (Error & { status: number }) { - return typeof value === "object" && value !== null && + return ( + typeof value === "object" && + value !== null && (value instanceof HttpError || (value instanceof Error && - typeof (value as HttpError).status === "number")); + typeof (value as HttpError).status === "number")) + ); } /** @@ -790,41 +781,50 @@ export function createHttpErrorClass< const finalOptions: HttpErrorOptions = {}; - finalOptions.status = constructorTimeOptions.status !== undefined - ? constructorTimeOptions.status - : dfo?.status; - - finalOptions.message = constructorTimeOptions.message !== undefined - ? constructorTimeOptions.message - : dfo?.message; - - finalOptions.name = constructorTimeOptions.name !== undefined - ? constructorTimeOptions.name - : dfo?.name; - - finalOptions.expose = constructorTimeOptions.expose !== undefined - ? constructorTimeOptions.expose - : dfo?.expose; - - finalOptions.cause = constructorTimeOptions.cause !== undefined - ? constructorTimeOptions.cause - : dfo?.cause; - - finalOptions.type = constructorTimeOptions.type !== undefined - ? constructorTimeOptions.type - : dfo?.type; - - finalOptions.instance = constructorTimeOptions.instance !== undefined - ? constructorTimeOptions.instance - : dfo?.instance; - - finalOptions.headers = constructorTimeOptions.headers !== undefined - ? constructorTimeOptions.headers - : dfo?.headers; - - finalOptions.statusText = constructorTimeOptions.statusText !== undefined - ? constructorTimeOptions.statusText - : dfo?.statusText; + finalOptions.status = + constructorTimeOptions.status !== undefined + ? constructorTimeOptions.status + : dfo?.status; + + finalOptions.message = + constructorTimeOptions.message !== undefined + ? constructorTimeOptions.message + : dfo?.message; + + finalOptions.name = + constructorTimeOptions.name !== undefined + ? constructorTimeOptions.name + : dfo?.name; + + finalOptions.expose = + constructorTimeOptions.expose !== undefined + ? constructorTimeOptions.expose + : dfo?.expose; + + finalOptions.cause = + constructorTimeOptions.cause !== undefined + ? constructorTimeOptions.cause + : dfo?.cause; + + finalOptions.type = + constructorTimeOptions.type !== undefined + ? constructorTimeOptions.type + : dfo?.type; + + finalOptions.instance = + constructorTimeOptions.instance !== undefined + ? constructorTimeOptions.instance + : dfo?.instance; + + finalOptions.headers = + constructorTimeOptions.headers !== undefined + ? constructorTimeOptions.headers + : dfo?.headers; + + finalOptions.statusText = + constructorTimeOptions.statusText !== undefined + ? constructorTimeOptions.statusText + : dfo?.statusText; finalOptions.exposedMessage = constructorTimeOptions.exposedMessage !== undefined @@ -832,8 +832,8 @@ export function createHttpErrorClass< : dfo?.exposedMessage; finalOptions.extensions = { - ...(dfo?.extensions), - ...(constructorTimeOptions.extensions), + ...dfo?.extensions, + ...constructorTimeOptions.extensions, } as Extensions; super(finalOptions); diff --git a/src/status.test.ts b/src/status.test.ts new file mode 100644 index 0000000..03df6fa --- /dev/null +++ b/src/status.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; + +import { STATUS_CODE, STATUS_TEXT } from "./status.ts"; + +describe("status table", () => { + it("has text for exactly the codes in STATUS_CODE", () => { + const codes = Object.values(STATUS_CODE).toSorted((a, b) => a - b); + const textKeys = Object.keys(STATUS_TEXT) + .map(Number) + .toSorted((a, b) => a - b); + expect(textKeys).toEqual(codes); + }); + + it("has unique codes", () => { + const codes = Object.values(STATUS_CODE); + expect(new Set(codes).size).toBe(codes.length); + }); + + it.each([ + ["OK", 200, "OK"], + ["NotFound", 404, "Not Found"], + ["Teapot", 418, "I'm a teapot"], + ["ContentTooLarge", 413, "Content Too Large"], + ["InternalServerError", 500, "Internal Server Error"], + ["NetworkAuthenticationRequired", 511, "Network Authentication Required"], + ] as const)("maps %s to %i and %s", (name, code, text) => { + expect(STATUS_CODE[name]).toBe(code); + expect(STATUS_TEXT[code]).toBe(text); + }); +}); diff --git a/src/status.ts b/src/status.ts new file mode 100644 index 0000000..b99a413 --- /dev/null +++ b/src/status.ts @@ -0,0 +1,205 @@ +// The status tables below are adapted from @std/http/status +// (https://jsr.io/@std/http/doc/status), Copyright 2018-2025 the Deno +// authors, MIT license. They are inlined so this package has no dependencies +// and no runtime-specific imports. + +/** Standard HTTP status codes by name. */ +export const STATUS_CODE = { + /** RFC 7231, 6.2.1 */ + Continue: 100, + /** RFC 7231, 6.2.2 */ + SwitchingProtocols: 101, + /** RFC 2518, 10.1 */ + Processing: 102, + /** RFC 8297 **/ + EarlyHints: 103, + + /** RFC 7231, 6.3.1 */ + OK: 200, + /** RFC 7231, 6.3.2 */ + Created: 201, + /** RFC 7231, 6.3.3 */ + Accepted: 202, + /** RFC 7231, 6.3.4 */ + NonAuthoritativeInfo: 203, + /** RFC 7231, 6.3.5 */ + NoContent: 204, + /** RFC 7231, 6.3.6 */ + ResetContent: 205, + /** RFC 7233, 4.1 */ + PartialContent: 206, + /** RFC 4918, 11.1 */ + MultiStatus: 207, + /** RFC 5842, 7.1 */ + AlreadyReported: 208, + /** RFC 3229, 10.4.1 */ + IMUsed: 226, + + /** RFC 7231, 6.4.1 */ + MultipleChoices: 300, + /** RFC 7231, 6.4.2 */ + MovedPermanently: 301, + /** RFC 7231, 6.4.3 */ + Found: 302, + /** RFC 7231, 6.4.4 */ + SeeOther: 303, + /** RFC 7232, 4.1 */ + NotModified: 304, + /** RFC 7231, 6.4.5 */ + UseProxy: 305, + /** RFC 7231, 6.4.7 */ + TemporaryRedirect: 307, + /** RFC 7538, 3 */ + PermanentRedirect: 308, + + /** RFC 7231, 6.5.1 */ + BadRequest: 400, + /** RFC 7235, 3.1 */ + Unauthorized: 401, + /** RFC 7231, 6.5.2 */ + PaymentRequired: 402, + /** RFC 7231, 6.5.3 */ + Forbidden: 403, + /** RFC 7231, 6.5.4 */ + NotFound: 404, + /** RFC 7231, 6.5.5 */ + MethodNotAllowed: 405, + /** RFC 7231, 6.5.6 */ + NotAcceptable: 406, + /** RFC 7235, 3.2 */ + ProxyAuthRequired: 407, + /** RFC 7231, 6.5.7 */ + RequestTimeout: 408, + /** RFC 7231, 6.5.8 */ + Conflict: 409, + /** RFC 7231, 6.5.9 */ + Gone: 410, + /** RFC 7231, 6.5.10 */ + LengthRequired: 411, + /** RFC 7232, 4.2 */ + PreconditionFailed: 412, + /** RFC 7231, 6.5.11 */ + ContentTooLarge: 413, + /** RFC 7231, 6.5.12 */ + URITooLong: 414, + /** RFC 7231, 6.5.13 */ + UnsupportedMediaType: 415, + /** RFC 7233, 4.4 */ + RangeNotSatisfiable: 416, + /** RFC 7231, 6.5.14 */ + ExpectationFailed: 417, + /** RFC 7168, 2.3.3 */ + Teapot: 418, + /** RFC 7540, 9.1.2 */ + MisdirectedRequest: 421, + /** RFC 4918, 11.2 */ + UnprocessableEntity: 422, + /** RFC 4918, 11.3 */ + Locked: 423, + /** RFC 4918, 11.4 */ + FailedDependency: 424, + /** RFC 8470, 5.2 */ + TooEarly: 425, + /** RFC 7231, 6.5.15 */ + UpgradeRequired: 426, + /** RFC 6585, 3 */ + PreconditionRequired: 428, + /** RFC 6585, 4 */ + TooManyRequests: 429, + /** RFC 6585, 5 */ + RequestHeaderFieldsTooLarge: 431, + /** RFC 7725, 3 */ + UnavailableForLegalReasons: 451, + + /** RFC 7231, 6.6.1 */ + InternalServerError: 500, + /** RFC 7231, 6.6.2 */ + NotImplemented: 501, + /** RFC 7231, 6.6.3 */ + BadGateway: 502, + /** RFC 7231, 6.6.4 */ + ServiceUnavailable: 503, + /** RFC 7231, 6.6.5 */ + GatewayTimeout: 504, + /** RFC 7231, 6.6.6 */ + HTTPVersionNotSupported: 505, + /** RFC 2295, 8.1 */ + VariantAlsoNegotiates: 506, + /** RFC 4918, 11.5 */ + InsufficientStorage: 507, + /** RFC 5842, 7.2 */ + LoopDetected: 508, + /** RFC 2774, 7 */ + NotExtended: 510, + /** RFC 6585, 6 */ + NetworkAuthenticationRequired: 511, +} as const; + +/** An HTTP status code. */ +export type StatusCode = (typeof STATUS_CODE)[keyof typeof STATUS_CODE]; + +/** The standard text for each HTTP status code. */ +export const STATUS_TEXT = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 103: "Early Hints", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non Authoritative Info", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi Status", + 208: "Already Reported", + 226: "IM Used", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Auth Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Content Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", + 421: "Misdirected Request", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required", +} as const; diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..79a454f --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "rootDir": "src", + "outDir": "dist", + "declaration": true, + "lib": ["ES2023", "DOM"], + "types": [] + }, + "include": ["src"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..57f873a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": ["node"], + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "declaration": true, + "isolatedDeclarations": true, + "isolatedModules": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "paths": { "@udibo/http-error": ["./src/mod.ts"] }, + "noEmit": true + }, + "include": ["src", "examples", "scripts", "vitest.config.ts"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..ee4f592 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,23 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig, type ViteUserConfig } from "vitest/config"; + +const config: ViteUserConfig = defineConfig({ + resolve: { + alias: { + "@udibo/http-error": fileURLToPath( + new URL("./src/mod.ts", import.meta.url), + ), + }, + }, + test: { + include: ["src/**/*.test.ts", "examples/**/*.test.ts"], + coverage: { + provider: "v8", + reporter: ["text", "lcov"], + include: ["src/**/*.ts", "examples/**/*.ts"], + exclude: ["**/*.test.ts"], + }, + }, +}); + +export default config;