From d8305a5a142f535c41a6141bc50cbc8909970218 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Sat, 22 Aug 2026 07:28:29 +0530 Subject: [PATCH] fix(db): normalise line endings when parsing queries The generator embeds each query's SQL as a string literal, taking whatever line endings the checkout happened to have. On a CRLF checkout every regenerated query differed from the committed one by `\n` -> `\r\n`, so `wrnexus build` dirtied the working tree and that churn buried real changes in the same file -- which is how a hand-applied edit ends up preferable to running the generator. Line endings carry no meaning in SQL, so normalise on parse and let generated output be stable across platforms. Co-Authored-By: Claude Opus 5 --- packages/db/src/generate.ts | 11 +++++++- .../db/test/generate-line-endings.test.ts | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 packages/db/test/generate-line-endings.test.ts diff --git a/packages/db/src/generate.ts b/packages/db/src/generate.ts index 6bcb9c63..42adab26 100644 --- a/packages/db/src/generate.ts +++ b/packages/db/src/generate.ts @@ -29,8 +29,17 @@ export interface ModelRef { model: Model; } -/** Parse annotated queries from one `.sql` file's contents. */ +/** + * Parse annotated queries from one `.sql` file's contents. + * + * Line endings are normalised to LF first. The generator embeds each query's + * SQL as a string literal, so without this a CRLF checkout regenerated every + * query with CRLF where the committed file had LF -- a build dirtied the + * working tree, and that churn buried real changes in the same file. Line + * endings carry no meaning in SQL. + */ export function parseQueries(content: string): QueryDef[] { + content = content.replace(/\r\n?/g, "\n"); const out: QueryDef[] = []; const re = /--\s*name:\s*(\w+)\s*:(one|many|exec)\b[^\n]*\n([\s\S]*?)(?=--\s*name:|$)/gi; let m: RegExpExecArray | null; diff --git a/packages/db/test/generate-line-endings.test.ts b/packages/db/test/generate-line-endings.test.ts new file mode 100644 index 00000000..f28d6b61 --- /dev/null +++ b/packages/db/test/generate-line-endings.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test"; +import { parseQueries } from "../src/generate.ts"; + +// The generator embeds each query's SQL as a string literal. It used to embed +// whatever line endings the checkout happened to have, so on a CRLF checkout +// every regenerated query differed from the committed one by `\n` -> `\r\n`. +// A build therefore dirtied the working tree, and the churn buried real +// changes in the same file. Line endings carry no meaning in SQL, so the +// parser normalises them and generated output stays stable across platforms. +test("query SQL is normalised to LF regardless of the checkout's line endings", () => { + const lf = "-- name: GetOne :one\nSELECT a,\n b\nFROM t\nWHERE id = :id;\n"; + const crlf = lf.replace(/\n/g, "\r\n"); + + const fromLf = parseQueries(lf); + const fromCrlf = parseQueries(crlf); + + expect(fromCrlf).toEqual(fromLf); + expect(fromCrlf[0]!.sql).not.toContain("\r"); + // The SQL itself must still be intact, not merely stripped of carriage returns. + expect(fromCrlf[0]!.sql).toContain("SELECT a,"); + expect(fromCrlf[0]!.sql).toContain("WHERE id = :id"); +}); + +test("a lone CR does not survive into the embedded SQL either", () => { + const cr = "-- name: GetOne :one\rSELECT 1\r"; + for (const q of parseQueries(cr)) expect(q.sql).not.toContain("\r"); +});