Skip to content

Commit

Permalink
fix(js): keep refs to ignored files and allow opting out of pruning s…
Browse files Browse the repository at this point in the history
…tale refs in typescript sync generator (nrwl#27636)
  • Loading branch information
leosvelperez authored Sep 10, 2024
1 parent 4986b88 commit 2a3307c
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ describe('syncGenerator()', () => {
references: [
{ path: './some/thing' },
{ path: './another/one' },
{ path: '../packages/c' }, // this is not a dependency, should be pruned
{ path: '../c' }, // this is not a dependency, should be pruned
],
});

Expand All @@ -391,6 +391,86 @@ describe('syncGenerator()', () => {
`);
});

it('should not prune existing external project references that are not dependencies but are git ignored', async () => {
writeJson(tree, 'packages/b/tsconfig.json', {
compilerOptions: {
composite: true,
},
references: [
{ path: './some/thing' },
{ path: './another/one' },
{ path: '../../some-path/dir' }, // this is not a dependency but it's git ignored, should not be pruned
{ path: '../c' }, // this is not a dependency and it's not git ignored, should be pruned
],
});
tree.write('some-path/dir/tsconfig.json', '{}');
tree.write('.gitignore', 'some-path/dir');

await syncGenerator(tree);

const rootTsconfig = readJson(tree, 'packages/b/tsconfig.json');
// The dependency reference on "a" is added to the start of the array
expect(rootTsconfig.references).toMatchInlineSnapshot(`
[
{
"path": "../a",
},
{
"path": "./some/thing",
},
{
"path": "./another/one",
},
{
"path": "../../some-path/dir",
},
]
`);
});

it('should not prune stale project references from projects included in `nx.sync.ignoredReferences`', async () => {
writeJson(tree, 'packages/b/tsconfig.json', {
compilerOptions: {
composite: true,
},
references: [
{ path: './some/thing' },
{ path: './another/one' },
// this is not a dependency and it's not git ignored, it would normally be pruned,
// but it's included in `nx.sync.ignoredReferences`, so we don't prune it
{ path: '../c' },
],
nx: {
sync: {
ignoredReferences: ['../c'],
},
},
});
tree.write('some-path/dir/tsconfig.json', '{}');
tree.write('.gitignore', 'some-path/dir');

await syncGenerator(tree);

const rootTsconfig = readJson(tree, 'packages/b/tsconfig.json');
// The dependency reference on "a" is added to the start of the array
expect(rootTsconfig.references).toMatchInlineSnapshot(`
[
{
"path": "../a",
},
{
"path": "./some/thing",
},
{
"path": "./another/one",
},
{
"path": "../c",
},
]
`);
});

it('should collect transitive dependencies and sync project references to tsconfig.json files', async () => {
// c => b => a
// d => b => a
Expand Down
66 changes: 54 additions & 12 deletions packages/js/src/generators/typescript-sync/typescript-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type ProjectGraphProjectNode,
type Tree,
} from '@nx/devkit';
import ignore from 'ignore';
import { applyEdits, modify } from 'jsonc-parser';
import { dirname, normalize, relative } from 'node:path/posix';
import type { SyncGeneratorResult } from 'nx/src/utils/sync-generators';
Expand All @@ -26,6 +27,11 @@ interface Tsconfig {
rootDir?: string;
outDir?: string;
};
nx?: {
sync?: {
ignoredReferences?: string[];
};
};
}

const COMMON_RUNTIME_TS_CONFIG_FILE_NAMES = [
Expand All @@ -37,6 +43,12 @@ const COMMON_RUNTIME_TS_CONFIG_FILE_NAMES = [
'tsconfig.runtime.json',
];

type GeneratorOptions = {
runtimeTsConfigFileNames?: string[];
};

type NormalizedGeneratorOptions = Required<GeneratorOptions>;

export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
// Ensure that the plugin has been wired up in nx.json
const nxJson = readNxJson(tree);
Expand Down Expand Up @@ -151,23 +163,27 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
}
}

const runtimeTsConfigFileNames =
(nxJson.sync?.generatorOptions?.['@nx/js:typescript-sync']
?.runtimeTsConfigFileNames as string[]) ??
COMMON_RUNTIME_TS_CONFIG_FILE_NAMES;
const userOptions = nxJson.sync?.generatorOptions?.[
'@nx/js:typescript-sync'
] as GeneratorOptions | undefined;
const { runtimeTsConfigFileNames }: NormalizedGeneratorOptions = {
runtimeTsConfigFileNames:
userOptions?.runtimeTsConfigFileNames ??
COMMON_RUNTIME_TS_CONFIG_FILE_NAMES,
};

const collectedDependencies = new Map<string, ProjectGraphProjectNode[]>();
for (const [name, data] of Object.entries(projectGraph.dependencies)) {
for (const [projectName, data] of Object.entries(projectGraph.dependencies)) {
if (
!projectGraph.nodes[name] ||
projectGraph.nodes[name].data.root === '.' ||
!projectGraph.nodes[projectName] ||
projectGraph.nodes[projectName].data.root === '.' ||
!data.length
) {
continue;
}

// Get the source project nodes for the source and target
const sourceProjectNode = projectGraph.nodes[name];
const sourceProjectNode = projectGraph.nodes[projectName];

// Find the relevant tsconfig file for the source project
const sourceProjectTsconfigPath = joinPathFragments(
Expand All @@ -179,7 +195,7 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
) {
if (process.env.NX_VERBOSE_LOGGING === 'true') {
logger.warn(
`Skipping project "${name}" as there is no tsconfig.json file found in the project root "${sourceProjectNode.data.root}".`
`Skipping project "${projectName}" as there is no tsconfig.json file found in the project root "${sourceProjectNode.data.root}".`
);
}
continue;
Expand All @@ -188,7 +204,7 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
// Collect the dependencies of the source project
const dependencies = collectProjectDependencies(
tree,
name,
projectName,
projectGraph,
collectedDependencies
);
Expand Down Expand Up @@ -299,14 +315,23 @@ function updateTsConfigReferences(
tsConfigPath
);
const tsConfig = parseJson<Tsconfig>(stringifiedJsonContents);
const ignoredReferences = new Set(tsConfig.nx?.sync?.ignoredReferences ?? []);

// We have at least one dependency so we can safely set it to an empty array if not already set
const references = [];
const originalReferencesSet = new Set();
const newReferencesSet = new Set();

for (const ref of tsConfig.references ?? []) {
const normalizedPath = normalizeReferencePath(ref.path);
originalReferencesSet.add(normalizedPath);
if (ignoredReferences.has(ref.path)) {
// we keep the user-defined ignored references
references.push(ref);
newReferencesSet.add(normalizedPath);
continue;
}

// reference path is relative to the tsconfig file
const resolvedRefPath = getTsConfigPathFromReferencePath(
tree,
Expand All @@ -320,9 +345,10 @@ function updateTsConfigReferences(
resolvedRefPath,
projectRoot,
projectRoots
)
) ||
isProjectReferenceIgnored(tree, resolvedRefPath)
) {
// we keep all references within the current Nx project
// we keep all references within the current Nx project or that are ignored
references.push(ref);
newReferencesSet.add(normalizedPath);
}
Expand Down Expand Up @@ -511,6 +537,22 @@ function isProjectReferenceWithinNxProject(
return true;
}

function isProjectReferenceIgnored(
tree: Tree,
refTsConfigPath: string
): boolean {
const ig = ignore();
if (tree.exists('.gitignore')) {
ig.add('.git');
ig.add(tree.read('.gitignore', 'utf-8'));
}
if (tree.exists('.nxignore')) {
ig.add(tree.read('.nxignore', 'utf-8'));
}

return ig.ignores(refTsConfigPath);
}

function getTsConfigDirName(
tree: Tree,
rawTsconfigContentsCache: Map<string, string>,
Expand Down

0 comments on commit 2a3307c

Please sign in to comment.