support absolute path aliases#7433
Conversation
🦋 Changeset detectedLatest commit: a70e1f8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
📝 WalkthroughSummary by CodeRabbit
WalkthroughApplies path-alias rewriting early in import resolution by adding an internal applyPathAliases helper, removing pathAliases from resolveFilePath, updating visitFile flow to run alias resolution before absolute checks, and adding fixtures/tests for absolute and node-style subpath aliases. Includes a changeset documenting the fix. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Caller as Importer
participant Import as visitFile
participant Alias as applyPathAliases
participant Resolver as resolveFilePath
participant FS as File System
Caller->>Import: importSchema(entryPath, { pathAliases })
activate Import
Import->>Alias: applyPathAliases(entryPath, pathAliases)
activate Alias
Alias-->>Import: aliasedEntryPath
deactivate Alias
loop per import statement
Import->>Resolver: resolveFilePath(aliasedEntryPath, importFrom)
activate Resolver
Resolver->>FS: join/check/resolve paths
FS-->>Resolver: resolvedPath or not found
Resolver-->>Import: resolvedPath
deactivate Resolver
Import->>FS: read resolvedPath
FS-->>Import: SDL content
Import-->>Import: merge definitions
end
Import-->>Caller: DocumentNode
deactivate Import
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
.changeset/solid-loops-feel.md (1)
1-18: Clarify behavior and precedence in the changeset.
Consider explicitly documenting:
- Alias resolution now occurs before absolute/path checks.
- Precedence between predefinedImports and pathAliases (ideally, predefinedImports should win to preserve virtual imports).
Proposed wording tweak:
The solution is straightforward: move path alias resolution before the absolute path check instead of after it. +Precedence note: virtual imports provided via `predefinedImports` should take priority over alias rewriting to avoid surprising lookups when a virtual key also matches an alias pattern.packages/import/src/index.ts (1)
683-709: Match TS “paths” selection order and minor robustness in alias resolution.
Today, the first mapping that matches wins. TypeScript prefers:
- Exact patterns over wildcard patterns.
- Among wildcards, longer prefixes first.
Also, handle absolute mapped targets without resolveFrom.Apply this diff:
-function applyPathAliases(filePath: string, pathAliases?: PathAliases): string { +function applyPathAliases(filePath: string, pathAliases?: PathAliases): string { if (pathAliases == null) { return filePath; } - for (const [prefixPattern, mapping] of Object.entries(pathAliases.mappings)) { + const entries = Object.entries(pathAliases.mappings).sort((a, b) => { + const [ak, bk] = [a[0], b[0]]; + const aWild = ak.includes('*') ? 1 : 0; + const bWild = bk.includes('*') ? 1 : 0; + if (aWild !== bWild) return aWild - bWild; // exact before wildcard + const aLen = ak.includes('*') ? ak.indexOf('*') : ak.length; + const bLen = bk.includes('*') ? bk.indexOf('*') : bk.length; + return bLen - aLen; // longer prefix first + }); + + for (const [prefixPattern, mapping] of entries) { const matchedMapping = applyPathAlias(prefixPattern, mapping, filePath); if (matchedMapping == null) { continue; } - const resolvedMapping = resolveFrom(pathAliases.rootDir ?? process.cwd(), matchedMapping); - return realpathSync(resolvedMapping); + const baseDir = pathAliases.rootDir ?? process.cwd(); + const target = isAbsolute(matchedMapping) + ? matchedMapping + : resolveFrom(baseDir, matchedMapping); + return realpathSync(target); } return filePath; }packages/import/tests/schema/import-schema.spec.ts (2)
1294-1307: DRY up repeated expected SDL across the two new testsThe expected SDL is identical in both tests. Consider a small constant to reduce duplication and ease future edits.
Add this helper near the top of the describe block (or right above these tests):
const expectedAWithB = /* GraphQL */ ` type Query { getA: TypeA } type TypeA { id: ID! relatedB: TypeB! } type TypeB { id: ID! } `;Then update both assertions:
- expect(document).toBeSimilarGqlDoc(/* GraphQL */ ` - type Query { - getA: TypeA - } - - type TypeA { - id: ID! - relatedB: TypeB! - } - - type TypeB { - id: ID! - } - `); + expect(document).toBeSimilarGqlDoc(expectedAWithB);Also applies to: 1321-1334
1310-1311: Rename for clarityConsider tightening the test name to: "should handle node-style subpath import aliases".
-it('should handle node-style subpath imports path aliases', () => { +it('should handle node-style subpath import aliases', () => {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
.changeset/solid-loops-feel.md(1 hunks)packages/import/src/index.ts(3 hunks)packages/import/tests/schema/fixtures/path-aliases/absolute/a.graphql(1 hunks)packages/import/tests/schema/fixtures/path-aliases/absolute/b.graphql(1 hunks)packages/import/tests/schema/fixtures/path-aliases/node-subpath/a.graphql(1 hunks)packages/import/tests/schema/fixtures/path-aliases/node-subpath/b.graphql(1 hunks)packages/import/tests/schema/import-schema.spec.ts(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Unit Test on Node 18 (ubuntu-latest) and GraphQL v15
- GitHub Check: Unit Test on Node 18 (windows-latest) and GraphQL v16
- GitHub Check: Unit Test on Node 18 (ubuntu-latest) and GraphQL v16
🔇 Additional comments (6)
packages/import/tests/schema/fixtures/path-aliases/absolute/b.graphql (1)
1-3: LGTM — minimal, correct fixture.
Defines TypeB as expected for the absolute-alias tests.packages/import/tests/schema/fixtures/path-aliases/node-subpath/b.graphql (1)
1-3: LGTM — minimal, correct fixture.
Matches the node-style subpath alias scenario.packages/import/tests/schema/fixtures/path-aliases/node-subpath/a.graphql (1)
1-10: LGTM — covers node-style subpath alias.
Imports via "#b.graphql" are correctly exercised; types are consistent.packages/import/src/index.ts (1)
670-681: All resolveFilePath calls use the new 2-arg signatureI ran the suggested grep across the
packages/import/src/index.tsfile:
- No instances of
resolveFilePathwith three arguments were found.- The only call (
line 177) uses exactly two parameters (cwd, filePath).No stale invocations remain—this change can be considered complete.
packages/import/tests/schema/fixtures/path-aliases/absolute/a.graphql (1)
1-10: LGTM: Absolute alias import in fixture verifiedThe companion
b.graphqlfixture is present atpackages/import/tests/schema/fixtures/path-aliases/absolute/b.graphql, anda.graphqlcorrectly imports it via#import TypeB from "/b.graphql"Approving these changes.
packages/import/tests/schema/import-schema.spec.ts (1)
1283-1292: LGTM: Adds a focused test for absolute path aliasesThis directly guards the regression by mapping '/*' and importing "/b.graphql". Nice coverage.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/import/src/index.ts (1)
175-180: Predefined-import precedence preserved; absolute aliases now resolved early — LGTM.Moving aliasing ahead of the absolute-path check while skipping predefinedImports fixes the bug without breaking virtual imports. Good call.
🧹 Nitpick comments (2)
packages/import/src/index.ts (2)
672-683: Consider extension fallbacks for local schema files.Optional: before falling back to resolveFrom, try .graphql/.gql variants when the direct joined path isn’t found. This helps common extensionless specifiers in repos that keep GraphQL next to code.
-function resolveFilePath(filePath: string, importFrom: string): string { +function resolveFilePath(filePath: string, importFrom: string): string { const dirName = dirname(filePath); try { - const fullPath = join(dirName, importFrom); - return realpathSync(fullPath); + const base = join(dirName, importFrom); + const candidates = [base, base + '.graphql', base + '.gql']; + for (const p of candidates) { + try { + return realpathSync(p); + } catch (e: any) { + if (e?.code !== 'ENOENT') throw e; + } + } } catch (e: any) { if (e.code === 'ENOENT') { return resolveFrom(dirName, importFrom); } throw e; } }
685-711: Match tsconfig paths semantics by preferring more specific alias patterns.Iterating Object.entries() order may mis-pick when multiple patterns match. Sort to prefer exact matches over wildcards and longer prefixes first.
-function applyPathAliases(filePath: string, pathAliases?: PathAliases): string { +function applyPathAliases(filePath: string, pathAliases?: PathAliases): string { if (pathAliases == null) { return filePath; } - for (const [prefixPattern, mapping] of Object.entries(pathAliases.mappings)) { + const entries = Object.entries(pathAliases.mappings).sort((a, b) => { + const [ap] = a; + const [bp] = b; + const aIsWildcard = ap.endsWith('*') ? 1 : 0; + const bIsWildcard = bp.endsWith('*') ? 1 : 0; + if (aIsWildcard !== bIsWildcard) return aIsWildcard - bIsWildcard; // exact before wildcard + const aLen = ap.replace('*', '').length; + const bLen = bp.replace('*', '').length; + return bLen - aLen; // longer (more specific) first + }); + + for (const [prefixPattern, mapping] of entries) { const matchedMapping = applyPathAlias(prefixPattern, mapping, filePath); if (matchedMapping == null) { continue; } - const resolvedMapping = resolveFrom(pathAliases.rootDir ?? process.cwd(), matchedMapping); - return realpathSync(resolvedMapping); + const baseDir = pathAliases.rootDir ?? process.cwd(); + const resolvedMapping = resolveFrom(baseDir, matchedMapping); + return realpathSync(resolvedMapping); } return filePath; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
.changeset/solid-loops-feel.md(1 hunks)packages/import/src/index.ts(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- .changeset/solid-loops-feel.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Unit Test on Node 24 (ubuntu-latest) and GraphQL v16
- GitHub Check: Unit Test on Node 22 (ubuntu-latest) and GraphQL v16
- GitHub Check: Unit Test on Node 18 (ubuntu-latest) and GraphQL v16
- GitHub Check: Unit Test on Node 18 (windows-latest) and GraphQL v16
- GitHub Check: Unit Test on Node 18 (ubuntu-latest) and GraphQL v15
- GitHub Check: Full Check on GraphQL v16
|
Thanks 🙏 |
* support absolute aliases * test node-style subpath import syntax * add changeset * address comments * more tests
Description
This change fixes a minor bug from #7310 that affects users of path aliases - a feature that lets GraphQL users leverage tsconfig.json#paths aliasing syntax for GraphQL imports.
The issue stems from the original implementation's execution order. Path alias resolution happens inside
resolveFilePath, which only runs for non-absolute paths:This means path aliases like
@/*→src/*work correctly, but absolute path aliases like/*→src/*fail since they never reach the resolution logic.The solution is straightforward: move path alias resolution before the absolute path check instead of after it.
Related #7311
Risk
Low impact change that only affects path alias users (a recently released feature with limited adoption). Among those users, only absolute path aliases are broken - and workarounds exist. The fix maintains full backward compatibility and doesn't impact users without path aliases.
Type of change
Please delete options that are not relevant.
How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can
reproduce. Please also list any relevant details for your test configuration
To run the tests yourself:
npm testTest Environment:
Checklist:
CONTRIBUTING doc and the
style guidelines of this project