diff --git a/package.json b/package.json index ac546b6..7f62613 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,11 @@ "sharp@<0.35.4": "0.35.4", "js-yaml@>=4.0.0 <4.3.2": "4.3.2", "svgo@>=4.0.0 <4.1.0": "4.1.0", - "smol-toml@<=1.7.0": "1.7.1" + "smol-toml@<=1.7.0": "1.7.1", + "brace-expansion@>=1.0.0 <1.1.21": "1.1.21", + "brace-expansion@>=2.0.0 <2.1.7": "2.1.7", + "brace-expansion@>=5.0.0 <5.0.12": "5.0.12", + "undici@>=7.0.0 <7.29.1": "7.29.1" } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3a5363..f63ce77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,10 @@ overrides: js-yaml@>=4.0.0 <4.3.2: 4.3.2 svgo@>=4.0.0 <4.1.0: 4.1.0 smol-toml@<=1.7.0: 1.7.1 + brace-expansion@>=1.0.0 <1.1.21: 1.1.21 + brace-expansion@>=2.0.0 <2.1.7: 2.1.7 + brace-expansion@>=5.0.0 <5.0.12: 5.0.12 + undici@>=7.0.0 <7.29.1: 7.29.1 importers: @@ -3106,8 +3110,8 @@ packages: resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==} engines: {node: '>=20.19.0'} - brace-expansion@5.0.9: - resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} engines: {node: 20 || >=22} browserslist@4.28.9: @@ -4900,8 +4904,8 @@ packages: undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} - undici@7.29.0: - resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + undici@7.29.1: + resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==} engines: {node: '>=20.18.1'} undici@8.11.2: @@ -7593,7 +7597,7 @@ snapshots: boolbase@2.0.0: {} - brace-expansion@5.0.9: + brace-expansion@5.0.12: dependencies: balanced-match: 4.0.4 @@ -8863,7 +8867,7 @@ snapshots: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.35.4(@types/node@25.6.0) - undici: 7.29.0 + undici: 7.29.1 workerd: 1.20260722.1 ws: 8.21.0 youch: 4.1.0-beta.10 @@ -8874,7 +8878,7 @@ snapshots: minimatch@10.2.6: dependencies: - brace-expansion: 5.0.9 + brace-expansion: 5.0.12 minipass@7.1.3: {} @@ -9641,7 +9645,7 @@ snapshots: undici-types@7.19.2: {} - undici@7.29.0: {} + undici@7.29.1: {} undici@8.11.2: {} diff --git a/src/worker.test.ts b/src/worker.test.ts index 0e994b8..7c2f5cf 100644 --- a/src/worker.test.ts +++ b/src/worker.test.ts @@ -1,10 +1,21 @@ import { beforeEach, expect, it, vi } from 'vitest'; import type { WorkerEnv } from './lib/worker-env'; -const mocks = vi.hoisted(() => ({ auth: vi.fn(), user: vi.fn(), assets: vi.fn() })); +const mocks = vi.hoisted(() => ({ + auth: vi.fn(), + user: vi.fn(), + assets: vi.fn(), + appHealth: vi.fn(), +})); vi.mock('./lib/auth', () => ({ createAuth: () => ({ handler: mocks.auth }) })); vi.mock('./lib/auth-api', () => ({ getAuthenticatedUserId: mocks.user })); vi.mock('./worker/bind-env', () => ({ bindWorkerEnv: vi.fn() })); +vi.mock('./worker/app-health', () => ({ + appHealthMiddleware: async (_context: unknown, next: () => Promise) => { + mocks.appHealth(); + await next(); + }, +})); import worker from './worker'; @@ -48,4 +59,5 @@ it('preserves discovery GET and HEAD without invoking account services', async ( expect(await head.text()).toBe(''); expect(mocks.auth).not.toHaveBeenCalled(); expect(mocks.user).not.toHaveBeenCalled(); + expect(mocks.appHealth).toHaveBeenCalledTimes(3); }); diff --git a/src/worker.ts b/src/worker.ts index 167b917..d410829 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -43,6 +43,15 @@ api.use('*', async (c, next) => { api.use('*', appHealthMiddleware); +api.on(['GET', 'HEAD'], '*', async (c, next) => { + const path = c.req.path.replace(/\/{2,}/g, '/').replace(/\/+$/, ''); + if (path === '/api/ai') { + const response = await handleAgentEdge(c.req.raw, c.env); + if (response) return response; + } + await next(); +}); + api.use('/api/*', async (c, next) => { await next(); const response = c.res; @@ -120,9 +129,9 @@ export default { async fetch(request: Request, env: WorkerEnv, ctx: ExecutionContext): Promise { const url = new URL(request.url); - // Discovery owns /api/ai, but its catch-all must not swallow product APIs. - const normalizedPath = url.pathname.replace(/\/{2,}/g, '/').replace(/\/+$/, ''); - if (!url.pathname.startsWith('/api/') || normalizedPath === '/api/ai') { + // Discovery outside /api/* remains separate from product API routes. + // /api/ai passes through Hono so App Health sees its public GET/HEAD traffic. + if (!url.pathname.startsWith('/api/')) { const agent = await handleAgentEdge(request, env); if (agent) return agent; }