Skip to content

Commit 9b84926

Browse files
authored
fix(js): handle extending from multiple config files and from local workspace packages in plugin (#30486)
## Current Behavior The `@nx/js/typescript` plugin doesn't handle [extending from multiple tsconfig files](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#supporting-multiple-configuration-files-in-extends). It also identifies local workspace packages linked by the package manager as external dependencies. ## Expected Behavior The `@nx/js/typescript` plugin should support extending from multiple tsconfig files. It should also identify local workspace packages linked by the package manager correctly and add their resolved path to the task inputs (not as external dependencies). ## Related Issue(s) Fixes #29678
1 parent 2d210b8 commit 9b84926

File tree

4 files changed

+284
-61
lines changed

4 files changed

+284
-61
lines changed

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

Lines changed: 192 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,27 +1177,114 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
11771177
`);
11781178
});
11791179

1180-
it('should add extended config files supporting node.js style resolution and set npm packages as external dependencies', async () => {
1180+
it('should add extended config files when there are multiple extended config files', async () => {
11811181
await applyFilesToTempFsAndContext(tempFs, context, {
11821182
'tsconfig.base.json': JSON.stringify({
1183-
extends: '@tsconfig/strictest/tsconfig.json',
1183+
exclude: ['node_modules', 'tmp'],
1184+
}),
1185+
'tsconfig.foo.json': '{}',
1186+
'tsconfig.bar.json': JSON.stringify({
1187+
extends: './tsconfig.foo.json',
1188+
exclude: ['node_modules', 'dist'], // extended last, it will override the base config
1189+
}),
1190+
'libs/my-lib/tsconfig.json': JSON.stringify({
1191+
extends: ['../../tsconfig.base.json', '../../tsconfig.bar.json'], // should collect both and any recursive extended configs as inputs
1192+
include: ['src/**/*.ts'],
1193+
// set this to keep outputs smaller
1194+
compilerOptions: { outDir: 'dist' },
1195+
}),
1196+
'libs/my-lib/package.json': `{}`,
1197+
});
1198+
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
1199+
.toMatchInlineSnapshot(`
1200+
{
1201+
"projects": {
1202+
"libs/my-lib": {
1203+
"projectType": "library",
1204+
"targets": {
1205+
"typecheck": {
1206+
"cache": true,
1207+
"command": "tsc --build --emitDeclarationOnly",
1208+
"dependsOn": [
1209+
"^typecheck",
1210+
],
1211+
"inputs": [
1212+
"{projectRoot}/package.json",
1213+
"{workspaceRoot}/tsconfig.base.json",
1214+
"{workspaceRoot}/tsconfig.bar.json",
1215+
"{workspaceRoot}/tsconfig.foo.json",
1216+
"{projectRoot}/tsconfig.json",
1217+
"{projectRoot}/src/**/*.ts",
1218+
"!{workspaceRoot}/node_modules",
1219+
"!{workspaceRoot}/dist",
1220+
"^production",
1221+
{
1222+
"externalDependencies": [
1223+
"typescript",
1224+
],
1225+
},
1226+
],
1227+
"metadata": {
1228+
"description": "Runs type-checking for the project.",
1229+
"help": {
1230+
"command": "npx tsc --build --help",
1231+
"example": {
1232+
"args": [
1233+
"--force",
1234+
],
1235+
},
1236+
},
1237+
"technologies": [
1238+
"typescript",
1239+
],
1240+
},
1241+
"options": {
1242+
"cwd": "libs/my-lib",
1243+
},
1244+
"outputs": [
1245+
"{projectRoot}/dist",
1246+
],
1247+
"syncGenerators": [
1248+
"@nx/js:typescript-sync",
1249+
],
1250+
},
1251+
},
1252+
},
1253+
},
1254+
}
1255+
`);
1256+
});
1257+
1258+
it('should add extended config files supporting node.js style resolution and local workspace packages', async () => {
1259+
await applyFilesToTempFsAndContext(tempFs, context, {
1260+
'tsconfig.base.json': JSON.stringify({
1261+
extends: '@tsconfig/strictest/tsconfig.json', // should be resolved and the package name should be included in inputs as an external dependency
11841262
exclude: ['node_modules', 'tmp'],
11851263
}),
11861264
'tsconfig.foo.json': JSON.stringify({
11871265
extends: './tsconfig.base', // extensionless relative path
11881266
}),
11891267
'libs/my-lib/tsconfig.json': JSON.stringify({
1190-
extends: '../../tsconfig.foo.json',
1268+
extends: [
1269+
'../../tsconfig.foo.json',
1270+
'@my-org/my-package/tsconfig.base.json', // should be resolved and the path should be included in inputs
1271+
],
11911272
include: ['src/**/*.ts'],
11921273
// set this to keep outputs smaller
11931274
compilerOptions: { outDir: 'dist' },
11941275
}),
11951276
'libs/my-lib/package.json': `{}`,
1196-
});
1197-
// simulate @tsconfig/strictest package
1198-
tempFs.createFilesSync({
1277+
'libs/my-package/package.json': `{}`,
1278+
'libs/my-package/tsconfig.base.json': `{}`,
1279+
// simulate @tsconfig/strictest package
11991280
'node_modules/@tsconfig/strictest/tsconfig.json': '{}',
12001281
});
1282+
// create a symlink to simulate a local workspace package linked by a package manager
1283+
tempFs.createSymlinkSync(
1284+
'libs/my-package',
1285+
'node_modules/@my-org/my-package',
1286+
'dir'
1287+
);
12011288

12021289
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
12031290
.toMatchInlineSnapshot(`
@@ -1216,6 +1303,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
12161303
"{projectRoot}/package.json",
12171304
"{workspaceRoot}/tsconfig.foo.json",
12181305
"{workspaceRoot}/tsconfig.base.json",
1306+
"{workspaceRoot}/libs/my-package/tsconfig.base.json",
12191307
"{projectRoot}/tsconfig.json",
12201308
"{projectRoot}/src/**/*.ts",
12211309
"!{workspaceRoot}/node_modules",
@@ -3655,29 +3743,120 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
36553743
`);
36563744
});
36573745

3658-
it('should add extended config files supporting node.js style resolution and set npm packages as external dependencies', async () => {
3746+
it('should add extended config files when there are multiple extended config files', async () => {
3747+
await applyFilesToTempFsAndContext(tempFs, context, {
3748+
'tsconfig.base.json': JSON.stringify({
3749+
exclude: ['node_modules', 'tmp'],
3750+
}),
3751+
'tsconfig.foo.json': '{}',
3752+
'tsconfig.bar.json': JSON.stringify({
3753+
extends: './tsconfig.foo.json',
3754+
exclude: ['node_modules', 'dist'], // extended last, it will override the base config
3755+
}),
3756+
'libs/my-lib/tsconfig.json': '{}',
3757+
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
3758+
extends: ['../../tsconfig.base.json', '../../tsconfig.bar.json'], // should collect both and any recursive extended configs as inputs
3759+
include: ['src/**/*.ts'],
3760+
compilerOptions: { outDir: 'dist' },
3761+
}),
3762+
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
3763+
});
3764+
expect(
3765+
await invokeCreateNodesOnMatchingFiles(context, {
3766+
typecheck: false,
3767+
build: true,
3768+
})
3769+
).toMatchInlineSnapshot(`
3770+
{
3771+
"projects": {
3772+
"libs/my-lib": {
3773+
"projectType": "library",
3774+
"targets": {
3775+
"build": {
3776+
"cache": true,
3777+
"command": "tsc --build tsconfig.lib.json",
3778+
"dependsOn": [
3779+
"^build",
3780+
],
3781+
"inputs": [
3782+
"{projectRoot}/package.json",
3783+
"{workspaceRoot}/tsconfig.base.json",
3784+
"{workspaceRoot}/tsconfig.bar.json",
3785+
"{workspaceRoot}/tsconfig.foo.json",
3786+
"{projectRoot}/tsconfig.lib.json",
3787+
"{projectRoot}/src/**/*.ts",
3788+
"!{workspaceRoot}/node_modules",
3789+
"!{workspaceRoot}/dist",
3790+
"^production",
3791+
{
3792+
"externalDependencies": [
3793+
"typescript",
3794+
],
3795+
},
3796+
],
3797+
"metadata": {
3798+
"description": "Builds the project with \`tsc\`.",
3799+
"help": {
3800+
"command": "npx tsc --build --help",
3801+
"example": {
3802+
"args": [
3803+
"--force",
3804+
],
3805+
},
3806+
},
3807+
"technologies": [
3808+
"typescript",
3809+
],
3810+
},
3811+
"options": {
3812+
"cwd": "libs/my-lib",
3813+
},
3814+
"outputs": [
3815+
"{projectRoot}/dist",
3816+
],
3817+
"syncGenerators": [
3818+
"@nx/js:typescript-sync",
3819+
],
3820+
},
3821+
},
3822+
},
3823+
},
3824+
}
3825+
`);
3826+
});
3827+
3828+
it('should add extended config files supporting node.js style resolution and local workspace packages', async () => {
36593829
await applyFilesToTempFsAndContext(tempFs, context, {
36603830
'tsconfig.base.json': JSON.stringify({
3661-
extends: '@tsconfig/strictest/tsconfig.json',
3831+
extends: '@tsconfig/strictest/tsconfig.json', // should be resolved and the package name should be included in inputs as an external dependency
36623832
exclude: ['node_modules', 'tmp'],
36633833
}),
36643834
'tsconfig.foo.json': JSON.stringify({
36653835
extends: './tsconfig.base', // extensionless relative path
36663836
}),
36673837
'libs/my-lib/tsconfig.json': '{}',
36683838
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
3669-
extends: '../../tsconfig.foo.json',
3839+
extends: [
3840+
'../../tsconfig.foo.json',
3841+
'@my-org/my-package/tsconfig.base.json', // should be resolved and the path should be included in inputs
3842+
],
36703843
compilerOptions: {
36713844
outDir: 'dist',
36723845
},
36733846
include: ['src/**/*.ts'],
36743847
}),
36753848
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
3676-
});
3677-
// simulate @tsconfig/strictest package
3678-
tempFs.createFilesSync({
3849+
'libs/my-package/package.json': `{}`,
3850+
'libs/my-package/tsconfig.base.json': `{}`,
3851+
// simulate @tsconfig/strictest package
36793852
'node_modules/@tsconfig/strictest/tsconfig.json': '{}',
36803853
});
3854+
// create a symlink to simulate a local workspace package linked by a package manager
3855+
tempFs.createSymlinkSync(
3856+
'libs/my-package',
3857+
'node_modules/@my-org/my-package',
3858+
'dir'
3859+
);
36813860

36823861
expect(
36833862
await invokeCreateNodesOnMatchingFiles(context, {
@@ -3700,6 +3879,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
37003879
"{projectRoot}/package.json",
37013880
"{workspaceRoot}/tsconfig.foo.json",
37023881
"{workspaceRoot}/tsconfig.base.json",
3882+
"{workspaceRoot}/libs/my-package/tsconfig.base.json",
37033883
"{projectRoot}/tsconfig.lib.json",
37043884
"{projectRoot}/src/**/*.ts",
37053885
"!{workspaceRoot}/node_modules",

0 commit comments

Comments
 (0)