From bf5e621183a29c416224e67bfaf9d1c6ac97704a Mon Sep 17 00:00:00 2001 From: Kyle June Date: Sat, 10 Oct 2026 00:21:49 -0400 Subject: [PATCH 1/5] chore: remove Deno configuration and the Oak example Deno is no longer a supported development runtime for this package, so the Deno workspace config, lockfile, editor settings, publish workflow, and the Oak example are removed ahead of the Node migration. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 50 ----------- .github/workflows/publish.yml | 16 ---- .vscode/launch.json | 23 ----- .vscode/settings.json | 10 --- deno.json | 35 -------- deno.lock | 156 ---------------------------------- examples/oak.test.ts | 71 ---------------- examples/oak.ts | 57 ------------- 8 files changed, 418 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/publish.yml delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json delete mode 100644 deno.json delete mode 100644 deno.lock delete mode 100644 examples/oak.test.ts delete mode 100644 examples/oak.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 273667b..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: CI -on: [push, pull_request] -jobs: - build: - name: test deno v2.x ${{ matrix.os }} - runs-on: ${{ matrix.os }} - timeout-minutes: 5 - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - fail-fast: true - steps: - - name: Clone repository - uses: actions/checkout@v4 - - name: Setup deno - uses: denoland/setup-deno@main - 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 - - name: Run tests - run: deno test -A --coverage - - name: Upload coverage - if: | - matrix.os == 'ubuntu-latest' - uses: codecov/codecov-action@v4 - 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 - run: | - echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV - - name: Release - uses: softprops/action-gh-release@v1 - if: env.RELEASE_VERSION != '' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - draft: true 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/.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/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/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); -} From 150bf2fbcd3c86ae77d7ec5fae88a3b72988ca94 Mon Sep 17 00:00:00 2001 From: Kyle June Date: Sat, 10 Oct 2026 00:21:56 -0400 Subject: [PATCH 2/5] feat: support Node.js and publish to npm Build the package with tsc into ESM JavaScript and type declarations under dist/ and publish it to npm as well as JSR (raw TypeScript). The source is erasable-only TypeScript with isolated declarations, so JSR accepts it without slow-type errors. The @std/http/status dependency is replaced by a typed status table inlined in src/status.ts, adapted from @std/http under its MIT license, so the package has no runtime dependencies and imports no Node.js modules. The public API of mod.ts is unchanged. Co-Authored-By: Claude Sonnet 5.5 --- .gitattributes | 1 + .gitignore | 3 + .nvmrc | 1 + .oxfmtrc.json | 5 + .oxlintrc.json | 17 + jsr.json | 9 + package.json | 74 + pnpm-lock.yaml | 4118 ++++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 5 + mod.ts => src/mod.ts | 168 +- src/status.ts | 205 +++ tsconfig.build.json | 13 + tsconfig.json | 24 + 13 files changed, 4559 insertions(+), 84 deletions(-) create mode 100644 .gitattributes create mode 100644 .nvmrc create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json create mode 100644 jsr.json create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml rename mod.ts => src/mod.ts (90%) create mode 100644 src/status.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json 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/.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/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/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/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.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"] +} From fead6db6e07628a238bc9bc8d036cd8b5fa1d22f Mon Sep 17 00:00:00 2001 From: Kyle June Date: Sat, 10 Oct 2026 00:22:03 -0400 Subject: [PATCH 3/5] test: port the suite to Vitest and test the Hono example in process Every behavioral assertion from the Deno suite is kept. Brittle assertions are tightened: the default-name table is explicit instead of derived from the status table, the response statusText assertion is unconditional, and the invalid JSON test no longer matches V8's error message text. Added coverage for the old error-name conversion, exposed message defaults, and the status table. The Hono example exports its app and is exercised with app.request() instead of a spawned server. Co-Authored-By: Claude Sonnet 5.5 --- examples/hono.test.ts | 73 +-- examples/hono.ts | 4 +- mod.test.ts | 1233 ---------------------------------------- src/mod.test.ts | 1260 +++++++++++++++++++++++++++++++++++++++++ src/status.test.ts | 30 + vitest.config.ts | 23 + 6 files changed, 1344 insertions(+), 1279 deletions(-) delete mode 100644 mod.test.ts create mode 100644 src/mod.test.ts create mode 100644 src/status.test.ts create mode 100644 vitest.config.ts 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/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/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/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/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; From fe2681878494bb78a243e25940f5438edc68234a Mon Sep 17 00:00:00 2001 From: Kyle June Date: Sat, 10 Oct 2026 00:22:03 -0400 Subject: [PATCH 4/5] ci: check, test, and release with GitHub Actions and semantic-release Run lint, formatting, type checks, and tests on Node 22, 24, and 26 on Linux and on Node 24 on Windows and macOS, upload coverage to Codecov, and smoke-test the packed npm tarball as a consumer would. Releases run semantic-release on main to publish to npm (OIDC trusted publishing with provenance) and JSR, and stay off until the RELEASE_ENABLED repository variable is true. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 128 +++++++++++++++++++++++++++ .releaserc.json | 35 ++++++++ PUBLISHING.md | 75 ++++++++++++++++ scripts/npm-smoke.ts | 184 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 422 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .releaserc.json create mode 100644 PUBLISHING.md create mode 100644 scripts/npm-smoke.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e6eaab5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,128 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + test: + name: Test on Node ${{ matrix.node }} (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + 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@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: Check lint, formatting, and types + run: pnpm check + - name: Run tests + if: ${{ !matrix.coverage }} + run: pnpm test + - name: Run tests with coverage + if: ${{ matrix.coverage }} + run: pnpm test:coverage + - name: Upload coverage + if: ${{ matrix.coverage }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6.0.2 + with: + files: coverage/lcov.info + 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: | + 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 }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: pnpm exec semantic-release 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/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/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(); From 9cae2ad2f2001245e6d71239c132046032d327e7 Mon Sep 17 00:00:00 2001 From: Kyle June Date: Sat, 10 Oct 2026 00:22:03 -0400 Subject: [PATCH 5/5] docs: document installation for npm, JSR, Node.js, and browsers Remove the Deno and Oak instructions and run the Hono example on Node. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 151 +++++++++++++++++++++++++++++------------------------- 1 file changed, 81 insertions(+), 70 deletions(-) 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 }); ```