diff --git a/src/utils/win-releases.ts b/src/utils/win-releases.ts index e63d520..ad2efd3 100644 --- a/src/utils/win-releases.ts +++ b/src/utils/win-releases.ts @@ -72,7 +72,9 @@ export async function parseRELEASES( content: string, ): Promise { const stripped = stripBom(content); - const normalizedEOL = stripped.replace("\r\n", "\n"); + // String#replace with a string pattern would only replace the first + // CRLF; replaceAll normalizes every CRLF occurrence + const normalizedEOL = stripped.replaceAll("\r\n", "\n"); const lines = normalizedEOL.split("\n"); const goodlines = lines.filter((line) => !!releaseRe.exec(line)); diff --git a/test/unit/win-releases.spec.ts b/test/unit/win-releases.spec.ts index 44fc13a..a2d131a 100644 --- a/test/unit/win-releases.spec.ts +++ b/test/unit/win-releases.spec.ts @@ -44,6 +44,42 @@ describe("Windows RELEASES", function () { expect(releases.length).toBe(5); }); + it("should normalize every CRLF, not just the first one", async function () { + // the line regex tolerates a trailing \r ([\r]*$), so parsed output + // alone can't distinguish full normalization from first-only + // String#replace; capture the lines actually handed to the regex and + // assert none still carries a \r + const seenLines: string[] = []; + const originalExec = RegExp.prototype.exec; + const spy = vi + .spyOn(RegExp.prototype, "exec") + .mockImplementation(function (this: RegExp, str: string) { + seenLines.push(str); + return originalExec.call(this, str); + }); + let crlfReleases; + try { + crlfReleases = await parseRELEASES( + "62E8BF432F29E8E08240910B85EDBF2D1A41EDF2 atom-0.178.0-full.nupkg 81272434\r\n" + + "5D754139E89802E88984185D2276B54DB730CD5E atom-0.178.1-delta.nupkg 8938535\r\n" + + "DD48D16EE177DD278F0A82CDDB72EBD043C767D2 atom-0.178.1-full.nupkg 81293415", + ); + } finally { + // restore even if parseRELEASES throws, or the global spy would + // leak into subsequent tests + spy.mockRestore(); + } + + expect(crlfReleases.length).toBe(3); + expect(crlfReleases[2].filename).toBe("atom-0.178.1-full.nupkg"); + expect(crlfReleases[2].size).toBe(81293415); + // with first-only replacement the second line (any CRLF-terminated + // line after the first) would still end in \r when it reaches the + // regex; the last line has no CRLF terminator + expect(seenLines.length).toBeGreaterThan(0); + expect(seenLines.some((line) => line.includes("\r"))).toBe(false); + }); + it("should parse a one-line file (with utf-8 BOM)", async function () { const oneRelease = await parseRELEASES( "\uFEFF24182FAD211FB9EB72610B1C086810FE37F70AE3 gitbook-editor-4.0.0-full.nupkg 46687158",