Skip to content

Commit 1cab59c

Browse files
leosvelperezjaysoo
authored andcommitted
fix(js): fix resolution of extended tsconfig files in plugin (#28535)
1 parent b07b057 commit 1cab59c

File tree

2 files changed

+214
-14
lines changed

2 files changed

+214
-14
lines changed

packages/js/src/plugins/typescript/plugin.spec.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,83 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
594594
`);
595595
});
596596

597+
it('should add extended config files supporting node.js style resolution and set npm packages as external dependencies', async () => {
598+
await applyFilesToTempFsAndContext(tempFs, context, {
599+
'tsconfig.base.json': JSON.stringify({
600+
extends: '@tsconfig/strictest/tsconfig.json',
601+
exclude: ['node_modules', 'tmp'],
602+
}),
603+
'tsconfig.foo.json': JSON.stringify({
604+
extends: './tsconfig.base', // extensionless relative path
605+
}),
606+
'libs/my-lib/tsconfig.json': JSON.stringify({
607+
extends: '../../tsconfig.foo.json',
608+
include: ['src/**/*.ts'],
609+
}),
610+
'libs/my-lib/package.json': `{}`,
611+
});
612+
// simulate @tsconfig/strictest package
613+
tempFs.createFilesSync({
614+
'node_modules/@tsconfig/strictest/tsconfig.json': '{}',
615+
});
616+
617+
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
618+
.toMatchInlineSnapshot(`
619+
{
620+
"projects": {
621+
"libs/my-lib": {
622+
"projectType": "library",
623+
"targets": {
624+
"typecheck": {
625+
"cache": true,
626+
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
627+
"dependsOn": [
628+
"^typecheck",
629+
],
630+
"inputs": [
631+
"{workspaceRoot}/tsconfig.foo.json",
632+
"{workspaceRoot}/tsconfig.base.json",
633+
"{projectRoot}/tsconfig.json",
634+
"{projectRoot}/src/**/*.ts",
635+
"!{workspaceRoot}/node_modules",
636+
"!{workspaceRoot}/tmp",
637+
"^production",
638+
{
639+
"externalDependencies": [
640+
"typescript",
641+
"@tsconfig/strictest",
642+
],
643+
},
644+
],
645+
"metadata": {
646+
"description": "Runs type-checking for the project.",
647+
"help": {
648+
"command": "npx tsc --build --help",
649+
"example": {
650+
"args": [
651+
"--force",
652+
],
653+
},
654+
},
655+
"technologies": [
656+
"typescript",
657+
],
658+
},
659+
"options": {
660+
"cwd": "libs/my-lib",
661+
},
662+
"outputs": [],
663+
"syncGenerators": [
664+
"@nx/js:typescript-sync",
665+
],
666+
},
667+
},
668+
},
669+
},
670+
}
671+
`);
672+
});
673+
597674
it('should add files from internal project references', async () => {
598675
await applyFilesToTempFsAndContext(tempFs, context, {
599676
'libs/my-lib/tsconfig.json': JSON.stringify({
@@ -1999,6 +2076,88 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
19992076
`);
20002077
});
20012078

2079+
it('should add extended config files supporting node.js style resolution and set npm packages as external dependencies', async () => {
2080+
await applyFilesToTempFsAndContext(tempFs, context, {
2081+
'tsconfig.base.json': JSON.stringify({
2082+
extends: '@tsconfig/strictest/tsconfig.json',
2083+
exclude: ['node_modules', 'tmp'],
2084+
}),
2085+
'tsconfig.foo.json': JSON.stringify({
2086+
extends: './tsconfig.base', // extensionless relative path
2087+
}),
2088+
'libs/my-lib/tsconfig.json': '{}',
2089+
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
2090+
extends: '../../tsconfig.foo.json',
2091+
include: ['src/**/*.ts'],
2092+
}),
2093+
'libs/my-lib/package.json': `{}`,
2094+
});
2095+
// simulate @tsconfig/strictest package
2096+
tempFs.createFilesSync({
2097+
'node_modules/@tsconfig/strictest/tsconfig.json': '{}',
2098+
});
2099+
2100+
expect(
2101+
await invokeCreateNodesOnMatchingFiles(context, {
2102+
typecheck: false,
2103+
build: true,
2104+
})
2105+
).toMatchInlineSnapshot(`
2106+
{
2107+
"projects": {
2108+
"libs/my-lib": {
2109+
"projectType": "library",
2110+
"targets": {
2111+
"build": {
2112+
"cache": true,
2113+
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
2114+
"dependsOn": [
2115+
"^build",
2116+
],
2117+
"inputs": [
2118+
"{workspaceRoot}/tsconfig.foo.json",
2119+
"{workspaceRoot}/tsconfig.base.json",
2120+
"{projectRoot}/tsconfig.lib.json",
2121+
"{projectRoot}/src/**/*.ts",
2122+
"!{workspaceRoot}/node_modules",
2123+
"!{workspaceRoot}/tmp",
2124+
"^production",
2125+
{
2126+
"externalDependencies": [
2127+
"typescript",
2128+
"@tsconfig/strictest",
2129+
],
2130+
},
2131+
],
2132+
"metadata": {
2133+
"description": "Builds the project with \`tsc\`.",
2134+
"help": {
2135+
"command": "npx tsc --build --help",
2136+
"example": {
2137+
"args": [
2138+
"--force",
2139+
],
2140+
},
2141+
},
2142+
"technologies": [
2143+
"typescript",
2144+
],
2145+
},
2146+
"options": {
2147+
"cwd": "libs/my-lib",
2148+
},
2149+
"outputs": [],
2150+
"syncGenerators": [
2151+
"@nx/js:typescript-sync",
2152+
],
2153+
},
2154+
},
2155+
},
2156+
},
2157+
}
2158+
`);
2159+
});
2160+
20022161
it('should add files from internal project references', async () => {
20032162
await applyFilesToTempFsAndContext(tempFs, context, {
20042163
'libs/my-lib/tsconfig.json': '{}',

packages/js/src/plugins/typescript/plugin.ts

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ async function createNodesInternal(
182182
readCachedTsConfig(fullConfigPath)
183183
);
184184
const nodeHash = hashArray([
185-
...[configFilePath, ...extendedConfigFiles, lockFileName].map(hashFile),
185+
...[configFilePath, ...extendedConfigFiles.files, lockFileName].map(
186+
hashFile
187+
),
186188
hashObject(options),
187189
]);
188190
const cacheKey = `${nodeHash}_${configFilePath}`;
@@ -334,14 +336,16 @@ function getInputs(
334336
projectRoot: string
335337
): TargetConfiguration['inputs'] {
336338
const configFiles = new Set<string>();
337-
const includePaths = new Set<string>();
338-
const excludePaths = new Set<string>();
339+
const externalDependencies = ['typescript'];
339340

340341
const extendedConfigFiles = getExtendedConfigFiles(configFilePath, tsConfig);
341-
extendedConfigFiles.forEach((configPath) => {
342+
extendedConfigFiles.files.forEach((configPath) => {
342343
configFiles.add(configPath);
343344
});
345+
externalDependencies.push(...extendedConfigFiles.packages);
344346

347+
const includePaths = new Set<string>();
348+
const excludePaths = new Set<string>();
345349
const projectTsConfigFiles: [string, ParsedCommandLine][] = [
346350
[configFilePath, tsConfig],
347351
...Object.entries(internalProjectReferences),
@@ -429,7 +433,7 @@ function getInputs(
429433
inputs.push('production' in namedInputs ? '^production' : '^default');
430434
}
431435

432-
inputs.push({ externalDependencies: ['typescript'] });
436+
inputs.push({ externalDependencies });
433437

434438
return inputs;
435439
}
@@ -537,23 +541,36 @@ function pathToInputOrOutput(
537541
function getExtendedConfigFiles(
538542
tsConfigPath: string,
539543
tsConfig: ParsedCommandLine
540-
): string[] {
544+
): {
545+
files: string[];
546+
packages: string[];
547+
} {
541548
const extendedConfigFiles = new Set<string>();
549+
const extendedExternalPackages = new Set<string>();
542550

543551
let currentConfigPath = tsConfigPath;
544552
let currentConfig = tsConfig;
545553
while (currentConfig.raw?.extends) {
546-
const extendedConfigPath = join(
547-
dirname(currentConfigPath),
548-
currentConfig.raw.extends
554+
const extendedConfigPath = resolveExtendedTsConfigPath(
555+
currentConfig.raw.extends,
556+
dirname(currentConfigPath)
549557
);
550-
extendedConfigFiles.add(extendedConfigPath);
551-
const extendedConfig = readCachedTsConfig(extendedConfigPath);
552-
currentConfigPath = extendedConfigPath;
553-
currentConfig = extendedConfig;
558+
if (!extendedConfigPath) {
559+
break;
560+
}
561+
if (extendedConfigPath.externalPackage) {
562+
extendedExternalPackages.add(extendedConfigPath.externalPackage);
563+
break;
564+
}
565+
extendedConfigFiles.add(extendedConfigPath.filePath);
566+
currentConfig = readCachedTsConfig(extendedConfigPath.filePath);
567+
currentConfigPath = extendedConfigPath.filePath;
554568
}
555569

556-
return Array.from(extendedConfigFiles);
570+
return {
571+
files: Array.from(extendedConfigFiles),
572+
packages: Array.from(extendedExternalPackages),
573+
};
557574
}
558575

559576
function resolveInternalProjectReferences(
@@ -742,3 +759,27 @@ function normalizePluginOptions(
742759
build,
743760
};
744761
}
762+
763+
function resolveExtendedTsConfigPath(
764+
tsConfigPath: string,
765+
directory?: string
766+
): { filePath: string; externalPackage?: string } | null {
767+
try {
768+
const resolvedPath = require.resolve(tsConfigPath, {
769+
paths: directory ? [directory] : undefined,
770+
});
771+
772+
if (tsConfigPath.startsWith('.')) {
773+
return { filePath: resolvedPath };
774+
}
775+
776+
// parse the package from the tsconfig path
777+
const packageName = tsConfigPath.startsWith('@')
778+
? tsConfigPath.split('/').slice(0, 2).join('/')
779+
: tsConfigPath.split('/')[0];
780+
781+
return { filePath: resolvedPath, externalPackage: packageName };
782+
} catch {
783+
return null;
784+
}
785+
}

0 commit comments

Comments
 (0)