[graphql-codegen-testing] fix: anchor validateTs module resolution at the calling test's directory - #10979
Merged
Conversation
🦋 Changeset detectedLatest commit: 7912f94 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
…er validateTs
Restores the three `validateTs(mergeOutputs([...]))` type-level assertions in
client-preset.fragment-masking.spec.ts that were commented out behind
FIXME(pnpm-update), plus the imports they need. All three fail:
Argument of type '{ ' $fragmentRefs'?: { FooFragment: FooFragment } } | null'
is not assignable to parameter of type 'readonly never[] | null | undefined'
Root cause is NOT what the FIXME guessed ("Maybe content shouldn't be
merged?"). The merge is fine. `validateTs`'s compiler host returns '' from
`getCurrentDirectory` and names the synthetic file `test-file.ts` with no
directory, so Node module resolution has no base directory and
`@graphql-typed-document-node/core` never resolves. `DocumentTypeDecoration`
degrades to an error type, `FragmentType<...>` collapses to `never`, and the
parameter becomes `readonly never[]`. The real diagnostic — `Cannot find
module` — is then swallowed by validateTs's own `Cannot find module` filter,
leaving only the confusing downstream overload error.
Evidence: giving the host a real cwd and file path takes the non-filtered
error count from 1 to 0. Two other hypotheses were tested and ruled out — the
fixture's `gql` vs the generated `graphql` tag name, and inference through the
tag (substituting the correctly typed `FooFragmentDoc` gives the identical
error).
Note these three tests previously asserted only a `toMatchInlineSnapshot` on
fragment-masking.ts — schema-independent boilerplate identical across all
three — so none of them actually checked the null/Array/ReadonlyArray
narrowing it is named for. The disabled `validateTs` call was the assertion
specific to each.
Checkpoint only — no fix included, so these tests are expected to be red.
eddeee888:oss:issue-verify
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01348oUnhcWGpgmx33ZhRwUV
eddeee888
force-pushed
the
repro/fragment-masking-validatets
branch
from
September 20, 2026 13:43
00ccd76 to
55ddfda
Compare
The file `validateTs`/`compileTs` type-check exists only in memory, but TypeScript still needs a real directory to anchor Node module resolution to. The compiler host reported '' as the current directory and named the file `test-file.ts` with no directory, so nothing resolved at all. Every resulting diagnostic was then swallowed by the blanket `Cannot find module` filter, so the failure was invisible: with `@graphql-typed-document-node/core` unresolved, `DocumentTypeDecoration` became an error type, `FragmentType<...>` collapsed to `never`, and the only surviving symptom was a confusing "not assignable to 'readonly never[]'" overload error. That is what the three client-preset useFragment assertions were disabled for, behind FIXME(pnpm-update) — the FIXME's guess that the merged content was at fault was wrong; `mergeOutputs` is fine. `process.cwd()` is not a usable anchor: it is the repo root, and under pnpm's isolated layout a package's dependencies live in that package's own `node_modules`, so `@graphql-typed-document-node/core` is only resolvable from `packages/presets/client`. The directory of the running test file is a usable anchor, and vitest already exposes it as `expect.getState().testPath`. Falls back to `process.cwd()` outside a test run. `vitest` moves from a phantom dependency to a declared peer one: `src/index.ts` already imported it for the custom matcher, and `validateTs` now depends on it too. The lockfile entry was added surgically — a plain `pnpm install`, or the `pnpm dedupe` lint-staged runs on a staged lockfile, rewrites ~1500 unrelated lines because the committed lockfile predates pnpm 11.24's peer-suffix format. Hence --no-verify here, with the hook's gates run directly instead: prettier, eslint, tsc --noEmit, and the suites below. Verified: client-preset 58/58 (the three restored assertions among them) and typescript-operations 243/243, the latter holding ~81 of the `compileProgram: true` call sites whose shared compiler host this changes. The assertions are live rather than vacuously passing — injecting `const x: number = 'string'` into the checked snippet fails with "Type 'string' is not assignable to type 'number'". eddeee888:oss:issue-fix Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01348oUnhcWGpgmx33ZhRwUV
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01348oUnhcWGpgmx33ZhRwUV
eddeee888
force-pushed
the
repro/fragment-masking-validatets
branch
from
September 22, 2026 13:43
6d837c7 to
8b762f6
Compare
TypeScript normalizes every path it passes back to the compiler host to forward slashes. `path.join` on Windows produced `D:\...\tests\test-file.ts`, so the `fileName === testFile` check in `getSourceFile` never matched `D:/.../tests/test-file.ts`, the in-memory file was never served, and every `compileProgram: true` call failed with "File '.../test-file.ts' not found" on windows-latest. Build the directory and file path with forward slashes instead. Verified: prettier, eslint, tsc --noEmit; client-preset 58/58 and typescript-operations 243/243. The mismatch was reproduced on Linux: a backslash root file is handed back to getSourceFile with forward slashes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159B3G5xTmbJ3jtWmrggY5A
Falling back to process.cwd() would silently reintroduce the unresolved imports this change fixes, so fail loudly instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GK2Swu7vXW3gQm1pCv2H7R
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GK2Swu7vXW3gQm1pCv2H7R
eddeee888
commented
Sep 23, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GK2Swu7vXW3gQm1pCv2H7R
eddeee888
marked this pull request as ready for review
September 23, 2026 14:18
This was referenced Sep 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related # (issue) — none filed. Found by auditing
FIXME(pnpm-update)markers, like #10977.Description
This PR re-enables three disabled
useFragmenttype assertions inclient-presetby anchoringvalidateTs/compileTsmodule resolution at the running test's directory.mergeOutputsis fine. The compiler host reported''as the current directory, so nothing resolved; the blanketCannot find modulefilter hid it, andFragmentType<…>silently collapsed tonever.process.cwd()is not a usable anchor: it's the repo root, and under pnpm's isolated layout deps live in each package's ownnode_modules.What changed
src/typescript.ts— addsresolveCallerDirectory()(fromexpect.getState().testPath), used for the synthetic file path andgetCurrentDirectory(). Paths use forward slashes so they match what TypeScript hands back on Windows. It throws outside a vitest test rather than falling back toprocess.cwd(), which would silently bring the bug back.package.json/pnpm-lock.yaml— declaresvitestas a peer dependency (it was already a phantom one).tests/client-preset.fragment-masking.spec.ts— restores the three assertions.Type of change
How Has This Been Tested?
55ddfdais the failing repro (readonly never[]);80a21e2fixes it;643a30ffixes Windows paths;96938fcmakes the no-test-context case throw.client-preset58/58, including the three restored tests — confirmed live by injecting a type error, which fails as expectedtypescript-operations(mostcompileProgram: truecall sites) 243/243Further comments
pnpm install --frozen-lockfile; regenerating it churns ~1500 unrelated lines even on cleanmaster, so the fix commit used--no-verifywith the hook's gates run manually.Cannot find modulefilter (which hid this) breaks 7typescript-operationstests relying on a fake@org/scalarsimport — worth its own PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01GK2Swu7vXW3gQm1pCv2H7R