Skip to content

Commit b68c349

Browse files
authored
fix(misc): loose and fix the ts solution setup requirements and use it when there is no root tsconfig file (#32150)
## Current Behavior - The logic to detect whether the workspace uses the TS solution setup restricts the top-level properties in the `tsconfig.base.json` file and only allows `compilerOptions`. This prevents extending from other tsconfig files in the `tsconfig.base.json` file. - Generating projects in a workspace without any known root tsconfig files (`tsconfig.json`, `tsconfig.base.json`) results in the old setup being generated. Workspaces without root TS configuration should be able to use the TS solution setup. ## Expected Behavior - The logic to detect whether the workspace uses the TS solution should not restrict the top-level properties in the `tsconfig.base.json` file. The only things required for the TS solution setup are to have `composite: true` and `declarations` enabled. - The TS solution setup should be used when generating projects in a workspace without any known root tsconfig files (`tsconfig.json`, `tsconfig.base.json`). ## Related Issue(s) Fixes #32134
1 parent 847d699 commit b68c349

25 files changed

Lines changed: 273 additions & 109 deletions

File tree

e2e/workspace-create/src/create-nx-workspace-npm.test.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('create-nx-workspace --preset=npm', () => {
2929

3030
afterEach(() => {
3131
// cleanup previous projects
32-
runCommand(`rm -rf packages/** tsconfig.base.json`);
32+
runCommand(`rm -rf packages/** tsconfig.base.json tsconfig.json`);
3333
});
3434

3535
afterAll(() => {
@@ -80,11 +80,14 @@ describe('create-nx-workspace --preset=npm', () => {
8080
expect(() =>
8181
runCLI(`generate @nx/js:library packages/${libName} --no-interactive`)
8282
).not.toThrow();
83-
checkFilesExist('tsconfig.base.json');
84-
const tsconfig = readJson(`tsconfig.base.json`);
85-
expect(tsconfig.compilerOptions.paths).toEqual({
86-
[`@${wsName}/${libName}`]: [`packages/${libName}/src/index.ts`],
87-
});
83+
checkFilesExist('tsconfig.base.json', 'tsconfig.json');
84+
const tsconfigBase = readJson(`tsconfig.base.json`);
85+
expect(tsconfigBase.compilerOptions.paths).toBeUndefined();
86+
const tsconfig = readJson(`tsconfig.json`);
87+
expect(tsconfig.extends).toBe('./tsconfig.base.json');
88+
expect(tsconfig.references).toStrictEqual([
89+
{ path: `./packages/${libName}` },
90+
]);
8891
});
8992

9093
it('should add web application', () => {
@@ -117,11 +120,14 @@ describe('create-nx-workspace --preset=npm', () => {
117120
expect(() => {
118121
runCLI(`generate @nx/react:lib packages/${libName} --no-interactive`);
119122
}).not.toThrow();
120-
checkFilesExist('tsconfig.base.json');
121-
const tsconfig = readJson(`tsconfig.base.json`);
122-
expect(tsconfig.compilerOptions.paths).toEqual({
123-
[`@${wsName}/${libName}`]: [`packages/${libName}/src/index.ts`],
124-
});
123+
checkFilesExist('tsconfig.base.json', 'tsconfig.json');
124+
const tsconfigBase = readJson(`tsconfig.base.json`);
125+
expect(tsconfigBase.compilerOptions.paths).toBeUndefined();
126+
const tsconfig = readJson(`tsconfig.json`);
127+
expect(tsconfig.extends).toBe('./tsconfig.base.json');
128+
expect(tsconfig.references).toStrictEqual([
129+
{ path: `./packages/${libName}` },
130+
]);
125131
});
126132

127133
it('should add next application', () => {
@@ -143,12 +149,14 @@ describe('create-nx-workspace --preset=npm', () => {
143149
expect(() => {
144150
runCLI(`generate @nx/next:lib packages/${libName} --no-interactive`);
145151
}).not.toThrow();
146-
checkFilesExist('tsconfig.base.json');
147-
const tsconfig = readJson(`tsconfig.base.json`);
148-
expect(tsconfig.compilerOptions.paths).toEqual({
149-
[`@${wsName}/${libName}`]: [`packages/${libName}/src/index.ts`],
150-
[`@${wsName}/${libName}/server`]: [`packages/${libName}/src/server.ts`],
151-
});
152+
checkFilesExist('tsconfig.base.json', 'tsconfig.json');
153+
const tsconfigBase = readJson(`tsconfig.base.json`);
154+
expect(tsconfigBase.compilerOptions.paths).toBeUndefined();
155+
const tsconfig = readJson(`tsconfig.json`);
156+
expect(tsconfig.extends).toBe('./tsconfig.base.json');
157+
expect(tsconfig.references).toStrictEqual([
158+
{ path: `./packages/${libName}` },
159+
]);
152160
});
153161

154162
it('should add react-native application', () => {
@@ -174,11 +182,14 @@ describe('create-nx-workspace --preset=npm', () => {
174182
`generate @nx/react-native:lib packages/${libName} --no-interactive`
175183
);
176184
}).not.toThrow();
177-
checkFilesExist('tsconfig.base.json');
178-
const tsconfig = readJson(`tsconfig.base.json`);
179-
expect(tsconfig.compilerOptions.paths).toEqual({
180-
[`@${wsName}/${libName}`]: [`packages/${libName}/src/index.ts`],
181-
});
185+
checkFilesExist('tsconfig.base.json', 'tsconfig.json');
186+
const tsconfigBase = readJson(`tsconfig.base.json`);
187+
expect(tsconfigBase.compilerOptions.paths).toBeUndefined();
188+
const tsconfig = readJson(`tsconfig.json`);
189+
expect(tsconfig.extends).toBe('./tsconfig.base.json');
190+
expect(tsconfig.references).toStrictEqual([
191+
{ path: `./packages/${libName}` },
192+
]);
182193
});
183194

184195
it('should add node application', () => {
@@ -200,11 +211,14 @@ describe('create-nx-workspace --preset=npm', () => {
200211
expect(() => {
201212
runCLI(`generate @nx/node:lib packages/${libName} --no-interactive`);
202213
}).not.toThrow();
203-
checkFilesExist('tsconfig.base.json');
204-
const tsconfig = readJson(`tsconfig.base.json`);
205-
expect(tsconfig.compilerOptions.paths).toEqual({
206-
[`@${wsName}/${libName}`]: [`packages/${libName}/src/index.ts`],
207-
});
214+
checkFilesExist('tsconfig.base.json', 'tsconfig.json');
215+
const tsconfigBase = readJson(`tsconfig.base.json`);
216+
expect(tsconfigBase.compilerOptions.paths).toBeUndefined();
217+
const tsconfig = readJson(`tsconfig.json`);
218+
expect(tsconfig.extends).toBe('./tsconfig.base.json');
219+
expect(tsconfig.references).toStrictEqual([
220+
{ path: `./packages/${libName}` },
221+
]);
208222
});
209223

210224
it('should add nest application', () => {
@@ -226,11 +240,14 @@ describe('create-nx-workspace --preset=npm', () => {
226240
expect(() => {
227241
runCLI(`generate @nx/nest:lib packages/${libName} --no-interactive`);
228242
}).not.toThrow();
229-
checkFilesExist('tsconfig.base.json');
230-
const tsconfig = readJson(`tsconfig.base.json`);
231-
expect(tsconfig.compilerOptions.paths).toEqual({
232-
[`@${wsName}/${libName}`]: [`packages/${libName}/src/index.ts`],
233-
});
243+
checkFilesExist('tsconfig.base.json', 'tsconfig.json');
244+
const tsconfigBase = readJson(`tsconfig.base.json`);
245+
expect(tsconfigBase.compilerOptions.paths).toBeUndefined();
246+
const tsconfig = readJson(`tsconfig.json`);
247+
expect(tsconfig.extends).toBe('./tsconfig.base.json');
248+
expect(tsconfig.references).toStrictEqual([
249+
{ path: `./packages/${libName}` },
250+
]);
234251
});
235252

236253
it('should add express application', () => {

packages/detox/src/generators/application/application.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Schema } from './schema';
1111
import { ensureDependencies } from './lib/ensure-dependencies';
1212
import {
1313
addProjectToTsSolutionWorkspace,
14+
shouldConfigureTsSolutionSetup,
1415
updateTsconfigFiles,
1516
} from '@nx/js/src/utils/typescript/ts-solution-setup';
1617
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
@@ -27,7 +28,9 @@ export async function detoxApplicationGeneratorInternal(
2728
host: Tree,
2829
schema: Schema
2930
) {
31+
const addTsPlugin = shouldConfigureTsSolutionSetup(host, schema.addPlugin);
3032
const jsInitTask = await jsInitGenerator(host, {
33+
addTsPlugin,
3134
skipFormat: true,
3235
});
3336

packages/expo/src/generators/application/application.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { initGenerator as jsInitGenerator } from '@nx/js';
99
import {
1010
addProjectToTsSolutionWorkspace,
11+
shouldConfigureTsSolutionSetup,
1112
updateTsconfigFiles,
1213
} from '@nx/js/src/utils/typescript/ts-solution-setup';
1314

@@ -42,10 +43,15 @@ export async function expoApplicationGeneratorInternal(
4243
schema: Schema
4344
): Promise<GeneratorCallback> {
4445
const tasks: GeneratorCallback[] = [];
46+
const addTsPlugin = shouldConfigureTsSolutionSetup(
47+
host,
48+
schema.addPlugin,
49+
schema.useTsSolution
50+
);
4551
const jsInitTask = await jsInitGenerator(host, {
4652
...schema,
4753
skipFormat: true,
48-
addTsPlugin: schema.useTsSolution,
54+
addTsPlugin,
4955
formatter: schema.formatter,
5056
platform: 'web',
5157
});

packages/expo/src/generators/library/library.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { initRootBabelConfig } from '../../utils/init-root-babel-config';
2929
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
3030
import {
3131
addProjectToTsSolutionWorkspace,
32+
shouldConfigureTsSolutionSetup,
3233
updateTsconfigFiles,
3334
} from '@nx/js/src/utils/typescript/ts-solution-setup';
3435
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
@@ -56,8 +57,10 @@ export async function expoLibraryGeneratorInternal(
5657
): Promise<GeneratorCallback> {
5758
const tasks: GeneratorCallback[] = [];
5859

60+
const addTsPlugin = shouldConfigureTsSolutionSetup(host, schema.addPlugin);
5961
const jsInitTask = await jsInitGenerator(host, {
6062
...schema,
63+
addTsPlugin,
6164
skipFormat: true,
6265
});
6366
tasks.push(jsInitTask);

packages/js/src/generators/library/library.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
addProjectToTsSolutionWorkspace,
5353
isUsingTsSolutionSetup,
5454
isUsingTypeScriptPlugin,
55+
shouldConfigureTsSolutionSetup,
5556
} from '../../utils/typescript/ts-solution-setup';
5657
import {
5758
esbuildVersion,
@@ -91,12 +92,14 @@ export async function libraryGeneratorInternal(
9192
) {
9293
const tasks: GeneratorCallback[] = [];
9394

95+
const addTsPlugin = shouldConfigureTsSolutionSetup(tree, schema.addPlugin);
9496
tasks.push(
9597
await jsInitGenerator(tree, {
9698
...schema,
9799
skipFormat: true,
98100
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
99101
addTsConfigBase: true,
102+
addTsPlugin,
100103
// In the new setup, Prettier is prompted for and installed during `create-nx-workspace`.
101104
formatter: isUsingTsSolutionSetup(tree) ? 'none' : 'prettier',
102105
})

packages/js/src/utils/typescript/ts-solution-setup.ts

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,34 @@ export function isUsingTypeScriptPlugin(tree: Tree): boolean {
3232
);
3333
}
3434

35+
export function shouldConfigureTsSolutionSetup(
36+
tree: Tree,
37+
addPlugins: boolean,
38+
addTsPlugin?: boolean
39+
): boolean {
40+
if (addTsPlugin !== undefined) {
41+
return addTsPlugin;
42+
}
43+
44+
if (addPlugins === undefined) {
45+
const nxJson = readNxJson(tree);
46+
addPlugins =
47+
process.env.NX_ADD_PLUGINS !== 'false' &&
48+
nxJson.useInferencePlugins !== false;
49+
}
50+
51+
if (!addPlugins) {
52+
return false;
53+
}
54+
55+
if (!isUsingPackageManagerWorkspaces(tree)) {
56+
return false;
57+
}
58+
59+
// if there are no root tsconfig files, we should configure the TS solution setup
60+
return !tree.exists('tsconfig.base.json') && !tree.exists('tsconfig.json');
61+
}
62+
3563
export function isUsingTsSolutionSetup(tree?: Tree): boolean {
3664
tree ??= new FsTree(workspaceRoot, false);
3765

@@ -41,6 +69,15 @@ export function isUsingTsSolutionSetup(tree?: Tree): boolean {
4169
);
4270
}
4371

72+
/**
73+
* The TS solution setup requires:
74+
* - `tsconfig.base.json`: TS config with common compiler options needed by the
75+
* majority of projects in the workspace. It's meant to be extended by other
76+
* tsconfig files in the workspace to reuse them.
77+
* - `tsconfig.json`: TS solution config file that references all other projects
78+
* in the repo. It shouldn't include any file and it's not meant to be
79+
* extended or define any common compiler options.
80+
*/
4481
function isWorkspaceSetupWithTsSolution(tree: Tree): boolean {
4582
if (!tree.exists('tsconfig.base.json') || !tree.exists('tsconfig.json')) {
4683
return false;
@@ -52,20 +89,27 @@ function isWorkspaceSetupWithTsSolution(tree: Tree): boolean {
5289
}
5390

5491
/**
55-
* New setup:
56-
* - `files` is defined and set to an empty array
57-
* - `references` is defined and set to an empty array
58-
* - `include` is not defined or is set to an empty array
92+
* TS solution setup requires:
93+
* - One of `files` or `include` defined
94+
* - If set, they must be empty arrays
95+
*
96+
* Note: while the TS solution setup uses TS project references, in the initial
97+
* state of the workspace, where there are no projects, `references` is not
98+
* required to be defined.
5999
*/
60100
if (
61-
!tsconfigJson.files ||
62-
tsconfigJson.files.length > 0 ||
63-
!tsconfigJson.references ||
64-
!!tsconfigJson.include?.length
101+
(!tsconfigJson.files && !tsconfigJson.include) ||
102+
tsconfigJson.files?.length > 0 ||
103+
tsconfigJson.include?.length > 0
65104
) {
66105
return false;
67106
}
68107

108+
/**
109+
* TS solution setup requires:
110+
* - `compilerOptions.composite`: true
111+
* - `compilerOptions.declaration`: true or not set (default to true)
112+
*/
69113
const baseTsconfigJson = readJson(tree, 'tsconfig.base.json');
70114
if (
71115
!baseTsconfigJson.compilerOptions ||
@@ -75,11 +119,6 @@ function isWorkspaceSetupWithTsSolution(tree: Tree): boolean {
75119
return false;
76120
}
77121

78-
const { compilerOptions, ...rest } = baseTsconfigJson;
79-
if (Object.keys(rest).length > 0) {
80-
return false;
81-
}
82-
83122
return true;
84123
}
85124

packages/nest/src/generators/library/lib/normalize-options.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
77
import type { LibraryGeneratorSchema as JsLibraryGeneratorSchema } from '@nx/js/src/generators/library/schema';
88
import type { LibraryGeneratorOptions, NormalizedOptions } from '../schema';
9-
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
9+
import {
10+
isUsingTsSolutionSetup,
11+
shouldConfigureTsSolutionSetup,
12+
} from '@nx/js/src/utils/typescript/ts-solution-setup';
1013

1114
export async function normalizeOptions(
1215
tree: Tree,
@@ -38,7 +41,12 @@ export async function normalizeOptions(
3841
? options.tags.split(',').map((s) => s.trim())
3942
: [];
4043

41-
const isUsingTsSolutionsConfig = isUsingTsSolutionSetup(tree);
44+
// this helper is called before the jsLibraryGenerator is called, so, if the
45+
// TS solution setup is not configured, we additionally check if the TS
46+
// solution setup will be configured by the jsLibraryGenerator
47+
const isUsingTsSolutionsConfig =
48+
isUsingTsSolutionSetup(tree) ||
49+
shouldConfigureTsSolutionSetup(tree, addPlugin);
4250
const normalized: NormalizedOptions = {
4351
...options,
4452
strict: options.strict ?? true,

packages/next/src/generators/application/application.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { tsLibVersion } from '../../utils/versions';
3131
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
3232
import {
3333
addProjectToTsSolutionWorkspace,
34+
shouldConfigureTsSolutionSetup,
3435
updateTsconfigFiles,
3536
} from '@nx/js/src/utils/typescript/ts-solution-setup';
3637
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
@@ -47,20 +48,25 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
4748

4849
export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
4950
const tasks: GeneratorCallback[] = [];
50-
const options = await normalizeOptions(host, schema);
51-
52-
showPossibleWarnings(host, options);
5351

52+
const addTsPlugin = shouldConfigureTsSolutionSetup(
53+
host,
54+
schema.addPlugin,
55+
schema.useTsSolution
56+
);
5457
const jsInitTask = await jsInitGenerator(host, {
55-
js: options.js,
56-
skipPackageJson: options.skipPackageJson,
58+
js: schema.js,
59+
skipPackageJson: schema.skipPackageJson,
5760
skipFormat: true,
58-
addTsPlugin: options.isTsSolutionSetup,
59-
formatter: options.formatter,
61+
addTsPlugin,
62+
formatter: schema.formatter,
6063
platform: 'web',
6164
});
6265
tasks.push(jsInitTask);
6366

67+
const options = await normalizeOptions(host, schema);
68+
showPossibleWarnings(host, options);
69+
6470
const nextTask = await nextInitGenerator(host, {
6571
...options,
6672
skipFormat: true,

0 commit comments

Comments
 (0)