diff --git a/lib/app-bootstrap.test.tsx b/lib/app-bootstrap.test.tsx index 69facc57e..980c9355e 100644 --- a/lib/app-bootstrap.test.tsx +++ b/lib/app-bootstrap.test.tsx @@ -8,6 +8,14 @@ vi.mock('@/utils/logger', () => ({ }, })); +vi.mock('@honeybadger-io/js', () => ({ + default: { + configure: vi.fn(), + notify: vi.fn(), + setContext: vi.fn(), + }, +})); + describe('app-bootstrap', () => { it('logs uncaught exceptions', async () => { const before = new Set(process.listeners('uncaughtException')); diff --git a/lib/app-bootstrap.tsx b/lib/app-bootstrap.tsx index c1bd50bc1..25468be68 100644 --- a/lib/app-bootstrap.tsx +++ b/lib/app-bootstrap.tsx @@ -10,6 +10,7 @@ import antiHotlink from '@/middleware/anti-hotlink'; import cache from '@/middleware/cache'; import debug from '@/middleware/debug'; import header from '@/middleware/header'; +import honeybadger from '@/middleware/honeybadger'; import mLogger from '@/middleware/logger'; import parameter from '@/middleware/parameter'; import sentry from '@/middleware/sentry'; @@ -35,6 +36,7 @@ app.use( ); app.use(mLogger); app.use(trace); +app.use(honeybadger); app.use(sentry); app.use(accessControl); app.use(debug); diff --git a/lib/app.worker.tsx b/lib/app.worker.tsx index d8c96848d..cd7045875 100644 --- a/lib/app.worker.tsx +++ b/lib/app.worker.tsx @@ -52,6 +52,7 @@ app.use(mLogger); app.use(trace); // Heavy middleware excluded in Worker build: +// - honeybadger: @honeybadger-io/js // - sentry: @sentry/node // - antiHotlink: cheerio // - parameter: cheerio, sanitize-html, @jocmp/mercury-parser diff --git a/lib/config.ts b/lib/config.ts index bd1e430bf..9ba567508 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -48,6 +48,8 @@ type ConfigEnvKeys = | 'OTEL_SECONDS_BUCKET' | 'OTEL_MILLISECONDS_BUCKET' | 'SHOW_LOGGER_TIMESTAMP' + | 'HONEYBADGER_API_KEY' + | 'ERROR_TRACKING_ROUTE_TIMEOUT' | 'SENTRY' | 'SENTRY_ROUTE_TIMEOUT' | 'ENABLE_REMOTE_DEBUGGING' @@ -306,10 +308,13 @@ export type Config = { milliseconds_bucket?: string; }; showLoggerTimestamp?: boolean; + honeybadger: { + apiKey?: string; + }; sentry: { dsn?: string; - routeTimeout: number; }; + errorTrackingRouteTimeout: number; enableRemoteDebugging?: boolean; // feed config hotlink: { @@ -798,10 +803,13 @@ const calculateValue = () => { milliseconds_bucket: envs.OTEL_MILLISECONDS_BUCKET || '10,20,50,100,250,500,1000,5000,15000', }, showLoggerTimestamp: toBoolean(envs.SHOW_LOGGER_TIMESTAMP, false), + honeybadger: { + apiKey: envs.HONEYBADGER_API_KEY, + }, sentry: { dsn: envs.SENTRY, - routeTimeout: toInt(envs.SENTRY_ROUTE_TIMEOUT, 30000), }, + errorTrackingRouteTimeout: toInt(envs.ERROR_TRACKING_ROUTE_TIMEOUT || envs.SENTRY_ROUTE_TIMEOUT, 30000), enableRemoteDebugging: toBoolean(envs.ENABLE_REMOTE_DEBUGGING, false), // feed config hotlink: { diff --git a/lib/errors/honeybadger.test.ts b/lib/errors/honeybadger.test.ts new file mode 100644 index 000000000..c59d26ceb --- /dev/null +++ b/lib/errors/honeybadger.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it, vi } from 'vitest'; + +const notify = vi.fn(); + +vi.mock('@honeybadger-io/js', () => ({ + default: { notify }, +})); + +vi.mock('@sentry/node', () => ({ + withScope: vi.fn(), + captureException: vi.fn(), +})); + +vi.mock('hono/route', () => ({ + routePath: () => '/test/path', +})); + +vi.mock('@/utils/logger', () => ({ + default: { + error: vi.fn(), + }, +})); + +vi.mock('@/utils/otel', () => ({ + requestMetric: { + error: vi.fn(), + }, +})); + +describe('error handler honeybadger', () => { + it('sends errors to honeybadger when enabled', async () => { + process.env.HONEYBADGER_API_KEY = 'hbp_test_key'; + vi.resetModules(); + + // Re-mock after resetModules + vi.doMock('@honeybadger-io/js', () => ({ + default: { notify }, + })); + vi.doMock('@sentry/node', () => ({ + withScope: vi.fn(), + captureException: vi.fn(), + })); + vi.doMock('hono/route', () => ({ + routePath: () => '/test/path', + })); + vi.doMock('@/utils/logger', () => ({ + default: { error: vi.fn() }, + })); + vi.doMock('@/utils/otel', () => ({ + requestMetric: { error: vi.fn() }, + })); + + const { errorHandler } = await import('@/errors'); + + const ctx = { + req: { + path: '/test/path', + method: 'GET', + query: () => 'json', + }, + res: { + status: 500, + headers: new Headers(), + }, + status: vi.fn(), + header: vi.fn(), + json: (payload: unknown) => payload, + html: (payload: unknown) => payload, + }; + + errorHandler(new Error('boom'), ctx as any); + + expect(notify).toHaveBeenCalledWith(expect.any(Error), { + context: { name: 'test' }, + }); + + delete process.env.HONEYBADGER_API_KEY; + }); +}); diff --git a/lib/errors/index.tsx b/lib/errors/index.tsx index c8ae927ed..b78603815 100644 --- a/lib/errors/index.tsx +++ b/lib/errors/index.tsx @@ -1,3 +1,4 @@ +import Honeybadger from '@honeybadger-io/js'; import * as Sentry from '@sentry/node'; import type { ErrorHandler, NotFoundHandler } from 'hono'; import { routePath } from 'hono/route'; @@ -36,6 +37,12 @@ export const errorHandler: ErrorHandler = (error, ctx) => { hasMatchedRoute && debug.errorRoutes[matchedRoute]++; setDebugInfo(debug); + if (config.honeybadger.apiKey) { + Honeybadger.notify(error, { + context: { name: requestPath.split('/')[1] }, + }); + } + if (config.sentry.dsn) { Sentry.withScope((scope) => { scope.setTag('name', requestPath.split('/')[1]); diff --git a/lib/middleware/honeybadger.test.ts b/lib/middleware/honeybadger.test.ts new file mode 100644 index 000000000..5c20ea4b7 --- /dev/null +++ b/lib/middleware/honeybadger.test.ts @@ -0,0 +1,69 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +afterEach(() => { + vi.resetModules(); + vi.restoreAllMocks(); + vi.unmock('@/config'); + vi.unmock('@/utils/helpers'); + vi.unmock('@/utils/logger'); + vi.unmock('@honeybadger-io/js'); +}); + +describe('honeybadger middleware', () => { + const loadMiddleware = async () => { + const honeybadger = { + configure: vi.fn(), + setContext: vi.fn(), + notify: vi.fn(), + }; + const logger = { + info: vi.fn(), + }; + const getRouteNameFromPath = vi.fn((path: string) => `route:${path}`); + + vi.doMock('@honeybadger-io/js', () => ({ + default: honeybadger, + })); + vi.doMock('@/utils/logger', () => ({ + default: logger, + })); + vi.doMock('@/utils/helpers', () => ({ + getRouteNameFromPath, + })); + vi.doMock('@/config', () => ({ + config: { + honeybadger: { + apiKey: 'hbp_test_key', + }, + errorTrackingRouteTimeout: 50, + nodeName: 'node-a', + }, + })); + + const { default: middleware } = await import('@/middleware/honeybadger'); + + return { middleware, honeybadger, logger, getRouteNameFromPath }; + }; + + it('initializes honeybadger and captures slow routes', async () => { + const { middleware, honeybadger, logger, getRouteNameFromPath } = await loadMiddleware(); + + expect(honeybadger.configure).toHaveBeenCalledWith({ + apiKey: 'hbp_test_key', + enableUncaught: false, + }); + expect(honeybadger.setContext).toHaveBeenCalledWith({ node_name: 'node-a' }); + expect(logger.info).toHaveBeenCalledWith('Honeybadger inited.'); + + const nowSpy = vi.spyOn(Date, 'now'); + nowSpy.mockReturnValueOnce(0).mockReturnValueOnce(100); + + await middleware({ req: { path: '/test/slow' } } as any, async () => {}); + + expect(getRouteNameFromPath).toHaveBeenCalledWith('/test/slow'); + expect(honeybadger.notify).toHaveBeenCalledTimes(1); + expect(honeybadger.notify).toHaveBeenCalledWith(expect.any(Error), { + context: { name: 'route:/test/slow' }, + }); + }); +}); diff --git a/lib/middleware/honeybadger.ts b/lib/middleware/honeybadger.ts new file mode 100644 index 000000000..bf7675f6e --- /dev/null +++ b/lib/middleware/honeybadger.ts @@ -0,0 +1,28 @@ +import Honeybadger from '@honeybadger-io/js'; +import type { MiddlewareHandler } from 'hono'; + +import { config } from '@/config'; +import { getRouteNameFromPath } from '@/utils/helpers'; +import logger from '@/utils/logger'; + +if (config.honeybadger.apiKey) { + Honeybadger.configure({ + apiKey: config.honeybadger.apiKey, + enableUncaught: false, + }); + Honeybadger.setContext({ node_name: config.nodeName }); + + logger.info('Honeybadger inited.'); +} + +const middleware: MiddlewareHandler = async (ctx, next) => { + const time = Date.now(); + await next(); + if (config.honeybadger.apiKey && Date.now() - time >= config.errorTrackingRouteTimeout) { + Honeybadger.notify(new Error('Route Timeout'), { + context: { name: getRouteNameFromPath(ctx.req.path) }, + }); + } +}; + +export default middleware; diff --git a/lib/middleware/sentry.test.ts b/lib/middleware/sentry.test.ts index 1b7273371..50c88bac0 100644 --- a/lib/middleware/sentry.test.ts +++ b/lib/middleware/sentry.test.ts @@ -36,8 +36,8 @@ describe('sentry middleware', () => { config: { sentry: { dsn: 'https://sentry.example/123', - routeTimeout: 50, }, + errorTrackingRouteTimeout: 50, nodeName: 'node-a', }, })); diff --git a/lib/middleware/sentry.ts b/lib/middleware/sentry.ts index 8c2952bbb..efc6f1679 100644 --- a/lib/middleware/sentry.ts +++ b/lib/middleware/sentry.ts @@ -17,7 +17,7 @@ if (config.sentry.dsn) { const middleware: MiddlewareHandler = async (ctx, next) => { const time = Date.now(); await next(); - if (config.sentry.dsn && Date.now() - time >= config.sentry.routeTimeout) { + if (config.sentry.dsn && Date.now() - time >= config.errorTrackingRouteTimeout) { Sentry.withScope((scope) => { scope.setTag('name', getRouteNameFromPath(ctx.req.path)); Sentry.captureException(new Error('Route Timeout')); diff --git a/lib/shims/honeybadger.ts b/lib/shims/honeybadger.ts new file mode 100644 index 000000000..2c3699c07 --- /dev/null +++ b/lib/shims/honeybadger.ts @@ -0,0 +1,6 @@ +// No-op shim for @honeybadger-io/js in Cloudflare Workers +export default { + configure: () => {}, + notify: () => {}, + setContext: () => {}, +}; diff --git a/package.json b/package.json index b3a98d668..6b408e8ba 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@bbob/html": "4.3.1", "@bbob/plugin-helper": "4.3.1", "@bbob/preset-html5": "4.3.1", + "@honeybadger-io/js": "6.12.3", "@hono/node-server": "1.19.12", "@hono/zod-openapi": "1.2.4", "@jocmp/mercury-parser": "3.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9bbc46d9a..94c9b36d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,9 @@ importers: '@bbob/preset-html5': specifier: 4.3.1 version: 4.3.1 + '@honeybadger-io/js': + specifier: 6.12.3 + version: 6.12.3 '@hono/node-server': specifier: 1.19.12 version: 1.19.12(hono@4.12.8) @@ -1074,6 +1077,15 @@ packages: peerDependencies: '@opentelemetry/api': ^1.9.0 + '@honeybadger-io/core@6.7.2': + resolution: {integrity: sha512-4+hyrFI0S/Eni2cBgO40Lqyft5hyNXjgnuh1EbeH457kty8g4YatJYuHBffmrjwLiZHgFbWXRGv7SlG+NehC5Q==} + engines: {node: '>=14'} + + '@honeybadger-io/js@6.12.3': + resolution: {integrity: sha512-CL+9A8tGpjawsJEKE74P94hmimHRsq+B90i5N/pXNgnJbgZG40bs9yZpfn2z3w0+3z9wtH12D2RqBjxsGm9DBg==} + engines: {node: '>=14'} + hasBin: true + '@hono/node-server@1.19.12': resolution: {integrity: sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==} engines: {node: '>=18.14.1'} @@ -2472,6 +2484,9 @@ packages: '@types/aes-js@3.1.4': resolution: {integrity: sha512-v3D66IptpUqh+pHKVNRxY8yvp2ESSZXe0rTzsGdzUhEwag7ljVfgCllkWv2YgiYXDhWFBrEywll4A5JToyTNFA==} + '@types/aws-lambda@8.10.161': + resolution: {integrity: sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==} + '@types/babel__preset-env@7.10.0': resolution: {integrity: sha512-LS8hRb/8TQir2f8W9/s5enDtrRS2F/6fsdkVw5ePHp6Q8SrSJHOGtWnP93ryaYMmg2du03vOsiGrl5mllz4uDA==} @@ -2481,6 +2496,9 @@ packages: '@types/bluebird@3.5.42': resolution: {integrity: sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/caseless@0.12.5': resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==} @@ -2514,6 +2532,12 @@ packages: '@types/etag@1.8.4': resolution: {integrity: sha512-f1z/UMth8gQ6636NBqhFmJ3zES7EuDcUnV6K1gl1osHp+85KPKX+VixYWUpqLkw1fftCagyHJjJOZjZkEi2rHw==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} @@ -2523,6 +2547,9 @@ packages: '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/js-beautify@1.14.3': resolution: {integrity: sha512-FMbQHz+qd9DoGvgLHxeqqVPaNRffpIu5ZjozwV8hf9JAGpIOzuAf4wGbRSo8LNITHqGjmmVjaMggTT5P4v4IHg==} @@ -2577,6 +2604,12 @@ packages: '@types/pg@8.15.6': resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/qs@6.15.0': + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/request-promise@4.1.51': resolution: {integrity: sha512-qVcP9Fuzh9oaAh8oPxiSoWMFGnWKkJDknnij66vi09Yiy62bsSDqtd+fG5kIM9wLLgZsRP3Y6acqj9O/v2ZtRw==} @@ -2586,6 +2619,12 @@ packages: '@types/sanitize-html@2.16.1': resolution: {integrity: sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==} + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -4355,6 +4394,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-nd@1.0.0: + resolution: {integrity: sha512-8TIp0HZAY0VVrwRQJJPb4+nOTSPoOWZeEKBTLizUfQO4oym5Fc/MKqN8vEbLCxcyxDf2vwNxOQ1q84O49GWPyQ==} + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -5419,6 +5461,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} @@ -5732,6 +5778,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + type-fest@3.13.1: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} @@ -6664,6 +6714,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@honeybadger-io/core@6.7.2': + dependencies: + json-nd: 1.0.0 + stacktrace-parser: 0.1.11 + + '@honeybadger-io/js@6.12.3': + dependencies: + '@honeybadger-io/core': 6.7.2 + '@types/aws-lambda': 8.10.161 + '@types/express': 5.0.6 + '@hono/node-server@1.19.12(hono@4.12.8)': dependencies: hono: 4.12.8 @@ -7895,12 +7956,19 @@ snapshots: '@types/aes-js@3.1.4': {} + '@types/aws-lambda@8.10.161': {} + '@types/babel__preset-env@7.10.0': {} '@types/bezier-js@4.1.3': {} '@types/bluebird@3.5.42': {} + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 25.5.0 + '@types/caseless@0.12.5': {} '@types/chai@5.2.3': @@ -7935,6 +8003,19 @@ snapshots: dependencies: '@types/node': 25.5.0 + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 25.5.0 + '@types/qs': 6.15.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 @@ -7944,6 +8025,8 @@ snapshots: '@types/http-cache-semantics@4.2.0': {} + '@types/http-errors@2.0.5': {} + '@types/js-beautify@1.14.3': {} '@types/jsdom@28.0.1': @@ -8005,6 +8088,10 @@ snapshots: pg-protocol: 1.13.0 pg-types: 2.2.0 + '@types/qs@6.15.0': {} + + '@types/range-parser@1.2.7': {} + '@types/request-promise@4.1.51': dependencies: '@types/bluebird': 3.5.42 @@ -8021,6 +8108,15 @@ snapshots: dependencies: htmlparser2: 10.1.0 + '@types/send@1.2.1': + dependencies: + '@types/node': 25.5.0 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 25.5.0 + '@types/statuses@2.0.6': {} '@types/tedious@4.0.14': @@ -9920,6 +10016,8 @@ snapshots: json-buffer@3.0.1: {} + json-nd@1.0.0: {} + json-parse-even-better-errors@2.3.1: {} json-schema-traverse@0.4.1: {} @@ -11302,6 +11400,10 @@ snapshots: stackback@0.0.2: {} + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + standard-as-callback@2.1.0: {} statuses@2.0.2: {} @@ -11618,6 +11720,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@0.7.1: {} + type-fest@3.13.1: {} type-fest@4.41.0: {} diff --git a/tsdown-worker.config.ts b/tsdown-worker.config.ts index 5b7626951..979a5850b 100644 --- a/tsdown-worker.config.ts +++ b/tsdown-worker.config.ts @@ -89,6 +89,7 @@ export default defineConfig({ 'node:child_process': path.resolve('./lib/shims/node-child-process.ts'), 'dotenv/config': path.resolve('./lib/shims/dotenv-config.ts'), '@sentry/node': path.resolve('./lib/shims/sentry-node.ts'), + '@honeybadger-io/js': path.resolve('./lib/shims/honeybadger.ts'), 'xxhash-wasm': path.resolve('./lib/shims/xxhash-wasm.ts'), // Routes file with Worker-specific build (match relative import from lib/) '../assets/build/routes.js': path.resolve('./assets/build/routes-worker.js'),