From 25cf2cf052d8e2439dd583c98d55bada41a45a70 Mon Sep 17 00:00:00 2001 From: guoyunfeng Date: Sun, 25 Oct 2026 03:33:50 +0800 Subject: [PATCH] fix(csv-parser): strip leading UTF-8 BOM before parsing batch CSVs (closes #1031) --- __tests__/lib/csv-parser.test.ts | 15 +++++++++++++++ lib/csv-parser.ts | 1 + 2 files changed, 16 insertions(+) diff --git a/__tests__/lib/csv-parser.test.ts b/__tests__/lib/csv-parser.test.ts index 7be0405..5d1971a 100644 --- a/__tests__/lib/csv-parser.test.ts +++ b/__tests__/lib/csv-parser.test.ts @@ -151,6 +151,20 @@ describe("parseCsvBatch", () => { }); }); + describe("UTF-8 BOM handling", () => { + it("parses CSV with a leading UTF-8 BOM identically to one without", () => { + const csvWithoutBom = `${HEADER}\n${makeRow()}`; + const csvWithBom = `\uFEFF${csvWithoutBom}`; + const resultWithout = parseCsvBatch(csvWithoutBom); + const resultWith = parseCsvBatch(csvWithBom); + + expect(resultWith.errors).toHaveLength(0); + expect(resultWith.rows).toHaveLength(1); + expect(resultWith.rows[0].recipient).toBe("GABC"); + expect(resultWith).toEqual(resultWithout); + }); + }); + describe("quoted fields", () => { it("handles quoted values containing commas", () => { const csv = [HEADER, '"GABC,rest",1000,1000,2000'].join("\n"); @@ -228,4 +242,5 @@ describe("parseDuration", () => { expect(parseDuration("1.5d")).toBeNull(); expect(parseDuration("-5d")).toBeNull(); }); + }); diff --git a/lib/csv-parser.ts b/lib/csv-parser.ts index 731439f..746b585 100644 --- a/lib/csv-parser.ts +++ b/lib/csv-parser.ts @@ -106,6 +106,7 @@ export function parseCsvBatch( customColumnMapping?: Record, ): CsvParseResult { const lines = csvText + .replace(/^\uFEFF/, "") .replace(/\r\n/g, "\n") .split("\n") .map((line) => line.trim())