Skip to content

Commit 8b2393e

Browse files
leosvelperezFrozenPandaz
authored andcommitted
fix(js): keep refs to ignored files and allow opting out of pruning stale refs in typescript sync generator (#27636)
(cherry picked from commit 2a3307c)
1 parent 5063a4c commit 8b2393e

File tree

2 files changed

+135
-13
lines changed

2 files changed

+135
-13
lines changed

packages/js/src/generators/typescript-sync/typescript-sync.spec.ts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ describe('syncGenerator()', () => {
368368
references: [
369369
{ path: './some/thing' },
370370
{ path: './another/one' },
371-
{ path: '../packages/c' }, // this is not a dependency, should be pruned
371+
{ path: '../c' }, // this is not a dependency, should be pruned
372372
],
373373
});
374374

@@ -391,6 +391,86 @@ describe('syncGenerator()', () => {
391391
`);
392392
});
393393

394+
it('should not prune existing external project references that are not dependencies but are git ignored', async () => {
395+
writeJson(tree, 'packages/b/tsconfig.json', {
396+
compilerOptions: {
397+
composite: true,
398+
},
399+
references: [
400+
{ path: './some/thing' },
401+
{ path: './another/one' },
402+
{ path: '../../some-path/dir' }, // this is not a dependency but it's git ignored, should not be pruned
403+
{ path: '../c' }, // this is not a dependency and it's not git ignored, should be pruned
404+
],
405+
});
406+
tree.write('some-path/dir/tsconfig.json', '{}');
407+
tree.write('.gitignore', 'some-path/dir');
408+
409+
await syncGenerator(tree);
410+
411+
const rootTsconfig = readJson(tree, 'packages/b/tsconfig.json');
412+
// The dependency reference on "a" is added to the start of the array
413+
expect(rootTsconfig.references).toMatchInlineSnapshot(`
414+
[
415+
{
416+
"path": "../a",
417+
},
418+
{
419+
"path": "./some/thing",
420+
},
421+
{
422+
"path": "./another/one",
423+
},
424+
{
425+
"path": "../../some-path/dir",
426+
},
427+
]
428+
`);
429+
});
430+
431+
it('should not prune stale project references from projects included in `nx.sync.ignoredReferences`', async () => {
432+
writeJson(tree, 'packages/b/tsconfig.json', {
433+
compilerOptions: {
434+
composite: true,
435+
},
436+
references: [
437+
{ path: './some/thing' },
438+
{ path: './another/one' },
439+
// this is not a dependency and it's not git ignored, it would normally be pruned,
440+
// but it's included in `nx.sync.ignoredReferences`, so we don't prune it
441+
{ path: '../c' },
442+
],
443+
nx: {
444+
sync: {
445+
ignoredReferences: ['../c'],
446+
},
447+
},
448+
});
449+
tree.write('some-path/dir/tsconfig.json', '{}');
450+
tree.write('.gitignore', 'some-path/dir');
451+
452+
await syncGenerator(tree);
453+
454+
const rootTsconfig = readJson(tree, 'packages/b/tsconfig.json');
455+
// The dependency reference on "a" is added to the start of the array
456+
expect(rootTsconfig.references).toMatchInlineSnapshot(`
457+
[
458+
{
459+
"path": "../a",
460+
},
461+
{
462+
"path": "./some/thing",
463+
},
464+
{
465+
"path": "./another/one",
466+
},
467+
{
468+
"path": "../c",
469+
},
470+
]
471+
`);
472+
});
473+
394474
it('should collect transitive dependencies and sync project references to tsconfig.json files', async () => {
395475
// c => b => a
396476
// d => b => a

packages/js/src/generators/typescript-sync/typescript-sync.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
type ProjectGraphProjectNode,
1111
type Tree,
1212
} from '@nx/devkit';
13+
import ignore from 'ignore';
1314
import { applyEdits, modify } from 'jsonc-parser';
1415
import { dirname, normalize, relative } from 'node:path/posix';
1516
import type { SyncGeneratorResult } from 'nx/src/utils/sync-generators';
@@ -26,6 +27,11 @@ interface Tsconfig {
2627
rootDir?: string;
2728
outDir?: string;
2829
};
30+
nx?: {
31+
sync?: {
32+
ignoredReferences?: string[];
33+
};
34+
};
2935
}
3036

3137
const COMMON_RUNTIME_TS_CONFIG_FILE_NAMES = [
@@ -37,6 +43,12 @@ const COMMON_RUNTIME_TS_CONFIG_FILE_NAMES = [
3743
'tsconfig.runtime.json',
3844
];
3945

46+
type GeneratorOptions = {
47+
runtimeTsConfigFileNames?: string[];
48+
};
49+
50+
type NormalizedGeneratorOptions = Required<GeneratorOptions>;
51+
4052
export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
4153
// Ensure that the plugin has been wired up in nx.json
4254
const nxJson = readNxJson(tree);
@@ -151,23 +163,27 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
151163
}
152164
}
153165

154-
const runtimeTsConfigFileNames =
155-
(nxJson.sync?.generatorOptions?.['@nx/js:typescript-sync']
156-
?.runtimeTsConfigFileNames as string[]) ??
157-
COMMON_RUNTIME_TS_CONFIG_FILE_NAMES;
166+
const userOptions = nxJson.sync?.generatorOptions?.[
167+
'@nx/js:typescript-sync'
168+
] as GeneratorOptions | undefined;
169+
const { runtimeTsConfigFileNames }: NormalizedGeneratorOptions = {
170+
runtimeTsConfigFileNames:
171+
userOptions?.runtimeTsConfigFileNames ??
172+
COMMON_RUNTIME_TS_CONFIG_FILE_NAMES,
173+
};
158174

159175
const collectedDependencies = new Map<string, ProjectGraphProjectNode[]>();
160-
for (const [name, data] of Object.entries(projectGraph.dependencies)) {
176+
for (const [projectName, data] of Object.entries(projectGraph.dependencies)) {
161177
if (
162-
!projectGraph.nodes[name] ||
163-
projectGraph.nodes[name].data.root === '.' ||
178+
!projectGraph.nodes[projectName] ||
179+
projectGraph.nodes[projectName].data.root === '.' ||
164180
!data.length
165181
) {
166182
continue;
167183
}
168184

169185
// Get the source project nodes for the source and target
170-
const sourceProjectNode = projectGraph.nodes[name];
186+
const sourceProjectNode = projectGraph.nodes[projectName];
171187

172188
// Find the relevant tsconfig file for the source project
173189
const sourceProjectTsconfigPath = joinPathFragments(
@@ -179,7 +195,7 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
179195
) {
180196
if (process.env.NX_VERBOSE_LOGGING === 'true') {
181197
logger.warn(
182-
`Skipping project "${name}" as there is no tsconfig.json file found in the project root "${sourceProjectNode.data.root}".`
198+
`Skipping project "${projectName}" as there is no tsconfig.json file found in the project root "${sourceProjectNode.data.root}".`
183199
);
184200
}
185201
continue;
@@ -188,7 +204,7 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
188204
// Collect the dependencies of the source project
189205
const dependencies = collectProjectDependencies(
190206
tree,
191-
name,
207+
projectName,
192208
projectGraph,
193209
collectedDependencies
194210
);
@@ -299,14 +315,23 @@ function updateTsConfigReferences(
299315
tsConfigPath
300316
);
301317
const tsConfig = parseJson<Tsconfig>(stringifiedJsonContents);
318+
const ignoredReferences = new Set(tsConfig.nx?.sync?.ignoredReferences ?? []);
302319

303320
// We have at least one dependency so we can safely set it to an empty array if not already set
304321
const references = [];
305322
const originalReferencesSet = new Set();
306323
const newReferencesSet = new Set();
324+
307325
for (const ref of tsConfig.references ?? []) {
308326
const normalizedPath = normalizeReferencePath(ref.path);
309327
originalReferencesSet.add(normalizedPath);
328+
if (ignoredReferences.has(ref.path)) {
329+
// we keep the user-defined ignored references
330+
references.push(ref);
331+
newReferencesSet.add(normalizedPath);
332+
continue;
333+
}
334+
310335
// reference path is relative to the tsconfig file
311336
const resolvedRefPath = getTsConfigPathFromReferencePath(
312337
tree,
@@ -320,9 +345,10 @@ function updateTsConfigReferences(
320345
resolvedRefPath,
321346
projectRoot,
322347
projectRoots
323-
)
348+
) ||
349+
isProjectReferenceIgnored(tree, resolvedRefPath)
324350
) {
325-
// we keep all references within the current Nx project
351+
// we keep all references within the current Nx project or that are ignored
326352
references.push(ref);
327353
newReferencesSet.add(normalizedPath);
328354
}
@@ -511,6 +537,22 @@ function isProjectReferenceWithinNxProject(
511537
return true;
512538
}
513539

540+
function isProjectReferenceIgnored(
541+
tree: Tree,
542+
refTsConfigPath: string
543+
): boolean {
544+
const ig = ignore();
545+
if (tree.exists('.gitignore')) {
546+
ig.add('.git');
547+
ig.add(tree.read('.gitignore', 'utf-8'));
548+
}
549+
if (tree.exists('.nxignore')) {
550+
ig.add(tree.read('.nxignore', 'utf-8'));
551+
}
552+
553+
return ig.ignores(refTsConfigPath);
554+
}
555+
514556
function getTsConfigDirName(
515557
tree: Tree,
516558
rawTsconfigContentsCache: Map<string, string>,

0 commit comments

Comments
 (0)