diff --git a/src/protect/templates/guard.ts b/src/protect/templates/guard.ts index 2e8b32a..0d2a770 100644 --- a/src/protect/templates/guard.ts +++ b/src/protect/templates/guard.ts @@ -30,7 +30,7 @@ let _protection: Awaited> | undefined; async function getProtection() { if (!_protection) { // Always-on: block by default. An explicit PATCHSTACK_MODE=dry-run downgrades to log-only. - const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block"; + const mode: "block" | "dry-run" = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block"; const token = process.env.PATCHSTACK_WAF_TOKEN; const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID; // Egress SSRF screening: block the app's outbound calls to internal / metadata addresses, @@ -80,11 +80,11 @@ export async function inspectServerFn(data: unknown): Promise<{ rule?: string; m // src/start.ts (the browser→Supabase tunnel screens its own forwarded response). Only acts on a // web Response (text/JSON/HTML) — anything else, or any error, passes through untouched (fail-open, // never breaks a response). -export async function screenResponse(response: unknown): Promise { +export async function screenResponse(response: T): Promise { try { if (!(response instanceof Response)) return response; const protection = await getProtection(); - return protection.screenResponse ? await protection.screenResponse(response) : response; + return (protection.screenResponse ? await protection.screenResponse(response) : response) as T; } catch { return response; // fail open } diff --git a/tests/protect/install.test.ts b/tests/protect/install.test.ts index 2d44015..196d554 100644 --- a/tests/protect/install.test.ts +++ b/tests/protect/install.test.ts @@ -77,6 +77,15 @@ describe('runProtect scaffolder', () => { expect(start).toContain('functionMiddleware: [patchstackFunctionGuard, attachSupabaseAuth]'); }); + it('scaffolds a type-safe guard.ts (compiles under a strict TanStack build)', () => { + runProtect(dir); + const guard = read(dir, 'src/integrations/patchstack/guard.ts'); + // Regression: a strict `tsc` build in the target app rejected a `string` mode and a + // `Promise` from screenResponse (the middleware return type). Keep both type-safe. + expect(guard).toContain('const mode: "block" | "dry-run" ='); + expect(guard).toContain('export async function screenResponse(response: T): Promise'); + }); + it('is idempotent — re-running does not duplicate wiring', () => { runProtect(dir); runProtect(dir);