From 3da3318c56f3f62faa24068a5ba14a27f664efa5 Mon Sep 17 00:00:00 2001 From: Leon-SK668 <0668001470@xydigit.com> Date: Fri, 17 Jul 2026 08:53:09 +0800 Subject: [PATCH] fix(audit): preserve emoji in npm registry errors (#108208) * fix(audit): keep registry errors valid Unicode * style(audit): format registry error boundary test * docs(audit): document direct Node import floor Co-authored-by: Leon-SK668 <0668001470@xydigit.com> --------- Co-authored-by: Peter Steinberger --- scripts/pre-commit/pnpm-audit-prod.mjs | 4 +++- test/scripts/pnpm-audit-prod.test.ts | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/pre-commit/pnpm-audit-prod.mjs b/scripts/pre-commit/pnpm-audit-prod.mjs index 66503c6c4069..bbe06be257be 100644 --- a/scripts/pre-commit/pnpm-audit-prod.mjs +++ b/scripts/pre-commit/pnpm-audit-prod.mjs @@ -5,6 +5,8 @@ import { readFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; import { pathToFileURL } from "node:url"; +// This zero-install hook runs on Node 22.22.3+, where native TypeScript stripping is enabled. +import { truncateUtf16Safe } from "../../packages/normalization-core/src/utf16-slice.ts"; import { readBoundedResponseText as readBoundedResponseTextWithLimit } from "../lib/bounded-response.mjs"; const DEFAULT_REGISTRY = "https://registry.npmjs.org"; @@ -783,7 +785,7 @@ export async function readBoundedBulkAdvisoryErrorText( text += decoder.decode(value, { stream: true }); if (text.length > maxChars) { - text = text.slice(0, maxChars); + text = truncateUtf16Safe(text, maxChars); truncated = true; break; } diff --git a/test/scripts/pnpm-audit-prod.test.ts b/test/scripts/pnpm-audit-prod.test.ts index 6338880c4896..da1c7f5899b0 100644 --- a/test/scripts/pnpm-audit-prod.test.ts +++ b/test/scripts/pnpm-audit-prod.test.ts @@ -248,6 +248,26 @@ snapshots: expect(text.length).toBeLessThan(4200); }); + it.each([ + { + caseName: "drops a split surrogate pair", + responseBody: `abc\u{1f600}tail`, + expectedText: "abc\n[truncated]", + }, + { + caseName: "preserves a complete surrogate pair", + responseBody: `ab\u{1f600}tail`, + expectedText: `ab\u{1f600}\n[truncated]`, + }, + ])( + "keeps bulk advisory error truncation UTF-16 safe: $caseName", + async ({ responseBody, expectedText }) => { + const response = new Response(responseBody, { status: 500 }); + + await expect(readBoundedBulkAdvisoryErrorText(response, 4)).resolves.toBe(expectedText); + }, + ); + it("aborts stalled bulk advisory requests", async () => { let signal: AbortSignal | undefined; const request = fetchBulkAdvisories({