Skip to content

Commit

Permalink
fix(core): Update move/remove workspace generators to work with ts pr…
Browse files Browse the repository at this point in the history
…oject references (#29331)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
When a workspace is setup to use ts project references the move/remove
generators from `@nx/workspace` do not work correctly or result in an
incorrect state for the workspace.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
This should work OOTB.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
  • Loading branch information
ndcunningham authored Dec 17, 2024
1 parent 0888977 commit 0329cad
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 30 deletions.
85 changes: 85 additions & 0 deletions packages/workspace/src/generators/move/lib/update-imports.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Schema } from '../schema';
import { normalizeSchema } from './normalize-schema';
import { updateImports } from './update-imports';
import * as tsSolution from '../../../utilities/typescript/ts-solution-setup';

// nx-ignore-next-line
const { libraryGenerator } = require('@nx/js');
Expand Down Expand Up @@ -530,4 +531,88 @@ export MyExtendedClass extends MyClass {};`
'@proj/my-source/server': ['my-destination/src/server.ts'],
});
});

describe('TypeScript project references', () => {
beforeEach(() => {
jest.spyOn(tsSolution, 'isUsingTsSolutionSetup').mockReturnValue(true);
const tsconfigContent = {
extends: './tsconfig.base.json',
...readJson(tree, 'tsconfig.base.json'),
};
tree.write('tsconfig.json', JSON.stringify(tsconfigContent, null, 2));

const packageJson = readJson(tree, 'package.json');
packageJson.workspaces = ['packages/**'];
tree.write('package.json', JSON.stringify(packageJson, null, 2));
});
it('should work with updateImportPath=false', async () => {
await libraryGenerator(tree, {
directory: 'packages/my-source',
});

const projectConfig = readProjectConfiguration(tree, 'my-source');

const tsconfigJson = readJson(tree, 'tsconfig.json');
tsconfigJson.references = [{ path: './packages/my-source' }];
tree.write('tsconfig.json', JSON.stringify(tsconfigJson, null, 2));

updateImports(
tree,
await normalizeSchema(
tree,
{
...schema,
updateImportPath: false,
},
projectConfig
),

projectConfig
);

expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(`
[
{
"path": "./packages/my-source",
},
{
"path": "./my-destination",
},
]
`);
});

it('should work with updateImportPath=true', async () => {
await libraryGenerator(tree, {
directory: 'packages/my-source',
});

const projectConfig = readProjectConfiguration(tree, 'my-source');

const tsconfigJson = readJson(tree, 'tsconfig.json');
tsconfigJson.references = [{ path: './packages/my-source' }];
tree.write('tsconfig.json', JSON.stringify(tsconfigJson, null, 2));

updateImports(
tree,
await normalizeSchema(
tree,
{
...schema,
},
projectConfig
),

projectConfig
);

expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(`
[
{
"path": "./my-destination",
},
]
`);
});
});
});
103 changes: 82 additions & 21 deletions packages/workspace/src/generators/move/lib/update-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
visitNotIgnoredFiles,
writeJson,
} from '@nx/devkit';
import { relative } from 'path';
import { isAbsolute, normalize, relative } from 'path';
import type * as ts from 'typescript';
import { getImportPath } from '../../../utilities/get-import-path';
import {
Expand All @@ -21,6 +21,7 @@ import {
import { ensureTypescript } from '../../../utilities/typescript';
import { NormalizedSchema } from '../schema';
import { normalizePathSlashes } from './utils';
import { isUsingTsSolutionSetup } from '../../../utilities/typescript/ts-solution-setup';

let tsModule: typeof import('typescript');

Expand All @@ -41,9 +42,14 @@ export function updateImports(
const { libsDir } = getWorkspaceLayout(tree);
const projects = getProjects(tree);

const isUsingTsSolution = isUsingTsSolutionSetup(tree);

// use the source root to find the from location
// this attempts to account for libs that have been created with --importPath
const tsConfigPath = getRootTsConfigPathInTree(tree);
const tsConfigPath = isUsingTsSolution
? 'tsconfig.json'
: getRootTsConfigPathInTree(tree);
// If we are using a ts solution setup, we need to use tsconfig.json instead of tsconfig.base.json
let tsConfig: any;
let mainEntryPointImportPath: string;
let secondaryEntryPointImportPaths: string[];
Expand Down Expand Up @@ -149,30 +155,85 @@ export function updateImports(
};

if (tsConfig) {
const path = tsConfig.compilerOptions.paths[projectRef.from] as string[];
if (!path) {
throw new Error(
[
`unable to find "${projectRef.from}" in`,
`${tsConfigPath} compilerOptions.paths`,
].join(' ')
if (!isUsingTsSolution) {
updateTsConfigPaths(
tsConfig,
projectRef,
tsConfigPath,
projectRoot,
schema
);
}
const updatedPath = path.map((x) =>
joinPathFragments(projectRoot.to, relative(projectRoot.from, x))
);

if (schema.updateImportPath && projectRef.to) {
tsConfig.compilerOptions.paths[projectRef.to] = updatedPath;
if (projectRef.from !== projectRef.to) {
delete tsConfig.compilerOptions.paths[projectRef.from];
}
} else {
tsConfig.compilerOptions.paths[projectRef.from] = updatedPath;
updateTsConfigReferences(tsConfig, projectRoot, tsConfigPath, schema);
}
writeJson(tree, tsConfigPath, tsConfig);
}
}
}

function updateTsConfigReferences(
tsConfig: any,
projectRoot: { from: string; to: string },
tsConfigPath: string,
schema: NormalizedSchema
) {
// Since paths can be './path' or 'path' we check if both are the same relative path to the workspace root
const projectRefIndex = tsConfig.references.findIndex(
(ref) => relative(ref.path, projectRoot.from) === ''
);
if (projectRefIndex === -1) {
throw new Error(
`unable to find "${projectRoot.from}" in ${tsConfigPath} references`
);
}
const updatedPath = joinPathFragments(
projectRoot.to,
relative(projectRoot.from, tsConfig.references[projectRefIndex].path)
);

let normalizedPath = normalize(updatedPath);
if (
!normalizedPath.startsWith('.') &&
!normalizedPath.startsWith('../') &&
!isAbsolute(normalizedPath)
) {
normalizedPath = `./${normalizedPath}`;
}

writeJson(tree, tsConfigPath, tsConfig);
if (schema.updateImportPath && projectRoot.to) {
tsConfig.references[projectRefIndex].path = normalizedPath;
} else {
tsConfig.references.push({ path: normalizedPath });
}
}

function updateTsConfigPaths(
tsConfig: any,
projectRef: { from: string; to: string },
tsConfigPath: string,
projectRoot: { from: string; to: string },
schema: NormalizedSchema
) {
const path = tsConfig.compilerOptions.paths[projectRef.from] as string[];
if (!path) {
throw new Error(
[
`unable to find "${projectRef.from}" in`,
`${tsConfigPath} compilerOptions.paths`,
].join(' ')
);
}
const updatedPath = path.map((x) =>
joinPathFragments(projectRoot.to, relative(projectRoot.from, x))
);

if (schema.updateImportPath && projectRef.to) {
tsConfig.compilerOptions.paths[projectRef.to] = updatedPath;
if (projectRef.from !== projectRef.to) {
delete tsConfig.compilerOptions.paths[projectRef.from];
}
} else {
tsConfig.compilerOptions.paths[projectRef.from] = updatedPath;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Schema } from '../schema';
import { updateTsconfig } from './update-tsconfig';
import * as tsSolution from '../../../utilities/typescript/ts-solution-setup';

// nx-ignore-next-line
const { libraryGenerator } = require('@nx/js');
Expand Down Expand Up @@ -212,4 +213,44 @@ describe('updateTsconfig', () => {
'@proj/nested/whatever-name': ['libs/my-lib/nested-lib/src/index.ts'],
});
});

it('should work with tsSolution setup', async () => {
jest.spyOn(tsSolution, 'isUsingTsSolutionSetup').mockReturnValue(true);

await libraryGenerator(tree, {
directory: 'my-lib',
});

const tsconfigContent = {
extends: './tsconfig.base.json',
compilerOptions: {},
files: [],
include: [],
references: [
{
path: './my-lib',
},
],
};

tree.write('tsconfig.json', JSON.stringify(tsconfigContent, null, 2));

graph = {
nodes: {
'my-lib': {
name: 'my-lib',
type: 'lib',
data: {
root: readProjectConfiguration(tree, 'my-lib').root,
} as any,
},
},
dependencies: {},
};

await updateTsconfig(tree, schema);

const tsConfig = readJson(tree, 'tsconfig.json');
expect(tsConfig.references).toEqual([]);
});
});
37 changes: 28 additions & 9 deletions packages/workspace/src/generators/remove/lib/update-tsconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
createProjectGraphAsync,
normalizePath,
ProjectGraph,
readProjectsConfigurationFromProjectGraph,
Tree,
updateJson,
} from '@nx/devkit';
Expand All @@ -11,28 +12,46 @@ import {
createProjectRootMappings,
findProjectForPath,
} from 'nx/src/project-graph/utils/find-project-for-path';
import { isUsingTsSolutionSetup } from '../../../utilities/typescript/ts-solution-setup';
import { relative } from 'path';

/**
* Updates the tsconfig paths to remove the project.
*
* @param schema The options provided to the schematic
*/
export async function updateTsconfig(tree: Tree, schema: Schema) {
const tsConfigPath = getRootTsConfigPathInTree(tree);
const isUsingTsSolution = isUsingTsSolutionSetup(tree);
const tsConfigPath = isUsingTsSolution
? 'tsconfig.json'
: getRootTsConfigPathInTree(tree);

if (tree.exists(tsConfigPath)) {
const graph: ProjectGraph = await createProjectGraphAsync();
const projectMapping = createProjectRootMappings(graph.nodes);
updateJson(tree, tsConfigPath, (json) => {
for (const importPath in json.compilerOptions.paths) {
for (const path of json.compilerOptions.paths[importPath]) {
const project = findProjectForPath(
normalizePath(path),
projectMapping
if (isUsingTsSolution) {
const projectConfigs = readProjectsConfigurationFromProjectGraph(graph);
const project = projectConfigs.projects[schema.projectName];
if (!project) {
throw new Error(
`Could not find project '${schema.project}'. Please choose a project that exists in the Nx Workspace.`
);
if (project === schema.projectName) {
delete json.compilerOptions.paths[importPath];
break;
}
json.references = json.references.filter(
(ref) => relative(ref.path, project.root) !== ''
);
} else {
for (const importPath in json.compilerOptions.paths) {
for (const path of json.compilerOptions.paths[importPath]) {
const project = findProjectForPath(
normalizePath(path),
projectMapping
);
if (project === schema.projectName) {
delete json.compilerOptions.paths[importPath];
break;
}
}
}
}
Expand Down
Loading

0 comments on commit 0329cad

Please sign in to comment.