diff --git a/dev-packages/e2e-tests/test-applications/hono-4-legacy/package.json b/dev-packages/e2e-tests/test-applications/hono-4-legacy/package.json index 1864b16c27f4..99f96170110f 100644 --- a/dev-packages/e2e-tests/test-applications/hono-4-legacy/package.json +++ b/dev-packages/e2e-tests/test-applications/hono-4-legacy/package.json @@ -9,7 +9,8 @@ "dev:bun": "bun src/entry.bun.ts", "dev:deno": "deno run --allow-net --allow-env --allow-read src/entry.deno.ts", "build": "wrangler deploy --dry-run", - "test:build": "pnpm install && pnpm build", + "test:build": "pnpm install && pnpm typecheck && pnpm build", + "typecheck": "tsc --noEmit -p tsconfig.typecheck.json", "test:assert": "TEST_ENV=production playwright test" }, "dependencies": { diff --git a/dev-packages/e2e-tests/test-applications/hono-4-legacy/src/sentry.typecheck.ts b/dev-packages/e2e-tests/test-applications/hono-4-legacy/src/sentry.typecheck.ts new file mode 100644 index 000000000000..9df86b4b4b85 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/hono-4-legacy/src/sentry.typecheck.ts @@ -0,0 +1,19 @@ +// Only `tsc` in `test:build` reads this file. Nothing runs it. +import { sentry as sentryBun } from '@sentry/hono/bun'; +import { sentry as sentryCloudflare } from '@sentry/hono/cloudflare'; +import { sentry as sentryDeno } from '@sentry/hono/deno'; +import { sentry as sentryNode } from '@sentry/hono/node'; +import { Hono } from 'hono'; + +type AppEnv = { Bindings: { E2E_TEST_DSN: string }; Variables: { user: string } }; + +const dsn = 'https://public@dsn.ingest.sentry.io/1337'; + +const app = new Hono(); +app.use(sentryNode(app)); +app.use(sentryBun(app, { dsn })); +app.use(sentryDeno(app, { dsn })); +app.use(sentryCloudflare(app, env => ({ dsn: env.E2E_TEST_DSN }))); + +const api = app.basePath('/api'); +api.use(sentryNode(api)); diff --git a/dev-packages/e2e-tests/test-applications/hono-4-legacy/tsconfig.typecheck.json b/dev-packages/e2e-tests/test-applications/hono-4-legacy/tsconfig.typecheck.json new file mode 100644 index 000000000000..9131a4be8ad3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/hono-4-legacy/tsconfig.typecheck.json @@ -0,0 +1,5 @@ +{ + // The rest of `src` needs Node, Bun and Deno types, which this app does not install. + "extends": "./tsconfig.json", + "include": ["src/*.typecheck.ts"] +} diff --git a/dev-packages/e2e-tests/test-applications/hono-4/package.json b/dev-packages/e2e-tests/test-applications/hono-4/package.json index 17467a9fe90e..82f47a8b7368 100644 --- a/dev-packages/e2e-tests/test-applications/hono-4/package.json +++ b/dev-packages/e2e-tests/test-applications/hono-4/package.json @@ -8,7 +8,8 @@ "dev:bun": "bun run dist/entry.bun.js", "dev:deno": "deno run --allow-net --allow-env --allow-read --preload=npm:@sentry/deno/import src/entry.deno.ts", "dev:cloudflare": "wrangler dev --config ./dist/hono_4/wrangler.json --var \"E2E_TEST_DSN:$E2E_TEST_DSN\" --log-level=$(test $CI && echo 'none' || echo 'log')", - "test:build": "pnpm install", + "test:build": "pnpm install && pnpm typecheck", + "typecheck": "tsc --noEmit -p tsconfig.typecheck.json", "test": "TEST_ENV=production playwright test", "test:assert": "pnpm test:assert:node", "test:assert:node": "RUNTIME=node pnpm test", diff --git a/dev-packages/e2e-tests/test-applications/hono-4/src/honoMiddleware.typecheck.ts b/dev-packages/e2e-tests/test-applications/hono-4/src/honoMiddleware.typecheck.ts new file mode 100644 index 000000000000..396d90942d11 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/hono-4/src/honoMiddleware.typecheck.ts @@ -0,0 +1,12 @@ +// Only `tsc` in `test:build` reads this file. Nothing runs it. +import * as Sentry from '@sentry/node'; +import { Hono } from 'hono'; + +type AppEnv = { Bindings: { E2E_TEST_DSN: string }; Variables: { user: string } }; + +const app = new Hono(); +app.use(Sentry.honoMiddleware(app)); +app.use('*', Sentry.honoMiddleware(app)); + +const api = app.basePath('/api'); +api.use(Sentry.honoMiddleware(api)); diff --git a/dev-packages/e2e-tests/test-applications/hono-4/tsconfig.typecheck.json b/dev-packages/e2e-tests/test-applications/hono-4/tsconfig.typecheck.json new file mode 100644 index 000000000000..9131a4be8ad3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/hono-4/tsconfig.typecheck.json @@ -0,0 +1,5 @@ +{ + // The rest of `src` needs Node, Bun and Deno types, which this app does not install. + "extends": "./tsconfig.json", + "include": ["src/*.typecheck.ts"] +} diff --git a/packages/hono/package.json b/packages/hono/package.json index 80db3ccd0369..24ab8672f17b 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -110,7 +110,8 @@ "@sentry/deno": "11.9.0", "@sentry/node": "11.9.0", "@types/node": "^20.19.0", - "hono": "^4.13.7", + "hono": "4.13.9", + "hono-5": "npm:hono@5.0.0-rc.1", "wrangler": "4.62.0" }, "scripts": { diff --git a/packages/hono/test/types.test-d.ts b/packages/hono/test/types.test-d.ts new file mode 100644 index 000000000000..3d069bd46138 --- /dev/null +++ b/packages/hono/test/types.test-d.ts @@ -0,0 +1,57 @@ +import { honoMiddleware } from '@sentry/server-utils'; +import { Hono } from 'hono'; +import type { Hono as Hono5 } from 'hono-5'; +import { describe, it } from 'vitest'; +import { sentry as sentryBun } from '../src/bun/middleware'; +import { sentry as sentryCloudflare } from '../src/cloudflare/middleware'; +import { sentry as sentryDeno } from '../src/deno/middleware'; +import { sentry as sentryNode } from '../src/node/middleware'; + +type AppEnv = { Bindings: { SENTRY_DSN: string }; Variables: { user: string } }; + +declare const app5: Hono5; + +const dsn = 'https://public@dsn.ingest.sentry.io/1337'; + +describe('Hono 4', () => { + it('accepts honoMiddleware(app)', () => { + const app = new Hono(); + app.use(honoMiddleware(app)); + + const api = app.basePath('/api'); + api.use(honoMiddleware(api)); + }); + + it('accepts sentry(app) from each runtime entry', () => { + const app = new Hono(); + app.use(sentryNode(app)); + app.use(sentryBun(app, { dsn })); + app.use(sentryDeno(app, { dsn })); + app.use(sentryCloudflare(app, env => ({ dsn: env.SENTRY_DSN }))); + + const api = app.basePath('/api'); + api.use(sentryNode(api)); + }); +}); + +describe('Hono 5', () => { + it('accepts honoMiddleware(app)', () => { + app5.use(honoMiddleware(app5)); + + const api = app5.basePath('/api'); + api.use(honoMiddleware(api)); + }); + + // In this package, `sentry()` gets its `Hono` type from Hono 4. That class has private members, so a + // Hono 5 app cannot match it. In a real Hono 5 app, `sentry()` gets the Hono 5 types. + it('rejects sentry(app) here, because `hono` is Hono 4 in this package', () => { + // @ts-expect-error -- Hono 5 app, Hono 4 types + app5.use(sentryNode(app5)); + // @ts-expect-error -- Hono 5 app, Hono 4 types + app5.use(sentryBun(app5, { dsn })); + // @ts-expect-error -- Hono 5 app, Hono 4 types + app5.use(sentryDeno(app5, { dsn })); + // @ts-expect-error -- Hono 5 app, Hono 4 types + app5.use(sentryCloudflare(app5, env => ({ dsn: env.SENTRY_DSN }))); + }); +}); diff --git a/packages/hono/vite.config.ts b/packages/hono/vite.config.ts index b2150cd225a4..bac0e296db14 100644 --- a/packages/hono/vite.config.ts +++ b/packages/hono/vite.config.ts @@ -3,4 +3,12 @@ import baseConfig from '../../vite/vite.config'; export default defineConfig({ ...baseConfig, + test: { + ...baseConfig.test, + typecheck: { + enabled: true, + tsconfig: './tsconfig.test.json', + ignoreSourceErrors: true, + }, + }, }); diff --git a/packages/server-utils/.oxlintrc.json b/packages/server-utils/.oxlintrc.json index d9e07c9dd0cd..001bf4e0b298 100644 --- a/packages/server-utils/.oxlintrc.json +++ b/packages/server-utils/.oxlintrc.json @@ -31,6 +31,16 @@ "name": "diagnostics_channel", "allowTypeImports": true, "message": "Bun garbage-collects a channel that no code references, together with its subscribers. Import the wrappers from `src/utils/diagnosticsChannel.ts` instead. See https://github.com/oven-sh/bun/issues/43086" + }, + { + "name": "hono", + "message": "`@sentry/server-utils` has no `hono` dependency and must work on every Hono major. Use the vendored types in `src/integrations/hono/honoTypes.ts`." + } + ], + "patterns": [ + { + "group": ["hono/*"], + "message": "`@sentry/server-utils` has no `hono` dependency and must work on every Hono major. Use the vendored types in `src/integrations/hono/honoTypes.ts`." } ] } diff --git a/packages/server-utils/src/integrations/hono/honoIntegration.ts b/packages/server-utils/src/integrations/hono/honoIntegration.ts index b66fe6b50de4..575462d6d018 100644 --- a/packages/server-utils/src/integrations/hono/honoIntegration.ts +++ b/packages/server-utils/src/integrations/hono/honoIntegration.ts @@ -4,7 +4,7 @@ import { HTTP_SERVER } from '@sentry/conventions/op'; import type { IntegrationFn } from '@sentry/core'; import { defineIntegration, startInactiveSpan } from '@sentry/core'; import type { HonoRuntime } from './connInfo'; -import type { Env, Hono, MiddlewareHandler } from './honoTypes'; +import type { Env, Hono, MatchedHandler, MiddlewareHandler } from './honoTypes'; import type { OrchestrionChannelContext } from '../../orchestrion/types'; import { CHANNELS } from '../../orchestrion/channels'; import { honoModuleNames } from '../../orchestrion/config/hono'; @@ -13,7 +13,8 @@ import { bindTracingChannelToSpan, safeChannelCallback } from '../../tracing-cha import { applyPatches } from './applyPatches'; import { INTERNAL_REQUEST_ORIGIN } from './constants'; import { createHonoRequestMiddleware } from './createHonoMiddleware'; -import { isMiddleware } from './isMiddleware'; +import { getMiddlewareFlags } from './isMiddleware'; +import { isMatchedHandlerList } from './matchResult'; import { extractPathname, isInternalRequestSpanActive } from './patchAppRequest'; import { wrapMiddlewareWithSpan } from './wrapMiddlewareSpan'; import type { SentryHonoMiddlewareOptions } from './types'; @@ -49,10 +50,14 @@ export function honoMiddleware(app: Hono, options: HonoIntegra }); } -// A Hono `matchResult[0]` entry: `[[handler, routeMeta], paramIndexMap]` — `compose` runs the -// handler (`entry[0][0]`) and the `matchedRoutes` getter reads `routeMeta` (`entry[0][1]`). -// oxlint-disable-next-line typescript/no-explicit-any -type MatchedHandlerEntry = [[any, any], any]; +function wrapMatchedMiddleware(handlers: MatchedHandler[]): void { + const middlewareFlags = getMiddlewareFlags(handlers.map(([[, route]]) => route)); + for (const [i, [pair]] of handlers.entries()) { + if (middlewareFlags[i]) { + pair[0] = wrapMiddlewareWithSpan(pair[0]); + } + } +} /** * Per-request Context hook. `#dispatch` builds `new Context(req, { matchResult })` before its @@ -69,47 +74,19 @@ function injectHonoInstrumentation( requestMiddleware: MiddlewareHandler, injectedHandlerLists: WeakSet, ): void { - const ctorOptions = message.arguments?.[1] as { matchResult?: [MatchedHandlerEntry[], unknown] } | undefined; + const ctorOptions = message.arguments?.[1] as { matchResult?: [unknown[], unknown] } | undefined; const handlers = ctorOptions?.matchResult?.[0]; if (!Array.isArray(handlers) || injectedHandlerLists.has(handlers)) { return; } injectedHandlerLists.add(handlers); - // Classify matched handlers with the same positional heuristic as `wrapSubAppMiddleware`: within a - // method+path group the last handler is the route handler and earlier ones are middleware; `.use()` - // registers as method 'ALL' with a lone genuine-middleware entry, so fall back to arity there. - // Position is needed because a route handler declared with an unused `next` param has middleware arity. - const lastIndexByKey = new Map(); - for (const [i, entry] of handlers.entries()) { - const routeMeta = entry?.[0]?.[1] as { method?: string; path?: string } | undefined; - if (routeMeta?.method != null && routeMeta.path != null) { - // \0 is a collision-free delimiter: it cannot appear in a valid HTTP method or URL path. - lastIndexByKey.set(`${routeMeta.method}\0${routeMeta.path}`, i); - } - } - - for (const [i, entry] of handlers.entries()) { - const pair = entry?.[0]; - const handler = pair?.[0]; - if (typeof handler !== 'function') { - continue; - } - - const routeMeta = pair?.[1] as { method?: string; path?: string } | undefined; - const isMW = - routeMeta?.method != null && routeMeta.path != null - ? lastIndexByKey.get(`${routeMeta.method}\0${routeMeta.path}`) !== i || - (routeMeta.method === 'ALL' && isMiddleware(handler)) - : isMiddleware(handler); - - if (isMW) { - pair[0] = wrapMiddlewareWithSpan(handler as MiddlewareHandler); - } + if (isMatchedHandlerList(handlers)) { + wrapMatchedMiddleware(handlers); } - // Prepend the Sentry request/response middleware. `routeMeta` is what the `matchedRoutes` getter - // exposes; its middleware arity keeps route-name resolution from picking it. + // Prepend the Sentry request/response middleware. Its arity marks it as a middleware, so its `/*` + // never becomes the route name. const routeMeta = { basePath: '/', path: '/*', method: 'ALL', handler: requestMiddleware }; handlers.unshift([[requestMiddleware, routeMeta], {}]); } diff --git a/packages/server-utils/src/integrations/hono/honoTypes.ts b/packages/server-utils/src/integrations/hono/honoTypes.ts index eadbe688e42a..ce8661a073f8 100644 --- a/packages/server-utils/src/integrations/hono/honoTypes.ts +++ b/packages/server-utils/src/integrations/hono/honoTypes.ts @@ -29,15 +29,16 @@ export interface HonoRoute { handler: (...args: any[]) => any; } +// One entry of Hono's `matchResult[0]`. +export type MatchedHandler = [pair: [handler: HonoRoute['handler'], route: HonoRoute], paramIndexMap: unknown]; + export interface HonoRequest { raw: Request; method: string; path: string; routeIndex: number; - // These are public (though deprecated) getters on Hono's request. Reading them avoids a runtime - // import of the `hono/route` helpers, which are their non-deprecated replacements. - matchedRoutes: HonoRoute[]; - routePath: string; + // Deprecated getter that Hono 5 removes. + matchedRoutes?: HonoRoute[]; [key: string]: any; } diff --git a/packages/server-utils/src/integrations/hono/isMiddleware.ts b/packages/server-utils/src/integrations/hono/isMiddleware.ts index 9c18e43e8602..ee7ad8d08afa 100644 --- a/packages/server-utils/src/integrations/hono/isMiddleware.ts +++ b/packages/server-utils/src/integrations/hono/isMiddleware.ts @@ -1,3 +1,5 @@ +import type { HonoRoute } from './honoTypes'; + // Hono stores the original (unwrapped) handler under this key when it wraps a sub-app's handlers // for a custom `onError`. Unwrapping it lets us inspect the real handler instead of the wrapper. // See https://github.com/honojs/hono/blob/9f0dadf141a3242a6c3b77462c7d33c6ce0f599d/src/hono-base.ts#L224-L226 @@ -12,7 +14,7 @@ const COMPOSED_HANDLER = '__COMPOSED_HANDLER'; * https://github.com/honojs/hono/blob/97c6fe1f12298c715eb7b2da65b4b6e0d81682bb/src/utils/handler.ts#L8 * * This is only one signal — callers that must classify inline middleware sharing a method+path with - * their handler (e.g. `wrapSubAppMiddleware`) additionally need positional information. + * their handler additionally need positional information (`getMiddlewareFlags`). */ export function isMiddleware(handler: unknown): boolean { if (typeof handler !== 'function') { @@ -24,3 +26,19 @@ export function isMiddleware(handler: unknown): boolean { return (original as (...args: unknown[]) => unknown).length >= 2; } + +/** + * Tells for each route if it is a middleware. Hono has no flag for this. In a method+path group, the + * last handler is the route handler (`app.get('/path', mw, handler)`). `.use()` adds middleware with + * method `ALL` and no route handler, so for `ALL` routes the arity also counts. Arity alone is not + * enough, because a route handler can declare an unused `next`. + */ +export function getMiddlewareFlags(routes: HonoRoute[]): boolean[] { + // `\0` cannot appear in an HTTP method or a URL path. + const groupKey = (route: HonoRoute): string => `${route.method}\0${route.path}`; + const lastIndexByKey = new Map(routes.map((route, i) => [groupKey(route), i])); + + return routes.map( + (route, i) => lastIndexByKey.get(groupKey(route)) !== i || (route.method === 'ALL' && isMiddleware(route.handler)), + ); +} diff --git a/packages/server-utils/src/integrations/hono/matchResult.ts b/packages/server-utils/src/integrations/hono/matchResult.ts new file mode 100644 index 000000000000..e6bd298f1206 --- /dev/null +++ b/packages/server-utils/src/integrations/hono/matchResult.ts @@ -0,0 +1,26 @@ +import { isPlainObject } from '@sentry/core'; +import type { MatchedHandler } from './honoTypes'; + +function isMatchedHandler(entry: unknown): entry is MatchedHandler { + const pair: unknown = Array.isArray(entry) ? entry[0] : undefined; + if (!Array.isArray(pair)) { + return false; + } + + const [handler, route]: unknown[] = pair; + return ( + typeof handler === 'function' && + isPlainObject(route) && + typeof route.method === 'string' && + typeof route.path === 'string' && + typeof route.handler === 'function' + ); +} + +/** + * Checks the entry shape that `compose` and `hono/route` read. The match result is a Hono internal, so + * the SDK leaves other shapes alone. + */ +export function isMatchedHandlerList(handlers: unknown): handlers is MatchedHandler[] { + return Array.isArray(handlers) && handlers.every(isMatchedHandler); +} diff --git a/packages/server-utils/src/integrations/hono/patchRoute.ts b/packages/server-utils/src/integrations/hono/patchRoute.ts index 4f13b1b760ad..398df72fb924 100644 --- a/packages/server-utils/src/integrations/hono/patchRoute.ts +++ b/packages/server-utils/src/integrations/hono/patchRoute.ts @@ -2,7 +2,7 @@ import { debug, getOriginalFunction } from '@sentry/core'; import type { WrappedFunction } from '@sentry/core'; import { DEBUG_BUILD } from '../../debug-build'; import type { Hono, HonoRoute } from './honoTypes'; -import { isMiddleware } from './isMiddleware'; +import { getMiddlewareFlags } from './isMiddleware'; import { patchAppRequest } from './patchAppRequest'; import { wrapMiddlewareWithSpan } from './wrapMiddlewareSpan'; @@ -103,31 +103,12 @@ export function installRouteHookOnPrototype(honoBaseProto: HonoBaseProto): Route } /** - * Identifies middleware handlers in a sub-app's flat routes array and wraps them in spans. - * - * Heuristics (since Hono has no "isMiddleware" flag): - * 1. Position: `app.get('/path', mw, handler)` produces entries with the same method+path. - * All but the LAST are middleware (they call `next()`). - * 2. Arity (# of params) for method 'ALL': `.use()` handlers always have 2+ params (context, next), - * while `.all()` route handlers typically have 1 (`context` only). - * See: https://github.com/honojs/hono/blob/18fe604c8cefc2628240651b1af219692e1918c1/src/hono-base.ts#L156-L168 + * Wraps the middleware handlers of a sub-app's routes in spans. */ export function wrapSubAppMiddleware(routes: HonoRoute[]): void { - const lastIndexByKey = new Map(); - for (const [i, route] of routes.entries()) { - // \0 (null byte) is a collision-free delimiter: it cannot appear in a valid HTTP method name or URL path - lastIndexByKey.set(`${route.method}\0${route.path}`, i); - } - + const middlewareFlags = getMiddlewareFlags(routes); for (const [i, route] of routes.entries()) { - if (typeof route.handler !== 'function') { - continue; - } - - const isLastForGroup = lastIndexByKey.get(`${route.method}\0${route.path}`) === i; - - const isMW = !isLastForGroup || (route.method === 'ALL' && isMiddleware(route.handler)); - if (isMW) { + if (middlewareFlags[i] && typeof route.handler === 'function') { route.handler = wrapMiddlewareWithSpan(route.handler); } } diff --git a/packages/server-utils/src/orchestrion/config/hono.ts b/packages/server-utils/src/orchestrion/config/hono.ts index eda1a8084ff3..f56ff5a12039 100644 --- a/packages/server-utils/src/orchestrion/config/hono.ts +++ b/packages/server-utils/src/orchestrion/config/hono.ts @@ -8,6 +8,8 @@ const honoInstrumentationConfig: InstrumentationConfig[] = [ { // `new Context()` runs once per request: where the Sentry middleware is injected and matched // middleware handlers are wrapped for spans. + // TODO(v12): Raise the minimum Hono version to 4.4.9, here and in the `@sentry/hono` peer range. + // Before 4.4.9, Hono passes `matchResult` to `new HonoRequest()`, so this hook does nothing. channelName: 'context', module: { name: 'hono', versionRange: '>=4.0.0 <5', filePath: /^dist\/(?:cjs\/)?context\.js$/ }, functionQuery: { className: 'Context' }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20f62385c01c..cc9587b89d54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -363,7 +363,7 @@ importers: version: link:../../packages/hono hono: specifier: ^4.13.7 - version: 4.13.7 + version: 4.13.9 mysql: specifier: ^2.18.1 version: 2.18.1 @@ -486,7 +486,7 @@ importers: version: 6.0.218(zod@4.6.5) hono: specifier: ^4.13.7 - version: 4.13.7 + version: 4.13.9 langchain: specifier: ^1.5.16 version: 1.5.16(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.5))(ws@8.21.3))(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ws@8.21.3) @@ -615,7 +615,7 @@ importers: version: 21.3.10 '@hono/node-server': specifier: ^2.0.10 - version: 2.0.10(hono@4.13.7) + version: 2.0.10(hono@4.13.9) '@koa/router': specifier: ^12.0.1 version: 12.0.2(supports-color@10.0.0) @@ -729,7 +729,7 @@ importers: version: 2.0.1-rc.22(crossws@0.4.10(srvx@0.11.22)) hono: specifier: ^4.13.7 - version: 4.13.7 + version: 4.13.9 http-terminator: specifier: ^3.2.0 version: 3.2.0 @@ -1572,8 +1572,11 @@ importers: specifier: ^20.19.0 version: 20.19.43 hono: - specifier: ^4.13.7 - version: 4.13.7 + specifier: 4.13.9 + version: 4.13.9 + hono-5: + specifier: npm:hono@5.0.0-rc.1 + version: hono@5.0.0-rc.1 wrangler: specifier: 4.62.0 version: 4.62.0(@cloudflare/workers-types@4.20250922.0) @@ -12880,10 +12883,14 @@ packages: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} - hono@4.13.7: - resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==} + hono@4.13.9: + resolution: {integrity: sha512-7dMkQmZoC4E6F7AtaQSPhlWAdnBti+j7rreMZl8QB4jFiEhP9TWbGWUMi8WYzBCgmgulxuvLQupKqo+Co6Omyg==} engines: {node: '>=16.9.0'} + hono@5.0.0-rc.1: + resolution: {integrity: sha512-vTcfU9yAggXh/+ZSi8zpYbzYDubCgFnSt8d5afCAlgkd38dyYb3CVtOh8YWhS65NkEqBseCsTj/P8mceJ+7KNA==} + engines: {node: '>=22.12.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -23632,9 +23639,9 @@ snapshots: '@hapi/bourne': 3.0.0 '@hapi/hoek': 11.0.4 - '@hono/node-server@2.0.10(hono@4.13.7)': + '@hono/node-server@2.0.10(hono@4.13.9)': dependencies: - hono: 4.13.7 + hono: 4.13.9 '@humanwhocodes/config-array@0.11.14(supports-color@10.0.0)': dependencies: @@ -32195,7 +32202,9 @@ snapshots: dependencies: parse-passwd: 1.0.0 - hono@4.13.7: {} + hono@4.13.9: {} + + hono@5.0.0-rc.1: {} hookable@5.5.3: {}