From d8cef00b4deed8bcc0d68851133c7c479dc9807d Mon Sep 17 00:00:00 2001 From: mv-ai Date: Mon, 1 Jun 2026 14:45:43 -0400 Subject: [PATCH 1/2] fix: anchor GitHub annotation path to GITHUB_WORKSPACE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reporter emits `::error file=,line=,col=::` workflow commands so test failures show as inline annotations on the PR diff. GitHub resolves the `file=` path from the repo root (GITHUB_WORKSPACE), but the reporter computed it with `relative(cwd, ...)`. Under lerna/nx the cwd is the package dir, so the path came out package-relative (e.g. `file=bar.test.js`) and never mapped onto the diff — no annotation appeared. Anchor the path to GITHUB_WORKSPACE in CI so it is repo-root-relative (`packages/foo/bar.test.js`); local runs stay cwd-relative. The logic lives in a new node:path-only `bin/github.js`, so the unit test can import it without dragging in the node:test reporter internals (which don't bundle for the barebone engines). Adds unit coverage for the CI and non-CI cases. Co-Authored-By: Claude Opus 4.8 (1M context) Co-Authored-By: Claude --- bin/github.js | 9 +++++++++ bin/reporter.js | 8 +++++++- package.json | 1 + tests/github.test.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 bin/github.js create mode 100644 tests/github.test.js diff --git a/bin/github.js b/bin/github.js new file mode 100644 index 00000000..1171466f --- /dev/null +++ b/bin/github.js @@ -0,0 +1,9 @@ +import { relative, resolve } from 'node:path' + +// Path used for the GitHub `::error file=` annotation. GitHub resolves it from the repo root +// (GITHUB_WORKSPACE), but under lerna/nx the cwd is the package dir, so a cwd-relative path won't +// map onto the PR diff. Anchor to GITHUB_WORKSPACE in CI so the annotation lands on the right file. +export const resolveAnnotationFile = (rawFile, { cwd, CI, GITHUB_WORKSPACE }) => { + const absolute = resolve(cwd, rawFile) + return CI && GITHUB_WORKSPACE ? relative(GITHUB_WORKSPACE, absolute) : relative(cwd, absolute) +} diff --git a/bin/reporter.js b/bin/reporter.js index f7eb8b5f..c035351d 100644 --- a/bin/reporter.js +++ b/bin/reporter.js @@ -4,6 +4,7 @@ import { relative, resolve, normalize } from 'node:path' import { spec as SpecReporter } from 'node:test/reporters' import { fileURLToPath } from 'node:url' import { color, haveColors, dim } from './color.js' +import { resolveAnnotationFile } from './github.js' const { CI, GITHUB_WORKSPACE, LERNA_PACKAGE_NAME } = process.env @@ -159,7 +160,12 @@ export default async function nodeTestReporterExodus(source) { if (path.length > 0) assert(path.pop() === data.name) // afterAll can generate failures too, with an empty path if (!data.todo) failedFiles.add(file) if (!notPrintedError(data.details.error)) { - const { body, loc } = extractError(data, relative(cwd, data.file || data.name)) // might be different from current file if in subimport + const errorFile = resolveAnnotationFile(data.file || data.name, { + cwd, + CI, + GITHUB_WORKSPACE, + }) + const { body, loc } = extractError(data, errorFile) // might be different from current file if in subimport if (!data.todo && CI && loc.line != null && loc.col != null) { print(`::error ${serializeGitHub(Object.entries(loc))}::${escapeGitHub(body)}`) } else if (body) { diff --git a/package.json b/package.json index 15d66bff..98e7f0fd 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "bin/electron.js", "bin/electron.preload.cjs", "bin/find-binary.js", + "bin/github.js", "bin/inband.js", "bin/reporter.js", "loader/babel.cjs", diff --git a/tests/github.test.js b/tests/github.test.js new file mode 100644 index 00000000..678d6c79 --- /dev/null +++ b/tests/github.test.js @@ -0,0 +1,31 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { resolveAnnotationFile } from '../bin/github.js' + +// GitHub resolves `::error file=...` annotation paths from the repo root (GITHUB_WORKSPACE). +// lerna/nx run each package with cwd = the package dir, so a cwd-relative path points at the wrong +// place and the annotation never lands on the PR diff. In CI the path must be workspace-relative. +const GITHUB_WORKSPACE = '/home/runner/work/repo/repo' +const cwd = `${GITHUB_WORKSPACE}/packages/foo` // what lerna/nx hands the reporter +const absFile = `${cwd}/src/bar.test.js` + +test('CI annotation path is repo-root-relative so it lands on the PR diff', () => { + const file = resolveAnnotationFile(absFile, { cwd, CI: '1', GITHUB_WORKSPACE }) + assert.equal(file, 'packages/foo/src/bar.test.js') +}) + +test('a relative input is resolved against cwd before anchoring to the workspace', () => { + const file = resolveAnnotationFile('src/bar.test.js', { cwd, CI: '1', GITHUB_WORKSPACE }) + assert.equal(file, 'packages/foo/src/bar.test.js') +}) + +test('outside CI the path stays cwd-relative (local runs are unchanged)', () => { + assert.equal( + resolveAnnotationFile(absFile, { cwd, CI: undefined, GITHUB_WORKSPACE }), + 'src/bar.test.js' + ) + assert.equal( + resolveAnnotationFile(absFile, { cwd, CI: '1', GITHUB_WORKSPACE: undefined }), + 'src/bar.test.js' + ) +}) From cb877aa8869204e1e4152359f2bf12e8f2552c04 Mon Sep 17 00:00:00 2001 From: exo-mv Date: Mon, 1 Jun 2026 15:25:06 -0400 Subject: [PATCH 2/2] fix: emit POSIX separators for GitHub annotation path on Windows resolveAnnotationFile ran the path through node:path relative()/resolve(), which produce backslash separators on Windows runners. GitHub matches `::error file=` annotation paths with forward slashes, so on Windows the annotation never landed on the repo file (and reporter.js's stack-line matcher, which assumes `/`, also missed). Normalize the result to POSIX separators so the annotation maps onto the PR diff on every platform. Co-Authored-By: Claude Opus 4.8 (1M context) --- bin/github.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/github.js b/bin/github.js index 1171466f..477baa93 100644 --- a/bin/github.js +++ b/bin/github.js @@ -1,9 +1,12 @@ -import { relative, resolve } from 'node:path' +import { relative, resolve, sep } from 'node:path' // Path used for the GitHub `::error file=` annotation. GitHub resolves it from the repo root // (GITHUB_WORKSPACE), but under lerna/nx the cwd is the package dir, so a cwd-relative path won't // map onto the PR diff. Anchor to GITHUB_WORKSPACE in CI so the annotation lands on the right file. +// GitHub matches annotation paths with POSIX separators, so emit forward slashes even on Windows +// runners (where node:path would otherwise produce backslashes that never match the repo file). export const resolveAnnotationFile = (rawFile, { cwd, CI, GITHUB_WORKSPACE }) => { const absolute = resolve(cwd, rawFile) - return CI && GITHUB_WORKSPACE ? relative(GITHUB_WORKSPACE, absolute) : relative(cwd, absolute) + const base = CI && GITHUB_WORKSPACE ? GITHUB_WORKSPACE : cwd + return relative(base, absolute).split(sep).join('/') }