From 439041678b065077048017c64faa1ea2a989d03c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Thu, 26 Feb 2026 18:46:38 +0400 Subject: [PATCH 1/4] feat(drizzle): add project root detection via tsconfig.json In deployed environments, process.cwd() may not be the project root (e.g., when the start script does `cd .amplify-hosting/compute/default/ && node app.js`). This adds findProjectRoot() which walks up from cwd looking for tsconfig.json to find the real project root. Co-Authored-By: Claude Opus 4.6 --- .../packages/sqlcommenter-drizzle/src/path.ts | 34 +++++++++++++++++++ .../sqlcommenter-drizzle/test/path.spec.ts | 25 ++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts create mode 100644 nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts new file mode 100644 index 00000000..385594e9 --- /dev/null +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts @@ -0,0 +1,34 @@ +import { existsSync } from "node:fs"; +import { dirname, join } from "node:path"; + +let cachedProjectRoot: string | undefined; + +/** + * Finds the project root by walking up from `process.cwd()` looking for `tsconfig.json`. + * + * In deployed environments, `process.cwd()` may not be the project root + * (e.g., `cd .amplify-hosting/compute/default/ && node app.js`). + * Walking up to find `tsconfig.json` — which is never copied to deployment directories — + * gives us the real project root. + * + * The result is cached since the project root doesn't change during a process's lifetime. + */ +export function findProjectRoot(): string { + if (cachedProjectRoot !== undefined) { + return cachedProjectRoot; + } + let projectRoot = process.cwd(); + for (let d = projectRoot; d !== dirname(d); d = dirname(d)) { + if (existsSync(join(d, "tsconfig.json"))) { + projectRoot = d; + break; + } + } + cachedProjectRoot = projectRoot; + return projectRoot; +} + +/** @internal Exposed for testing only */ +export function _resetProjectRootCache() { + cachedProjectRoot = undefined; +} diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts new file mode 100644 index 00000000..c1fde537 --- /dev/null +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts @@ -0,0 +1,25 @@ +import { test } from "node:test"; +import assert from "node:assert"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { findProjectRoot, _resetProjectRootCache } from "../src/path.js"; + +test("findProjectRoot", async (t) => { + t.afterEach(() => { + _resetProjectRootCache(); + }); + + await t.test("returns a directory containing tsconfig.json", () => { + const root = findProjectRoot(); + assert.ok( + existsSync(join(root, "tsconfig.json")), + `Expected ${root} to contain tsconfig.json`, + ); + }); + + await t.test("caches the result across calls", () => { + const first = findProjectRoot(); + const second = findProjectRoot(); + assert.strictEqual(first, second); + }); +}); From f3702215db9e396f0fe169a69accb96205255af8 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Thu, 26 Feb 2026 18:47:44 +0400 Subject: [PATCH 2/4] feat(drizzle): normalize source-map-resolved file paths When compiled JS is relocated (e.g., postbuild copies dist/ to a deployment directory), source map relative paths resolve to wrong absolute paths. The file tag would contain bogus paths like /.amplify-hosting/compute/src/routes/admin.ts instead of the real project path. This extracts the src/-relative portion from the stack trace path and reconstructs it using the real project root found via tsconfig.json. Co-Authored-By: Claude Opus 4.6 --- .../sqlcommenter-drizzle/src/index.ts | 3 +- .../packages/sqlcommenter-drizzle/src/path.ts | 29 +++++++++++ .../sqlcommenter-drizzle/test/path.spec.ts | 50 ++++++++++++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/index.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/index.ts index fe34a5c5..b01d8948 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/index.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/index.ts @@ -1,6 +1,7 @@ import { alreadyHasComment, serializeTags } from "./sqlcommenter.js"; import { als } from "./als.js"; import { pushW3CTraceContext } from "./tracing.js"; +import { resolveFilePath } from "./path.js"; const LIBRARY_NAME = "sqlcommenter-drizzle"; @@ -54,7 +55,7 @@ export function traceCaller(): string | undefined { } const match = methodCaller.match(filepathRegex); if (match) { - return match[1]; + return resolveFilePath(match[1]); } } diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts index 385594e9..2a2c2460 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts @@ -28,6 +28,35 @@ export function findProjectRoot(): string { return projectRoot; } +/** + * Resolves a file path from a stack trace to a correct absolute path. + * + * When compiled JS is relocated (e.g., postbuild copies `dist/` to a deployment directory), + * source-map-resolved paths become incorrect because the relative `sources` entries in + * `.map` files resolve against the new location instead of the original project. + * + * This extracts the `src/`-relative portion and reconstructs the path using the real + * project root. + * + * @param raw - A stack trace entry like "/wrong/path/src/routes/admin.ts:12:15" + * @returns The resolved path like "/project/root/src/routes/admin.ts:12:15" + */ +export function resolveFilePath(raw: string): string { + // Split off :line:column suffix + const match = raw.match(/^(.*?):(\d+:\d+)$/); + if (!match) { + return raw; + } + const [, filePath, lineCol] = match; + const srcIdx = filePath.indexOf("src/"); + if (srcIdx < 0) { + return raw; + } + const projectRoot = findProjectRoot(); + const relativePath = filePath.substring(srcIdx); + return `${projectRoot}/${relativePath}:${lineCol}`; +} + /** @internal Exposed for testing only */ export function _resetProjectRootCache() { cachedProjectRoot = undefined; diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts index c1fde537..287e64e5 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts @@ -2,7 +2,11 @@ import { test } from "node:test"; import assert from "node:assert"; import { existsSync } from "node:fs"; import { join } from "node:path"; -import { findProjectRoot, _resetProjectRootCache } from "../src/path.js"; +import { + findProjectRoot, + resolveFilePath, + _resetProjectRootCache, +} from "../src/path.js"; test("findProjectRoot", async (t) => { t.afterEach(() => { @@ -23,3 +27,47 @@ test("findProjectRoot", async (t) => { assert.strictEqual(first, second); }); }); + +test("resolveFilePath", async (t) => { + t.afterEach(() => { + _resetProjectRootCache(); + }); + + await t.test( + "resolves path with src/ to project root", + () => { + const projectRoot = findProjectRoot(); + const result = resolveFilePath( + "/wrong/deploy/dir/src/routes/admin.ts:12:15", + ); + assert.strictEqual(result, `${projectRoot}/src/routes/admin.ts:12:15`); + }, + ); + + await t.test("leaves path without src/ unchanged", () => { + const result = resolveFilePath("/some/other/path/routes/admin.ts:5:10"); + assert.strictEqual(result, "/some/other/path/routes/admin.ts:5:10"); + }); + + await t.test("preserves line:column suffix", () => { + const projectRoot = findProjectRoot(); + const result = resolveFilePath("/bad/path/src/index.ts:99:3"); + assert.strictEqual(result, `${projectRoot}/src/index.ts:99:3`); + }); + + await t.test("uses first src/ occurrence", () => { + const projectRoot = findProjectRoot(); + const result = resolveFilePath( + "/deploy/src/nested/src/routes/admin.ts:1:1", + ); + assert.strictEqual( + result, + `${projectRoot}/src/nested/src/routes/admin.ts:1:1`, + ); + }); + + await t.test("returns raw string if no line:column suffix", () => { + const result = resolveFilePath("/some/path/src/file.ts"); + assert.strictEqual(result, "/some/path/src/file.ts"); + }); +}); From abf71da01ae54158787f9edc08520df45ec492c4 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Thu, 26 Feb 2026 18:48:39 +0400 Subject: [PATCH 3/4] feat(drizzle): add WSL path support for file tag Inside WSL, absolute paths like /home/user/project/... can't be resolved from Windows-side tooling (dashboards, VS Code terminal). This detects the WSL_DISTRO_NAME env var and prefixes resolved paths with //wsl.localhost/ to make them accessible from Windows. Co-Authored-By: Claude Opus 4.6 --- .../packages/sqlcommenter-drizzle/src/path.ts | 19 ++++++- .../sqlcommenter-drizzle/test/path.spec.ts | 51 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts index 2a2c2460..811866bd 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/path.ts @@ -54,7 +54,24 @@ export function resolveFilePath(raw: string): string { } const projectRoot = findProjectRoot(); const relativePath = filePath.substring(srcIdx); - return `${projectRoot}/${relativePath}:${lineCol}`; + const resolved = `${projectRoot}/${relativePath}`; + return `${applyWslPrefix(resolved)}:${lineCol}`; +} + +/** + * Prefixes an absolute path with the WSL network path when running inside WSL. + * + * Inside WSL, absolute paths like `/home/user/project/...` can't be resolved + * from Windows-side tooling (e.g., clickable links in dashboards or VS Code). + * The `WSL_DISTRO_NAME` env var is always set inside WSL, and the path format + * `//wsl.localhost//...` makes paths accessible from Windows. + */ +export function applyWslPrefix(filePath: string): string { + const distro = process.env.WSL_DISTRO_NAME; + if (distro) { + return `//wsl.localhost/${distro}${filePath}`; + } + return filePath; } /** @internal Exposed for testing only */ diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts index 287e64e5..2ac88428 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/path.spec.ts @@ -5,6 +5,7 @@ import { join } from "node:path"; import { findProjectRoot, resolveFilePath, + applyWslPrefix, _resetProjectRootCache, } from "../src/path.js"; @@ -71,3 +72,53 @@ test("resolveFilePath", async (t) => { assert.strictEqual(result, "/some/path/src/file.ts"); }); }); + +test("applyWslPrefix", async (t) => { + const originalWslDistro = process.env.WSL_DISTRO_NAME; + + t.afterEach(() => { + if (originalWslDistro === undefined) { + delete process.env.WSL_DISTRO_NAME; + } else { + process.env.WSL_DISTRO_NAME = originalWslDistro; + } + }); + + await t.test("prefixes path when WSL_DISTRO_NAME is set", () => { + process.env.WSL_DISTRO_NAME = "Ubuntu"; + const result = applyWslPrefix("/home/user/project/src/index.ts"); + assert.strictEqual( + result, + "//wsl.localhost/Ubuntu/home/user/project/src/index.ts", + ); + }); + + await t.test("returns path unchanged when WSL_DISTRO_NAME is not set", () => { + delete process.env.WSL_DISTRO_NAME; + const result = applyWslPrefix("/home/user/project/src/index.ts"); + assert.strictEqual(result, "/home/user/project/src/index.ts"); + }); +}); + +test("resolveFilePath with WSL", async (t) => { + const originalWslDistro = process.env.WSL_DISTRO_NAME; + + t.afterEach(() => { + _resetProjectRootCache(); + if (originalWslDistro === undefined) { + delete process.env.WSL_DISTRO_NAME; + } else { + process.env.WSL_DISTRO_NAME = originalWslDistro; + } + }); + + await t.test("applies WSL prefix to resolved src/ paths", () => { + process.env.WSL_DISTRO_NAME = "Ubuntu"; + const projectRoot = findProjectRoot(); + const result = resolveFilePath("/wrong/path/src/routes/admin.ts:12:15"); + assert.strictEqual( + result, + `//wsl.localhost/Ubuntu${projectRoot}/src/routes/admin.ts:12:15`, + ); + }); +}); From 67b280fbac246d57952a4b71bb8001faad49ef37 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Thu, 26 Feb 2026 19:24:52 +0400 Subject: [PATCH 4/4] chore(drizzle): bump version to 0.2.0 Co-Authored-By: Claude Opus 4.6 --- .../packages/sqlcommenter-drizzle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json index 43e379f5..bcd66612 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@query-doctor/sqlcommenter-drizzle", - "version": "0.1.1", + "version": "0.2.0", "description": "SQLCommenter patch for drizzle-orm", "main": "dist/cjs/index.js", "type": "module",