Merge branch 'main' into fix/import-resolver-nodenext-rewrite

Resolve conflict in tests/skill/understand/test_extract_import_map.test.mjs
by keeping both new test groups — they cover independent fixes that should
coexist:
  - upstream #214: tsconfig path-alias targets with leading "./"
  - this PR  #294: NodeNext .js → .ts rewrite for ESM TypeScript imports

The extract-import-map.mjs script auto-merged cleanly; both fixes are
already present in the merged source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tirth Kanani
2026-06-01 10:25:37 +01:00
Unverified
13 changed files with 150 additions and 10 deletions
@@ -238,6 +238,111 @@ describe('extract-import-map.mjs — TypeScript / JavaScript resolver', () => {
);
});
// ── Issue #214: tsconfig path-alias targets with leading "./" ───────────
// create-next-app ships `"@/*": ["./*"]` as the default. With a root
// tsconfig the candidate would stay as "./lib/thing" while ctx.fileSet
// stores normalized "lib/thing", silently dropping every cross-module
// import edge. Three originally broken cases plus one regression guard
// for the already working `["*"]` form.
it('resolves tsconfig paths with leading "./" target and no baseUrl (#214)', () => {
projectRoot = setupTree({
'tsconfig.json': JSON.stringify({
compilerOptions: {
paths: { '@/*': ['./*'] },
},
}),
'src/app.ts': `import { x } from '@/lib/thing';\nconst _ = x;\n`,
'lib/thing.ts': `export const x = 1;\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
{ path: 'src/app.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'lib/thing.ts', language: 'typescript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/app.ts']).toContain('lib/thing.ts');
});
it('resolves tsconfig paths with leading "./" target and baseUrl "." (#214)', () => {
projectRoot = setupTree({
'tsconfig.json': JSON.stringify({
compilerOptions: {
baseUrl: '.',
paths: { '@/*': ['./*'] },
},
}),
'src/app.ts': `import { x } from '@/lib/thing';\nconst _ = x;\n`,
'lib/thing.ts': `export const x = 1;\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
{ path: 'src/app.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'lib/thing.ts', language: 'typescript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/app.ts']).toContain('lib/thing.ts');
});
it('resolves tsconfig paths with leading "./" target and baseUrl "src" (#214)', () => {
projectRoot = setupTree({
'tsconfig.json': JSON.stringify({
compilerOptions: {
baseUrl: 'src',
paths: { '@/*': ['./*'] },
},
}),
'src/app.ts': `import { x } from '@/thing';\nconst _ = x;\n`,
'src/thing.ts': `export const x = 1;\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
{ path: 'src/app.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/thing.ts', language: 'typescript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/app.ts']).toContain('src/thing.ts');
});
it('keeps resolving tsconfig paths with bare "*" target (#214 regression guard)', () => {
projectRoot = setupTree({
'tsconfig.json': JSON.stringify({
compilerOptions: {
paths: { '@/*': ['*'] },
},
}),
'src/app.ts': `import { x } from '@/lib/thing';\nconst _ = x;\n`,
'lib/thing.ts': `export const x = 1;\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
{ path: 'src/app.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'lib/thing.ts', language: 'typescript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/app.ts']).toContain('lib/thing.ts');
});
// ── #294: NodeNext / ESM TypeScript `.js → .ts` rewrite ────────────────
//
// Under `moduleResolution: NodeNext`, TypeScript does NOT rewrite import