Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f335069
fix: apply ESM .js extension fallback to tsconfig path alias resolution
chouzz May 12, 2026
13049de
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 12, 2026
b6a1fe1
chore(autofix): apply prettier + eslint fixes via /autofix command
github-actions[bot] May 12, 2026
7ed8bd9
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 12, 2026
6e25530
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 13, 2026
96e3290
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 13, 2026
7c316a6
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 13, 2026
8b0c410
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 13, 2026
837df7a
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 13, 2026
5ecd961
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 13, 2026
ee6d36d
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 13, 2026
122d3f1
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
8a109de
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
c453844
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
462796e
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
a17a18e
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
084bb39
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
45fb443
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
4be8869
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
dec773e
test(esm): cover .mjs/.cjs path-alias extension resolution
magyargergo May 14, 2026
371b491
test(esm): use Map for path aliases in resolveWithAlias helper
magyargergo May 14, 2026
7aa49d2
Merge branch 'main' into fix/esm-path-alias-js-extension
magyargergo May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions gitnexus/src/core/ingestion/import-resolvers/standard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ export const resolveImportPath = (
const resolved = tryResolveWithExtensions(rewritten, allFiles);
if (resolved) return cache(resolved);

// ESM fallback: strip .js/.jsx/.mjs/.cjs and retry with TS equivalents
const strippedAlias = stripJsExtension(rewritten);
if (strippedAlias !== null) {
const esmResolved = tryResolveWithExtensions(strippedAlias, allFiles);
if (esmResolved) return cache(esmResolved);
}

// Try suffix matching as fallback
const parts = rewritten.split('/').filter(Boolean);
const suffixResult = suffixResolve(parts, normalizedFileList, allFileList, index);
Expand Down Expand Up @@ -132,9 +139,6 @@ export const resolveImportPath = (

// TypeScript ESM: imports use .js/.jsx/.mjs/.cjs but source files are
// .ts/.tsx/.mts/.cts. Strip the JS-family extension and re-resolve.
// NOTE: This fallback only applies to relative imports. Path alias imports
// (e.g. @/utils.js via tsconfig paths) do not yet strip .js extensions —
// that is a known limitation tracked for follow-up.
if (language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript) {
const stripped = stripJsExtension(basePath);
if (stripped !== null) {
Expand Down
73 changes: 73 additions & 0 deletions gitnexus/test/unit/esm-extension-resolution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,76 @@ describe('stripJsExtension', () => {
it('returns null for .ts', () => expect(stripJsExtension('foo/bar.ts')).toBeNull());
it('returns null for no extension', () => expect(stripJsExtension('foo/bar')).toBeNull());
});

describe('ESM extension resolution — path aliases with .js extensions', () => {
const aliasAtToSrc = new Map<string, string>([['@/', 'src/']]);
const aliasTildeToSrc = new Map<string, string>([['~/', 'src/']]);

function resolveWithAlias(
currentFile: string,
importPath: string,
ctx: ReturnType<typeof makeCtx>,
aliases: Map<string, string>,
baseUrl = '.',
): string | null {
return resolveImportPath(
currentFile,
importPath,
ctx.allFilesSet,
ctx.files,
ctx.normalized,
ctx.cache,
SupportedLanguages.TypeScript,
{ aliases, baseUrl },
ctx.index,
);
}

it('resolves @/utils.js to src/utils.ts via alias', () => {
const ctx = makeCtx(['src/index.ts', 'src/utils.ts']);
const result = resolveWithAlias('src/index.ts', '@/utils.js', ctx, aliasAtToSrc, '.');
expect(result).toBe('src/utils.ts');
});

it('resolves @/component.jsx to src/component.tsx via alias', () => {
const ctx = makeCtx(['src/index.ts', 'src/component.tsx']);
const result = resolveWithAlias('src/index.ts', '@/component.jsx', ctx, aliasAtToSrc, '.');
expect(result).toBe('src/component.tsx');
});

it('resolves @/config.mjs to src/config.mts via alias', () => {
const ctx = makeCtx(['src/index.ts', 'src/config.mts']);
const result = resolveWithAlias('src/index.ts', '@/config.mjs', ctx, aliasAtToSrc, '.');
expect(result).toBe('src/config.mts');
});

it('resolves @/legacy.cjs to src/legacy.cts via alias', () => {
const ctx = makeCtx(['src/index.ts', 'src/legacy.cts']);
const result = resolveWithAlias('src/index.ts', '@/legacy.cjs', ctx, aliasAtToSrc, '.');
expect(result).toBe('src/legacy.cts');
});

it('prefers actual .js file over TS fallback in alias resolution', () => {
const ctx = makeCtx(['src/index.ts', 'src/utils.js', 'src/utils.ts']);
const result = resolveWithAlias('src/index.ts', '@/utils.js', ctx, aliasAtToSrc, '.');
expect(result).toBe('src/utils.js');
});

it('resolves alias with baseUrl prefix', () => {
const ctx = makeCtx(['app/src/index.ts', 'app/src/helpers/token.ts']);
const result = resolveWithAlias(
'app/src/index.ts',
'~/helpers/token.js',
ctx,
aliasTildeToSrc,
'app',
);
expect(result).toBe('app/src/helpers/token.ts');
});

it('returns null when alias .js import has no matching source', () => {
const ctx = makeCtx(['src/index.ts']);
const result = resolveWithAlias('src/index.ts', '@/missing.js', ctx, aliasAtToSrc, '.');
expect(result).toBeNull();
});
});
Loading