chore(core): reconcile functions to find project of a path (#13364)

This commit is contained in:
Jason Jean 2022-11-24 11:02:32 -05:00 committed by GitHub
parent 0e54262ca2
commit 6d35fd4d85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 374 additions and 413 deletions

View File

@ -21,8 +21,7 @@ import {
stripIndents, stripIndents,
workspaceRoot, workspaceRoot,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { createProjectFileMappings } from 'nx/src/utils/target-project-locator'; import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs';
import { lstatSync, mkdirSync, writeFileSync } from 'fs';
import { dirname, join, relative } from 'path'; import { dirname, join, relative } from 'path';
import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/webpack-browser.impl'; import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/webpack-browser.impl';
@ -279,16 +278,22 @@ function withSchemaDefaults(options: any): BrowserBuilderSchema {
* this file should get cleaned up via the cypress executor * this file should get cleaned up via the cypress executor
*/ */
function getTempStylesForTailwind(ctExecutorContext: ExecutorContext) { function getTempStylesForTailwind(ctExecutorContext: ExecutorContext) {
const mappedGraphFiles = createProjectFileMappings(
ctExecutorContext.projectGraph.nodes
);
const ctProjectConfig = ctExecutorContext.projectGraph.nodes[ const ctProjectConfig = ctExecutorContext.projectGraph.nodes[
ctExecutorContext.projectName ctExecutorContext.projectName
].data as ProjectConfiguration; ].data as ProjectConfiguration;
// angular only supports `tailwind.config.{js,cjs}` // angular only supports `tailwind.config.{js,cjs}`
const ctProjectTailwindConfig = join(ctProjectConfig.root, 'tailwind.config'); const ctProjectTailwindConfig = join(
const isTailWindInCtProject = !!mappedGraphFiles[ctProjectTailwindConfig]; ctExecutorContext.root,
const isTailWindInRoot = !!mappedGraphFiles['tailwind.config']; ctProjectConfig.root,
'tailwind.config'
);
const isTailWindInCtProject =
existsSync(ctProjectTailwindConfig + '.js') ||
existsSync(ctProjectTailwindConfig + '.cjs');
const rootTailwindPath = join(ctExecutorContext.root, 'tailwind.config');
const isTailWindInRoot =
existsSync(rootTailwindPath + '.js') ||
existsSync(rootTailwindPath + '.cjs');
if (isTailWindInRoot || isTailWindInCtProject) { if (isTailWindInRoot || isTailWindInCtProject) {
const pathToStyle = getTempTailwindPath(ctExecutorContext); const pathToStyle = getTempTailwindPath(ctExecutorContext);

View File

@ -1,27 +1,25 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { import {
joinPathFragments, joinPathFragments,
readCachedProjectGraph,
readProjectConfiguration, readProjectConfiguration,
readWorkspaceConfiguration, readWorkspaceConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import type { NormalizedSchema, Schema } from '../schema'; import type { NormalizedSchema, Schema } from '../schema';
import { getProjectNameFromDirPath } from 'nx/src/utils/project-graph-utils'; import {
createProjectRootMappings,
function getProjectFromPath(path: string) { findProjectForPath,
try { } from 'nx/src/project-graph/utils/find-project-for-path';
return getProjectNameFromDirPath(path);
} catch {
return null;
}
}
export function normalizeOptions( export function normalizeOptions(
tree: Tree, tree: Tree,
options: Schema options: Schema
): NormalizedSchema { ): NormalizedSchema {
const projectGraph = readCachedProjectGraph();
const projectRootMappings = createProjectRootMappings(projectGraph.nodes);
const project = const project =
options.project ?? options.project ??
getProjectFromPath(options.path) ?? findProjectForPath(options.path, projectRootMappings) ??
readWorkspaceConfiguration(tree).defaultProject; readWorkspaceConfiguration(tree).defaultProject;
const { projectType, root, sourceRoot } = readProjectConfiguration( const { projectType, root, sourceRoot } = readProjectConfiguration(
tree, tree,

View File

@ -1,7 +1,6 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { getSourceNodes } from '@nrwl/workspace/src/utilities/typescript';
import { findNodes } from 'nx/src/utils/typescript'; import { findNodes } from 'nx/src/utils/typescript';
import { getSourceNodes } from '@nrwl/workspace/src/utilities/typescript';
import type { PropertyDeclaration } from 'typescript'; import type { PropertyDeclaration } from 'typescript';
import { SyntaxKind } from 'typescript'; import { SyntaxKind } from 'typescript';
import { getTsSourceFile } from '../../../utils/nx-devkit/ast-utils'; import { getTsSourceFile } from '../../../utils/nx-devkit/ast-utils';

View File

@ -9,9 +9,12 @@ import {
workspaceRoot, workspaceRoot,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph'; import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
import { createProjectFileMappings } from 'nx/src/utils/target-project-locator';
import { dirname, extname, join, relative } from 'path'; import { dirname, extname, join, relative } from 'path';
import { lstatSync } from 'fs'; import { lstatSync } from 'fs';
import {
createProjectRootMappings,
findProjectForPath,
} from 'nx/src/project-graph/utils/find-project-for-path';
interface BaseCypressPreset { interface BaseCypressPreset {
videosFolder: string; videosFolder: string;
@ -90,9 +93,11 @@ export function getProjectConfigByPath(
: configFileFromWorkspaceRoot : configFileFromWorkspaceRoot
); );
const mappedGraphFiles = createProjectFileMappings(graph.nodes); const projectRootMappings = createProjectRootMappings(graph.nodes);
const componentTestingProjectName = const componentTestingProjectName = findProjectForPath(
mappedGraphFiles[normalizedPathFromWorkspaceRoot]; normalizedPathFromWorkspaceRoot,
projectRootMappings
);
if ( if (
!componentTestingProjectName || !componentTestingProjectName ||
!graph.nodes[componentTestingProjectName]?.data !graph.nodes[componentTestingProjectName]?.data

View File

@ -3,6 +3,7 @@
"rules": { "rules": {
"no-restricted-imports": [ "no-restricted-imports": [
"error", "error",
"@nrwl/workspace",
"@angular-devkit/core", "@angular-devkit/core",
"@angular-devkit/architect", "@angular-devkit/architect",
"@angular-devkit/schematics" "@angular-devkit/schematics"

View File

@ -3,13 +3,11 @@ import { DependencyType } from '@nrwl/devkit';
import * as parser from '@typescript-eslint/parser'; import * as parser from '@typescript-eslint/parser';
import { TSESLint } from '@typescript-eslint/utils'; import { TSESLint } from '@typescript-eslint/utils';
import { vol } from 'memfs'; import { vol } from 'memfs';
import { import { TargetProjectLocator } from 'nx/src/utils/target-project-locator';
TargetProjectLocator,
createProjectFileMappings,
} from 'nx/src/utils/target-project-locator';
import enforceModuleBoundaries, { import enforceModuleBoundaries, {
RULE_NAME as enforceModuleBoundariesRuleName, RULE_NAME as enforceModuleBoundariesRuleName,
} from './enforce-module-boundaries'; } from '../../src/rules/enforce-module-boundaries';
import { createProjectRootMappings } from 'nx/src/project-graph/utils/find-project-for-path';
jest.mock('fs', () => require('memfs').fs); jest.mock('fs', () => require('memfs').fs);
@ -1015,7 +1013,7 @@ Violation detected in:
tags: [], tags: [],
implicitDependencies: [], implicitDependencies: [],
architect: {}, architect: {},
files: [createFile(`libs/other/src/index.ts`)], files: [createFile(`libs/other/index.ts`)],
}, },
}, },
}, },
@ -1033,7 +1031,6 @@ Violation detected in:
if (importKind === 'type') { if (importKind === 'type') {
expect(failures.length).toEqual(0); expect(failures.length).toEqual(0);
} else { } else {
expect(failures.length).toEqual(1);
expect(failures[0].message).toEqual( expect(failures[0].message).toEqual(
'Imports of lazy-loaded libraries are forbidden' 'Imports of lazy-loaded libraries are forbidden'
); );
@ -1145,10 +1142,7 @@ Violation detected in:
tags: [], tags: [],
implicitDependencies: [], implicitDependencies: [],
architect: {}, architect: {},
files: [ files: [createFile(`libs/mylib/src/main.ts`)],
createFile(`libs/mylib/src/main.ts`),
createFile(`libs/mylib/src/index.ts`),
],
}, },
}, },
anotherlibName: { anotherlibName: {
@ -1272,10 +1266,7 @@ Violation detected in:
tags: [], tags: [],
implicitDependencies: [], implicitDependencies: [],
architect: {}, architect: {},
files: [ files: [createFile(`libs/mylib/src/main.ts`, ['anotherlibName'])],
createFile(`libs/mylib/src/main.ts`, ['anotherlibName']),
createFile(`libs/mylib/src/index.ts`),
],
}, },
}, },
anotherlibName: { anotherlibName: {
@ -1370,7 +1361,6 @@ Circular file chain:
architect: {}, architect: {},
files: [ files: [
createFile(`libs/badcirclelib/src/main.ts`, ['anotherlibName']), createFile(`libs/badcirclelib/src/main.ts`, ['anotherlibName']),
createFile(`libs/badcirclelib/src/index.ts`),
], ],
}, },
}, },
@ -1894,7 +1884,7 @@ function runRule(
): TSESLint.Linter.LintMessage[] { ): TSESLint.Linter.LintMessage[] {
(global as any).projectPath = `${process.cwd()}/proj`; (global as any).projectPath = `${process.cwd()}/proj`;
(global as any).projectGraph = projectGraph; (global as any).projectGraph = projectGraph;
(global as any).projectGraphFileMappings = createProjectFileMappings( (global as any).projectRootMappings = createProjectRootMappings(
projectGraph.nodes projectGraph.nodes
); );
(global as any).targetProjectLocator = new TargetProjectLocator( (global as any).targetProjectLocator = new TargetProjectLocator(

View File

@ -15,9 +15,8 @@ import {
findConstraintsFor, findConstraintsFor,
findDependenciesWithTags, findDependenciesWithTags,
findProjectUsingImport, findProjectUsingImport,
findSourceProject, findProject,
findTransitiveExternalDependencies, findTransitiveExternalDependencies,
findTargetProject,
getSourceFilePath, getSourceFilePath,
getTargetProjectBasedOnRelativeImport, getTargetProjectBasedOnRelativeImport,
groupImports, groupImports,
@ -154,8 +153,7 @@ export default createESLintRule<Options, MessageIds>({
); );
const fileName = normalizePath(context.getFilename()); const fileName = normalizePath(context.getFilename());
const { projectGraph, projectGraphFileMappings } = const { projectGraph, projectRootMappings } = readProjectGraph(RULE_NAME);
readProjectGraph(RULE_NAME);
if (!projectGraph) { if (!projectGraph) {
return {}; return {};
@ -199,9 +197,9 @@ export default createESLintRule<Options, MessageIds>({
const sourceFilePath = getSourceFilePath(fileName, projectPath); const sourceFilePath = getSourceFilePath(fileName, projectPath);
const sourceProject = findSourceProject( const sourceProject = findProject(
projectGraph, projectGraph,
projectGraphFileMappings, projectRootMappings,
sourceFilePath sourceFilePath
); );
// If source is not part of an nx workspace, return. // If source is not part of an nx workspace, return.
@ -215,17 +213,13 @@ export default createESLintRule<Options, MessageIds>({
let targetProject: ProjectGraphProjectNode | ProjectGraphExternalNode; let targetProject: ProjectGraphProjectNode | ProjectGraphExternalNode;
if (isAbsoluteImportIntoAnotherProj) { if (isAbsoluteImportIntoAnotherProj) {
targetProject = findTargetProject( targetProject = findProject(projectGraph, projectRootMappings, imp);
projectGraph,
projectGraphFileMappings,
imp
);
} else { } else {
targetProject = getTargetProjectBasedOnRelativeImport( targetProject = getTargetProjectBasedOnRelativeImport(
imp, imp,
projectPath, projectPath,
projectGraph, projectGraph,
projectGraphFileMappings, projectRootMappings,
sourceFilePath sourceFilePath
); );
} }

View File

@ -6,10 +6,7 @@ import {
readJsonFile, readJsonFile,
workspaceRoot, workspaceRoot,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { import { findProject, getSourceFilePath } from '../utils/runtime-lint-utils';
findSourceProject,
getSourceFilePath,
} from '../utils/runtime-lint-utils';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { registerTsProject } from 'nx/src/utils/register'; import { registerTsProject } from 'nx/src/utils/register';
import * as path from 'path'; import * as path from 'path';
@ -87,17 +84,16 @@ export default createESLintRule<Options, MessageIds>({
return {}; return {};
} }
const { projectGraph, projectGraphFileMappings } = const { projectGraph, projectRootMappings } = readProjectGraph(RULE_NAME);
readProjectGraph(RULE_NAME);
const sourceFilePath = getSourceFilePath( const sourceFilePath = getSourceFilePath(
context.getFilename(), context.getFilename(),
workspaceRoot workspaceRoot
); );
const sourceProject = findSourceProject( const sourceProject = findProject(
projectGraph, projectGraph,
projectGraphFileMappings, projectRootMappings,
sourceFilePath sourceFilePath
); );
// If source is not part of an nx workspace, return. // If source is not part of an nx workspace, return.

View File

@ -1,18 +1,17 @@
import { ProjectGraph, readCachedProjectGraph, readNxJson } from '@nrwl/devkit'; import { ProjectGraph, readCachedProjectGraph, readNxJson } from '@nrwl/devkit';
import { createProjectFileMappings } from 'nx/src/utils/target-project-locator';
import { isTerminalRun } from './runtime-lint-utils'; import { isTerminalRun } from './runtime-lint-utils';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import {
createProjectRootMappings,
ProjectRootMappings,
} from 'nx/src/project-graph/utils/find-project-for-path';
export function ensureGlobalProjectGraph(ruleName: string) { export function ensureGlobalProjectGraph(ruleName: string) {
/** /**
* Only reuse graph when running from terminal * Only reuse graph when running from terminal
* Enforce every IDE change to get a fresh nxdeps.json * Enforce every IDE change to get a fresh nxdeps.json
*/ */
if ( if (!(global as any).projectGraph || !isTerminalRun()) {
!(global as any).projectGraph ||
!(global as any).projectGraphFileMappings ||
!isTerminalRun()
) {
const nxJson = readNxJson(); const nxJson = readNxJson();
(global as any).workspaceLayout = nxJson.workspaceLayout; (global as any).workspaceLayout = nxJson.workspaceLayout;
@ -22,7 +21,7 @@ export function ensureGlobalProjectGraph(ruleName: string) {
*/ */
try { try {
(global as any).projectGraph = readCachedProjectGraph(); (global as any).projectGraph = readCachedProjectGraph();
(global as any).projectGraphFileMappings = createProjectFileMappings( (global as any).projectRootMappings = createProjectRootMappings(
(global as any).projectGraph.nodes (global as any).projectGraph.nodes
); );
} catch { } catch {
@ -38,11 +37,11 @@ export function ensureGlobalProjectGraph(ruleName: string) {
export function readProjectGraph(ruleName: string): { export function readProjectGraph(ruleName: string): {
projectGraph: ProjectGraph; projectGraph: ProjectGraph;
projectGraphFileMappings: Record<string, string>; projectRootMappings: ProjectRootMappings;
} { } {
ensureGlobalProjectGraph(ruleName); ensureGlobalProjectGraph(ruleName);
return { return {
projectGraph: (global as any).projectGraph, projectGraph: (global as any).projectGraph,
projectGraphFileMappings: (global as any).projectGraphFileMappings, projectRootMappings: (global as any).projectRootMappings,
}; };
} }

View File

@ -1,23 +1,24 @@
import * as path from 'path'; import * as path from 'path';
import { join } from 'path';
import { import {
DependencyType,
joinPathFragments,
normalizePath,
parseJson,
ProjectGraph, ProjectGraph,
ProjectGraphDependency, ProjectGraphDependency,
ProjectGraphProjectNode,
normalizePath,
DependencyType,
parseJson,
ProjectGraphExternalNode, ProjectGraphExternalNode,
joinPathFragments, ProjectGraphProjectNode,
workspaceRoot, workspaceRoot,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { join } from 'path';
import { getPath, pathExists } from './graph-utils'; import { getPath, pathExists } from './graph-utils';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { readFileIfExisting } from 'nx/src/project-graph/file-utils'; import { readFileIfExisting } from 'nx/src/project-graph/file-utils';
import { TargetProjectLocator } from 'nx/src/utils/target-project-locator';
import { import {
TargetProjectLocator, findProjectForPath,
removeExt, ProjectRootMappings,
} from 'nx/src/utils/target-project-locator'; } from 'nx/src/project-graph/utils/find-project-for-path';
export type Deps = { [projectName: string]: ProjectGraphDependency[] }; export type Deps = { [projectName: string]: ProjectGraphDependency[] };
export type DepConstraint = { export type DepConstraint = {
@ -99,7 +100,7 @@ export function getTargetProjectBasedOnRelativeImport(
imp: string, imp: string,
projectPath: string, projectPath: string,
projectGraph: ProjectGraph, projectGraph: ProjectGraph,
projectGraphFileMappings: Record<string, string>, projectRootMappings: ProjectRootMappings,
sourceFilePath: string sourceFilePath: string
): ProjectGraphProjectNode<any> | undefined { ): ProjectGraphProjectNode<any> | undefined {
if (!isRelative(imp)) { if (!isRelative(imp)) {
@ -111,55 +112,17 @@ export function getTargetProjectBasedOnRelativeImport(
projectPath.length + 1 projectPath.length + 1
); );
return findTargetProject(projectGraph, projectGraphFileMappings, targetFile); return findProject(projectGraph, projectRootMappings, targetFile);
} }
function findProjectUsingFile( export function findProject(
projectGraph: ProjectGraph, projectGraph: ProjectGraph,
projectGraphFileMappings: Record<string, string>, projectRootMappings: ProjectRootMappings,
file: string
): ProjectGraphProjectNode {
return projectGraph.nodes[projectGraphFileMappings[file]];
}
export function findSourceProject(
projectGraph: ProjectGraph,
projectGraphFileMappings: Record<string, string>,
sourceFilePath: string sourceFilePath: string
) { ) {
const targetFile = removeExt(sourceFilePath); return projectGraph.nodes[
return findProjectUsingFile( findProjectForPath(sourceFilePath, projectRootMappings)
projectGraph, ];
projectGraphFileMappings,
targetFile
);
}
export function findTargetProject(
projectGraph: ProjectGraph,
projectGraphFileMappings: Record<string, string>,
targetFile: string
) {
let targetProject = findProjectUsingFile(
projectGraph,
projectGraphFileMappings,
targetFile
);
if (!targetProject) {
targetProject = findProjectUsingFile(
projectGraph,
projectGraphFileMappings,
normalizePath(path.join(targetFile, 'index'))
);
}
if (!targetProject) {
targetProject = findProjectUsingFile(
projectGraph,
projectGraphFileMappings,
normalizePath(path.join(targetFile, 'src', 'index'))
);
}
return targetProject;
} }
export function isAbsoluteImportIntoAnotherProject( export function isAbsoluteImportIntoAnotherProject(

View File

@ -1,5 +1,6 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import { findNodes, InsertChange, ReplaceChange } from '@nrwl/workspace'; import { InsertChange, ReplaceChange } from '@nrwl/workspace';
import { findNodes } from 'nx/src/utils/typescript';
import { Tree } from '@angular-devkit/schematics'; import { Tree } from '@angular-devkit/schematics';
import { stripJsonComments } from '@nrwl/devkit'; import { stripJsonComments } from '@nrwl/devkit';
import { Config } from '@jest/types'; import { Config } from '@jest/types';

View File

@ -22,9 +22,9 @@ describe('getTouchedProjects', () => {
it('should return a list of projects for the given changes', () => { it('should return a list of projects for the given changes', () => {
const fileChanges = getFileChanges(['libs/a/index.ts', 'libs/b/index.ts']); const fileChanges = getFileChanges(['libs/a/index.ts', 'libs/b/index.ts']);
const projects = { const projects = {
a: { root: 'libs/a', files: [{ file: 'libs/a/index.ts' }] }, a: { root: 'libs/a' },
b: { root: 'libs/b', files: [{ file: 'libs/b/index.ts' }] }, b: { root: 'libs/b' },
c: { root: 'libs/c', files: [{ file: 'libs/c/index.ts' }] }, c: { root: 'libs/c' },
}; };
expect( expect(
getTouchedProjects(fileChanges, buildProjectGraphNodes(projects)) getTouchedProjects(fileChanges, buildProjectGraphNodes(projects))
@ -34,9 +34,9 @@ describe('getTouchedProjects', () => {
it('should return projects with the root matching a whole directory name in the file path', () => { it('should return projects with the root matching a whole directory name in the file path', () => {
const fileChanges = getFileChanges(['libs/a-b/index.ts']); const fileChanges = getFileChanges(['libs/a-b/index.ts']);
const projects = { const projects = {
a: { root: 'libs/a', files: [{ file: 'libs/a/index.ts' }] }, a: { root: 'libs/a' },
abc: { root: 'libs/a-b-c', files: [{ file: 'libs/a-b-c/index.ts' }] }, abc: { root: 'libs/a-b-c' },
ab: { root: 'libs/a-b', files: [{ file: 'libs/a-b/index.ts' }] }, ab: { root: 'libs/a-b' },
}; };
expect( expect(
getTouchedProjects(fileChanges, buildProjectGraphNodes(projects)) getTouchedProjects(fileChanges, buildProjectGraphNodes(projects))
@ -46,9 +46,9 @@ describe('getTouchedProjects', () => {
it('should return projects with the root matching a whole directory name in the file path', () => { it('should return projects with the root matching a whole directory name in the file path', () => {
const fileChanges = getFileChanges(['libs/a-b/index.ts']); const fileChanges = getFileChanges(['libs/a-b/index.ts']);
const projects = { const projects = {
aaaaa: { root: 'libs/a', files: [{ file: 'libs/a/index.ts' }] }, aaaaa: { root: 'libs/a' },
abc: { root: 'libs/a-b-c', files: [{ file: 'libs/a-b-c/index.ts' }] }, abc: { root: 'libs/a-b-c' },
ab: { root: 'libs/a-b', files: [{ file: 'libs/a-b/index.ts' }] }, ab: { root: 'libs/a-b' },
}; };
expect( expect(
getTouchedProjects(fileChanges, buildProjectGraphNodes(projects)) getTouchedProjects(fileChanges, buildProjectGraphNodes(projects))
@ -58,8 +58,8 @@ describe('getTouchedProjects', () => {
it('should return the most qualifying match with the file path', () => { it('should return the most qualifying match with the file path', () => {
const fileChanges = getFileChanges(['libs/a/b/index.ts']); const fileChanges = getFileChanges(['libs/a/b/index.ts']);
const projects = { const projects = {
aaaaa: { root: 'libs/a', files: [{ file: 'libs/a/index.ts' }] }, aaaaa: { root: 'libs/a' },
ab: { root: 'libs/a/b', files: [{ file: 'libs/a/b/index.ts' }] }, ab: { root: 'libs/a/b' },
}; };
expect( expect(
getTouchedProjects(fileChanges, buildProjectGraphNodes(projects)) getTouchedProjects(fileChanges, buildProjectGraphNodes(projects))
@ -69,8 +69,8 @@ describe('getTouchedProjects', () => {
it('should not return parent project if nested project is touched', () => { it('should not return parent project if nested project is touched', () => {
const fileChanges = getFileChanges(['libs/a/b/index.ts']); const fileChanges = getFileChanges(['libs/a/b/index.ts']);
const projects = { const projects = {
a: { root: 'libs/a', files: [{ file: 'libs/a/index.ts' }] }, a: { root: 'libs/a' },
b: { root: 'libs/a/b', files: [{ file: 'libs/a/b/index.ts' }] }, b: { root: 'libs/a/b' },
}; };
expect( expect(
getTouchedProjects(fileChanges, buildProjectGraphNodes(projects)) getTouchedProjects(fileChanges, buildProjectGraphNodes(projects))

View File

@ -2,23 +2,24 @@ import * as minimatch from 'minimatch';
import { TouchedProjectLocator } from '../affected-project-graph-models'; import { TouchedProjectLocator } from '../affected-project-graph-models';
import { NxJsonConfiguration } from '../../../config/nx-json'; import { NxJsonConfiguration } from '../../../config/nx-json';
import { ProjectGraphProjectNode } from '../../../config/project-graph'; import { ProjectGraphProjectNode } from '../../../config/project-graph';
import { createProjectFileMappings } from '../../../utils/target-project-locator'; import {
createProjectRootMappings,
findProjectForPath,
} from '../../utils/find-project-for-path';
export const getTouchedProjects: TouchedProjectLocator = ( export const getTouchedProjects: TouchedProjectLocator = (
touchedFiles, touchedFiles,
projectGraphNodes projectGraphNodes
): string[] => { ): string[] => {
const allProjectFiles = createProjectFileMappings(projectGraphNodes); const projectRootMap = createProjectRootMappings(projectGraphNodes);
const affected = [];
touchedFiles.forEach((f) => { return touchedFiles.reduce((affected, f) => {
const matchingProject = allProjectFiles[f.file]; const matchingProject = findProjectForPath(f.file, projectRootMap);
if (matchingProject) { if (matchingProject) {
affected.push(matchingProject); affected.push(matchingProject);
} }
}); return affected;
}, []);
return affected;
}; };
export const getImplicitlyTouchedProjects: TouchedProjectLocator = ( export const getImplicitlyTouchedProjects: TouchedProjectLocator = (

View File

@ -1,57 +1,23 @@
import { dirname } from 'path';
import { FileData, ProjectFileMap } from '../config/project-graph'; import { FileData, ProjectFileMap } from '../config/project-graph';
import {
function createProjectRootMappings( createProjectRootMappingsFromProjectConfigurations,
workspaceJson: any, findProjectForPath,
projectFileMap: ProjectFileMap } from './utils/find-project-for-path';
) {
const projectRootMappings = new Map();
for (const projectName of Object.keys(workspaceJson.projects)) {
if (!projectFileMap[projectName]) {
projectFileMap[projectName] = [];
}
const root =
workspaceJson.projects[projectName].root === ''
? '.'
: workspaceJson.projects[projectName].root;
projectRootMappings.set(
root.endsWith('/') ? root.substring(0, root.length - 1) : root,
projectFileMap[projectName]
);
}
return projectRootMappings;
}
function findMatchingProjectFiles(
projectRootMappings: Map<string, FileData[]>,
file: string
) {
let currentPath = file;
do {
currentPath = dirname(currentPath);
const p = projectRootMappings.get(currentPath);
if (p) {
return p;
}
} while (currentPath != dirname(currentPath));
return null;
}
export function createProjectFileMap( export function createProjectFileMap(
workspaceJson: any, workspaceJson: any,
allWorkspaceFiles: FileData[] allWorkspaceFiles: FileData[]
): { projectFileMap: ProjectFileMap; allWorkspaceFiles: FileData[] } { ): { projectFileMap: ProjectFileMap; allWorkspaceFiles: FileData[] } {
const projectFileMap: ProjectFileMap = {}; const projectFileMap: ProjectFileMap = {};
const projectRootMappings = createProjectRootMappings( const projectRootMappings =
workspaceJson, createProjectRootMappingsFromProjectConfigurations(workspaceJson.projects);
projectFileMap
); for (const projectName of Object.keys(workspaceJson.projects)) {
projectFileMap[projectName] ??= [];
}
for (const f of allWorkspaceFiles) { for (const f of allWorkspaceFiles) {
const matchingProjectFiles = findMatchingProjectFiles( const matchingProjectFiles =
projectRootMappings, projectFileMap[findProjectForPath(f.file, projectRootMappings)];
f.file
);
if (matchingProjectFiles) { if (matchingProjectFiles) {
matchingProjectFiles.push(f); matchingProjectFiles.push(f);
} }
@ -66,16 +32,12 @@ export function updateProjectFileMap(
updatedFiles: Map<string, string>, updatedFiles: Map<string, string>,
deletedFiles: string[] deletedFiles: string[]
): { projectFileMap: ProjectFileMap; allWorkspaceFiles: FileData[] } { ): { projectFileMap: ProjectFileMap; allWorkspaceFiles: FileData[] } {
const projectRootMappings = createProjectRootMappings( const projectRootMappings =
workspaceJson, createProjectRootMappingsFromProjectConfigurations(workspaceJson.projects);
projectFileMap
);
for (const f of updatedFiles.keys()) { for (const f of updatedFiles.keys()) {
const matchingProjectFiles = findMatchingProjectFiles( const matchingProjectFiles =
projectRootMappings, projectFileMap[findProjectForPath(f, projectRootMappings)] ?? [];
f
);
if (matchingProjectFiles) { if (matchingProjectFiles) {
const fileData: FileData = matchingProjectFiles.find((t) => t.file === f); const fileData: FileData = matchingProjectFiles.find((t) => t.file === f);
if (fileData) { if (fileData) {
@ -100,10 +62,8 @@ export function updateProjectFileMap(
} }
for (const f of deletedFiles) { for (const f of deletedFiles) {
const matchingProjectFiles = findMatchingProjectFiles( const matchingProjectFiles =
projectRootMappings, projectFileMap[findProjectForPath(f, projectRootMappings)] ?? [];
f
);
if (matchingProjectFiles) { if (matchingProjectFiles) {
const index = matchingProjectFiles.findIndex((t) => t.file === f); const index = matchingProjectFiles.findIndex((t) => t.file === f);
if (index > -1) { if (index > -1) {

View File

@ -0,0 +1,85 @@
import {
createProjectRootMappings,
findProjectForPath,
} from './find-project-for-path';
import { ProjectGraph } from 'nx/src/config/project-graph';
describe('get project utils', () => {
let projectGraph: ProjectGraph;
let projectRootMappings: Map<string, string>;
describe('findProject', () => {
beforeEach(() => {
projectGraph = {
nodes: {
'demo-app': {
name: 'demo-app',
type: 'app',
data: {
root: 'apps/demo-app',
},
},
ui: {
name: 'ui',
type: 'lib',
data: {
root: 'libs/ui',
sourceRoot: 'libs/ui/src',
projectType: 'library',
targets: {},
},
},
core: {
name: 'core',
type: 'lib',
data: {
root: 'libs/core',
sourceRoot: 'libs/core/src',
projectType: 'library',
targets: {},
},
},
'implicit-lib': {
name: 'implicit-lib',
type: 'lib',
data: {},
},
},
dependencies: {
'demo-app': [
{
type: 'static',
source: 'demo-app',
target: 'ui',
},
{
type: 'static',
source: 'demo-app',
target: 'npm:chalk',
},
{
type: 'static',
source: 'demo-app',
target: 'core',
},
],
},
};
projectRootMappings = createProjectRootMappings(projectGraph.nodes);
});
it('should find the project given a file within its src root', () => {
expect(findProjectForPath('apps/demo-app', projectRootMappings)).toEqual(
'demo-app'
);
expect(
findProjectForPath('apps/demo-app/src', projectRootMappings)
).toEqual('demo-app');
expect(
findProjectForPath('apps/demo-app/src/subdir/bla', projectRootMappings)
).toEqual('demo-app');
});
});
});

View File

@ -0,0 +1,64 @@
import { dirname } from 'path';
import { ProjectGraphProjectNode } from '../../config/project-graph';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
export type ProjectRootMappings = Map<string, string>;
/**
* This creates a map of project roots to project names to easily look up the project of a file
* @param projects This is the map of project configurations commonly found in "workspace.json"
*/
export function createProjectRootMappingsFromProjectConfigurations(
projects: Record<string, ProjectConfiguration>
) {
const projectRootMappings: ProjectRootMappings = new Map();
for (const projectName of Object.keys(projects)) {
const root = projects[projectName].root;
projectRootMappings.set(normalizeProjectRoot(root), projectName);
}
return projectRootMappings;
}
/**
* This creates a map of project roots to project names to easily look up the project of a file
* @param nodes This is the nodes from the project graph
*/
export function createProjectRootMappings(
nodes: Record<string, ProjectGraphProjectNode>
): ProjectRootMappings {
const projectRootMappings = new Map<string, string>();
for (const projectName of Object.keys(nodes)) {
let root = nodes[projectName].data.root;
projectRootMappings.set(normalizeProjectRoot(root), projectName);
}
return projectRootMappings;
}
/**
* Locates a project in projectRootMap based on a file within it
* @param filePath path that is inside of projectName. This should be relative from the workspace root
* @param projectRootMap Map<projectRoot, projectName> Use {@link createProjectRootMappings} to create this
*/
export function findProjectForPath(
filePath: string,
projectRootMap: ProjectRootMappings
): string | null {
let currentPath = filePath;
for (
;
currentPath != dirname(currentPath);
currentPath = dirname(currentPath)
) {
const p = projectRootMap.get(currentPath);
if (p) {
return p;
}
}
return projectRootMap.get(currentPath);
}
function normalizeProjectRoot(root: string) {
root = root === '' ? '.' : root;
return root && root.endsWith('/') ? root.substring(0, root.length - 1) : root;
}

View File

@ -13,11 +13,14 @@ import {
import { registerTsProject } from './register'; import { registerTsProject } from './register';
import { import {
ProjectConfiguration, ProjectConfiguration,
TargetConfiguration,
ProjectsConfigurations, ProjectsConfigurations,
TargetConfiguration,
} from '../config/workspace-json-project-json'; } from '../config/workspace-json-project-json';
import { findMatchingProjectForPath } from './target-project-locator';
import { logger } from './logger'; import { logger } from './logger';
import {
createProjectRootMappingsFromProjectConfigurations,
findProjectForPath,
} from '../project-graph/utils/find-project-for-path';
export type ProjectTargetConfigurator = ( export type ProjectTargetConfigurator = (
file: string file: string
@ -192,15 +195,13 @@ function findNxProjectForImportPath(
): string | null { ): string | null {
const tsConfigPaths: Record<string, string[]> = readTsConfigPaths(root); const tsConfigPaths: Record<string, string[]> = readTsConfigPaths(root);
const possiblePaths = tsConfigPaths[importPath]?.map((p) => const possiblePaths = tsConfigPaths[importPath]?.map((p) =>
path.resolve(root, p) path.relative(root, path.join(root, p))
); );
if (possiblePaths?.length) { if (possiblePaths?.length) {
const projectRootMappings = buildProjectRootMap(workspace.projects, root); const projectRootMappings =
createProjectRootMappingsFromProjectConfigurations(workspace.projects);
for (const tsConfigPath of possiblePaths) { for (const tsConfigPath of possiblePaths) {
const nxProject = findMatchingProjectForPath( const nxProject = findProjectForPath(tsConfigPath, projectRootMappings);
tsConfigPath,
projectRootMappings
);
if (nxProject) { if (nxProject) {
return nxProject; return nxProject;
} }
@ -218,16 +219,6 @@ function findNxProjectForImportPath(
} }
} }
function buildProjectRootMap(
projects: Record<string, ProjectConfiguration>,
root: string
) {
return Object.entries(projects).reduce((m, [project, config]) => {
m.set(path.resolve(root, config.root), project);
return m;
}, new Map<string, string>());
}
let tsconfigPaths: Record<string, string[]>; let tsconfigPaths: Record<string, string[]>;
function readTsConfigPaths(root: string = workspaceRoot) { function readTsConfigPaths(root: string = workspaceRoot) {
if (!tsconfigPaths) { if (!tsconfigPaths) {

View File

@ -13,7 +13,6 @@ jest.mock('nx/src/utils/fileutils', () => ({
import { PackageJson } from './package-json'; import { PackageJson } from './package-json';
import { ProjectGraph } from '../config/project-graph'; import { ProjectGraph } from '../config/project-graph';
import { import {
getProjectNameFromDirPath,
getSourceDirOfDependentProjects, getSourceDirOfDependentProjects,
mergeNpmScriptsWithTargets, mergeNpmScriptsWithTargets,
} from './project-graph-utils'; } from './project-graph-utils';
@ -116,26 +115,6 @@ describe('project graph utils', () => {
expect(warnings).toContain('implicit-lib'); expect(warnings).toContain('implicit-lib');
}); });
}); });
it('should find the project given a file within its src root', () => {
expect(getProjectNameFromDirPath('apps/demo-app', projGraph)).toEqual(
'demo-app'
);
expect(getProjectNameFromDirPath('apps/demo-app/src', projGraph)).toEqual(
'demo-app'
);
expect(
getProjectNameFromDirPath('apps/demo-app/src/subdir/bla', projGraph)
).toEqual('demo-app');
});
it('should throw an error if the project name has not been found', () => {
expect(() => {
getProjectNameFromDirPath('apps/demo-app-unknown');
}).toThrowError();
});
}); });
describe('mergeNpmScriptsWithTargets', () => { describe('mergeNpmScriptsWithTargets', () => {

View File

@ -1,8 +1,7 @@
import { buildTargetFromScript, PackageJson } from './package-json'; import { buildTargetFromScript, PackageJson } from './package-json';
import { join, relative } from 'path'; import { join } from 'path';
import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph'; import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph';
import { readJsonFile } from './fileutils'; import { readJsonFile } from './fileutils';
import { normalizePath } from './path';
import { readCachedProjectGraph } from '../project-graph/project-graph'; import { readCachedProjectGraph } from '../project-graph/project-graph';
import { TargetConfiguration } from '../config/workspace-json-project-json'; import { TargetConfiguration } from '../config/workspace-json-project-json';
@ -74,38 +73,6 @@ export function getSourceDirOfDependentProjects(
); );
} }
/**
* Finds the project node name by a file that lives within it's src root
* @param projRelativeDirPath directory path relative to the workspace root
* @param projectGraph
*/
export function getProjectNameFromDirPath(
projRelativeDirPath: string,
projectGraph = readCachedProjectGraph()
) {
let parentNodeName = null;
for (const [nodeName, node] of Object.entries(projectGraph.nodes)) {
const normalizedRootPath = normalizePath(node.data.root);
const normalizedProjRelPath = normalizePath(projRelativeDirPath);
const relativePath = relative(normalizedRootPath, normalizedProjRelPath);
const isMatch = relativePath && !relativePath.startsWith('..');
if (isMatch || normalizedRootPath === normalizedProjRelPath) {
parentNodeName = nodeName;
break;
}
}
if (!parentNodeName) {
throw new Error(
`Could not find any project containing the file "${projRelativeDirPath}" among it's project files`
);
}
return parentNodeName;
}
/** /**
* Find all internal project dependencies. * Find all internal project dependencies.
* All the external (npm) dependencies will be filtered out * All the external (npm) dependencies will be filtered out

View File

@ -75,15 +75,17 @@ describe('findTargetProjectWithImport', () => {
...nxJson, ...nxJson,
} as any, } as any,
fileMap: { fileMap: {
rootProj: [
{
file: 'index.ts',
hash: 'some-hash',
},
],
proj: [ proj: [
{ {
file: 'libs/proj/index.ts', file: 'libs/proj/index.ts',
hash: 'some-hash', hash: 'some-hash',
}, },
{
file: 'libs/proj/class.ts',
hash: 'some-hash',
},
], ],
proj2: [ proj2: [
{ {
@ -151,12 +153,20 @@ describe('findTargetProjectWithImport', () => {
} as any; } as any;
projects = { projects = {
rootProj: {
name: 'rootProj',
type: 'lib',
data: {
root: '.',
files: [],
},
},
proj3a: { proj3a: {
name: 'proj3a', name: 'proj3a',
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj3a', root: 'libs/proj3a',
files: ctx.fileMap['proj3a'], files: [],
}, },
}, },
proj2: { proj2: {
@ -164,7 +174,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj2', root: 'libs/proj2',
files: ctx.fileMap['proj2'], files: [],
}, },
}, },
proj: { proj: {
@ -172,7 +182,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj', root: 'libs/proj',
files: ctx.fileMap['proj'], files: [],
}, },
}, },
proj1234: { proj1234: {
@ -180,7 +190,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj1234', root: 'libs/proj1234',
files: ctx.fileMap['proj1234'], files: [],
}, },
}, },
proj123: { proj123: {
@ -188,7 +198,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj123', root: 'libs/proj123',
files: ctx.fileMap['proj123'], files: [],
}, },
}, },
proj4ab: { proj4ab: {
@ -196,7 +206,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj4ab', root: 'libs/proj4ab',
files: ctx.fileMap['proj4ab'], files: [],
}, },
}, },
proj5: { proj5: {
@ -204,7 +214,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj5', root: 'libs/proj5',
files: ctx.fileMap['proj5'], files: [],
}, },
}, },
proj6: { proj6: {
@ -212,7 +222,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj6', root: 'libs/proj6',
files: ctx.fileMap['proj6'], files: [],
}, },
}, },
proj7: { proj7: {
@ -220,7 +230,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj7', root: 'libs/proj7',
files: ctx.fileMap['proj7'], files: [],
}, },
}, },
'proj1234-child': { 'proj1234-child': {
@ -228,7 +238,7 @@ describe('findTargetProjectWithImport', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj1234-child', root: 'libs/proj1234-child',
files: ctx.fileMap['proj1234-child'], files: [],
}, },
}, },
}; };
@ -319,11 +329,16 @@ describe('findTargetProjectWithImport', () => {
'../proj/../index.ts', '../proj/../index.ts',
'libs/proj/src/index.ts' 'libs/proj/src/index.ts'
); );
const res5 = targetProjectLocator.findProjectWithImport(
'../../../index.ts',
'libs/proj/src/index.ts'
);
expect(res1).toEqual('proj'); expect(res1).toEqual('proj');
expect(res2).toEqual('proj'); expect(res2).toEqual('proj');
expect(res3).toEqual('proj2'); expect(res3).toEqual('proj2');
expect(res4).toEqual('proj'); expect(res4).toEqual('proj');
expect(res5).toEqual('rootProj');
}); });
it('should be able to resolve a module by using tsConfig paths', () => { it('should be able to resolve a module by using tsConfig paths', () => {
@ -512,10 +527,6 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
file: 'libs/proj/index.ts', file: 'libs/proj/index.ts',
hash: 'some-hash', hash: 'some-hash',
}, },
{
file: 'libs/proj/class.ts',
hash: 'some-hash',
},
], ],
proj2: [ proj2: [
{ {
@ -588,7 +599,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj3a', root: 'libs/proj3a',
files: ctx.fileMap.proj3a, files: [],
}, },
}, },
proj2: { proj2: {
@ -596,7 +607,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj2', root: 'libs/proj2',
files: ctx.fileMap.proj2, files: [],
}, },
}, },
proj: { proj: {
@ -604,7 +615,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj', root: 'libs/proj',
files: ctx.fileMap.proj, files: [],
}, },
}, },
proj1234: { proj1234: {
@ -612,7 +623,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj1234', root: 'libs/proj1234',
files: ctx.fileMap.proj1234, files: [],
}, },
}, },
proj123: { proj123: {
@ -620,7 +631,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj123', root: 'libs/proj123',
files: ctx.fileMap.proj123, files: [],
}, },
}, },
proj4ab: { proj4ab: {
@ -628,7 +639,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj4ab', root: 'libs/proj4ab',
files: ctx.fileMap.proj4ab, files: [],
}, },
}, },
proj5: { proj5: {
@ -636,7 +647,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj5', root: 'libs/proj5',
files: ctx.fileMap.proj5, files: [],
}, },
}, },
proj6: { proj6: {
@ -644,7 +655,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj6', root: 'libs/proj6',
files: ctx.fileMap.proj6, files: [],
}, },
}, },
proj7: { proj7: {
@ -652,7 +663,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj7', root: 'libs/proj7',
files: ctx.fileMap.proj7, files: [],
}, },
}, },
'proj1234-child': { 'proj1234-child': {
@ -660,7 +671,7 @@ describe('findTargetProjectWithImport (without tsconfig.json)', () => {
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/proj1234-child', root: 'libs/proj1234-child',
files: ctx.fileMap['proj1234-child'], files: [],
}, },
}, },
}; };

View File

@ -6,9 +6,13 @@ import {
ProjectGraphExternalNode, ProjectGraphExternalNode,
ProjectGraphProjectNode, ProjectGraphProjectNode,
} from '../config/project-graph'; } from '../config/project-graph';
import {
createProjectRootMappings,
findProjectForPath,
} from '../project-graph/utils/find-project-for-path';
export class TargetProjectLocator { export class TargetProjectLocator {
private allProjectsFiles = createProjectFileMappings(this.nodes); private projectRootMappings = createProjectRootMappings(this.nodes);
private npmProjects = filterRootExternalDependencies(this.externalNodes); private npmProjects = filterRootExternalDependencies(this.externalNodes);
private tsConfig = this.getRootTsConfig(); private tsConfig = this.getRootTsConfig();
private paths = this.tsConfig.config?.compilerOptions?.paths; private paths = this.tsConfig.config?.compilerOptions?.paths;
@ -145,19 +149,10 @@ export class TargetProjectLocator {
const normalizedResolvedModule = resolvedModule.startsWith('./') const normalizedResolvedModule = resolvedModule.startsWith('./')
? resolvedModule.substring(2) ? resolvedModule.substring(2)
: resolvedModule; : resolvedModule;
const importedProject = this.findMatchingProjectFiles(
// for wildcard paths, we need to find file that starts with resolvedModule normalizedResolvedModule
if (normalizedResolvedModule.endsWith('*')) {
const matchingFile = Object.keys(this.allProjectsFiles).find((f) =>
f.startsWith(normalizedResolvedModule.slice(0, -1))
);
return matchingFile && this.allProjectsFiles[matchingFile];
}
return (
this.allProjectsFiles[normalizedResolvedModule] ||
this.allProjectsFiles[`${normalizedResolvedModule}/index`]
); );
return importedProject ? importedProject.name : void 0;
} }
private getAbsolutePath(path: string) { private getAbsolutePath(path: string) {
@ -183,8 +178,8 @@ export class TargetProjectLocator {
} }
private findMatchingProjectFiles(file: string) { private findMatchingProjectFiles(file: string) {
const project = this.allProjectsFiles[file]; const project = findProjectForPath(file, this.projectRootMappings);
return project && this.nodes[project]; return this.nodes[project];
} }
} }
@ -206,78 +201,3 @@ function filterRootExternalDependencies(
} }
return nodes; return nodes;
} }
/**
* @deprecated This function will be removed in v16. Use {@link createProjectFileMappings} instead.
*
* Mapps the project root paths to the project name
* @param nodes
* @returns
*/
export function createProjectRootMappings(
nodes: Record<string, ProjectGraphProjectNode>
): Map<string, string> {
const projectRootMappings = new Map<string, string>();
for (const projectName of Object.keys(nodes)) {
const root = nodes[projectName].data.root;
projectRootMappings.set(
root && root.endsWith('/') ? root.substring(0, root.length - 1) : root,
projectName
);
}
return projectRootMappings;
}
/**
* Strips the file extension from the file path
* @param file
* @returns
*/
export function removeExt(file: string): string {
return file.replace(/(?<!(^|\/))\.[^/.]+$/, '');
}
/**
* Maps the file paths to the project name, both with and without the file extension
* apps/myapp/src/main.ts -> { 'apps/myapp/src/main': 'myapp', 'apps/myapp/src/main.ts': 'myapp' }
* @param projectGraph
* @returns
*/
export function createProjectFileMappings(
nodes: Record<string, ProjectGraphProjectNode>
): Record<string, string> {
const result: Record<string, string> = {};
Object.entries(nodes).forEach(([name, node]) => {
node.data.files.forEach(({ file }) => {
const fileName = removeExt(file);
result[fileName] = name;
result[file] = name;
});
});
return result;
}
/**
* @deprecated This function will be removed in v16. Use {@link createProjectFileMappings} instead.
*
* Locates a project in projectRootMap based on a file within it
* @param filePath path that is inside of projectName
* @param projectRootMap Map<projectRoot, projectName>
*/
export function findMatchingProjectForPath(
filePath: string,
projectRootMap: Map<string, string>
): string | null {
for (
let currentPath = filePath;
currentPath != dirname(currentPath);
currentPath = dirname(currentPath)
) {
const p = projectRootMap.get(currentPath);
if (p) {
return p;
}
}
return null;
}

View File

@ -2,6 +2,7 @@ import { workspaceRoot } from './workspace-root';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { dirname, join } from 'path'; import { dirname, join } from 'path';
import type * as ts from 'typescript'; import type * as ts from 'typescript';
import type { Node, SyntaxKind } from 'typescript';
const normalizedAppRoot = workspaceRoot.replace(/\\/g, '/'); const normalizedAppRoot = workspaceRoot.replace(/\\/g, '/');
@ -106,15 +107,15 @@ export function getRootTsConfigPath(): string | null {
} }
export function findNodes( export function findNodes(
node: ts.Node, node: Node,
kind: ts.SyntaxKind | ts.SyntaxKind[], kind: SyntaxKind | SyntaxKind[],
max = Infinity max = Infinity
): ts.Node[] { ): Node[] {
if (!node || max == 0) { if (!node || max == 0) {
return []; return [];
} }
const arr: ts.Node[] = []; const arr: Node[] = [];
const hasMatch = Array.isArray(kind) const hasMatch = Array.isArray(kind)
? kind.includes(node.kind) ? kind.includes(node.kind)
: node.kind === kind; : node.kind === kind;

View File

@ -21,7 +21,6 @@ export {
readPackageJson, readPackageJson,
} from 'nx/src/project-graph/file-utils'; } from 'nx/src/project-graph/file-utils';
export { ProjectGraphCache } from 'nx/src/project-graph/nx-deps-cache'; export { ProjectGraphCache } from 'nx/src/project-graph/nx-deps-cache';
export { findNodes } from 'nx/src/utils/typescript';
export { export {
readJsonInTree, readJsonInTree,
updateJsonInTree, updateJsonInTree,
@ -35,6 +34,7 @@ export {
getProjectConfig, getProjectConfig,
addParameterToConstructor, addParameterToConstructor,
createOrUpdate, createOrUpdate,
findNodes, // TODO(v16): remove this
updatePackageJsonDependencies, updatePackageJsonDependencies,
readWorkspace, readWorkspace,
renameSyncInTree, renameSyncInTree,

View File

@ -2,12 +2,13 @@ import { joinPathFragments, logger } from '@nrwl/devkit';
import { workspaceRoot } from 'nx/src/utils/workspace-root'; import { workspaceRoot } from 'nx/src/utils/workspace-root';
import { dirname, join, relative, resolve } from 'path'; import { dirname, join, relative, resolve } from 'path';
import { readCachedProjectGraph } from 'nx/src/project-graph/project-graph'; import { readCachedProjectGraph } from 'nx/src/project-graph/project-graph';
import { import { getSourceDirOfDependentProjects } from 'nx/src/utils/project-graph-utils';
getProjectNameFromDirPath,
getSourceDirOfDependentProjects,
} from 'nx/src/utils/project-graph-utils';
import { existsSync, lstatSync, readdirSync, readFileSync } from 'fs'; import { existsSync, lstatSync, readdirSync, readFileSync } from 'fs';
import ignore, { Ignore } from 'ignore'; import ignore, { Ignore } from 'ignore';
import {
createProjectRootMappings,
findProjectForPath,
} from 'nx/src/project-graph/utils/find-project-for-path';
function configureIgnore() { function configureIgnore() {
let ig: Ignore; let ig: Ignore;
@ -31,14 +32,21 @@ export function createGlobPatternsForDependencies(
let ig = configureIgnore(); let ig = configureIgnore();
const filenameRelativeToWorkspaceRoot = relative(workspaceRoot, dirPath); const filenameRelativeToWorkspaceRoot = relative(workspaceRoot, dirPath);
const projectGraph = readCachedProjectGraph(); const projectGraph = readCachedProjectGraph();
const projectRootMappings = createProjectRootMappings(projectGraph.nodes);
// find the project // find the project
let projectName; let projectName;
try { try {
projectName = getProjectNameFromDirPath( projectName = findProjectForPath(
filenameRelativeToWorkspaceRoot, filenameRelativeToWorkspaceRoot,
projectGraph projectRootMappings
); );
if (!projectName) {
throw new Error(
`Could not find any project containing the file "${filenameRelativeToWorkspaceRoot}" among it's project files`
);
}
} catch (e) { } catch (e) {
throw new Error( throw new Error(
`createGlobPatternsForDependencies: Error when trying to determine main project.\n${e?.message}` `createGlobPatternsForDependencies: Error when trying to determine main project.\n${e?.message}`

View File

@ -5,6 +5,7 @@ import { dirname, join } from 'path';
import type * as ts from 'typescript'; import type * as ts from 'typescript';
export { compileTypeScript } from './typescript/compilation'; export { compileTypeScript } from './typescript/compilation';
export type { TypeScriptCompilationOptions } from './typescript/compilation'; export type { TypeScriptCompilationOptions } from './typescript/compilation';
export { findNodes } from './typescript/find-nodes'; // TODO(v16): remove this
export { getSourceNodes } from './typescript/get-source-nodes'; export { getSourceNodes } from './typescript/get-source-nodes';
const normalizedAppRoot = workspaceRoot.replace(/\\/g, '/'); const normalizedAppRoot = workspaceRoot.replace(/\\/g, '/');

View File

@ -0,0 +1,16 @@
import { findNodes as _findNodes } from 'nx/src/utils/typescript';
import type { Node, SyntaxKind } from 'typescript';
// TODO(v16): This should be removed.
/**
* @deprecated This function is deprecated and no longer supported.
*/
export function findNodes(
node: Node,
kind: SyntaxKind | SyntaxKind[],
max = Infinity
) {
console.warn('"findNodes" is deprecated and no longer supported.');
return _findNodes(node, kind, max);
}

View File

@ -18,10 +18,15 @@ import {
Tree, Tree,
} from '@angular-devkit/schematics'; } from '@angular-devkit/schematics';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { parseJson, serializeJson, FileData } from '@nrwl/devkit'; import {
parseJson,
ProjectConfiguration,
serializeJson,
FileData,
} from '@nrwl/devkit';
import { getWorkspacePath } from './cli-config-utils'; import { getWorkspacePath } from './cli-config-utils';
import { extname, join, normalize, Path } from '@angular-devkit/core'; import { extname, join, normalize, Path } from '@angular-devkit/core';
import type { NxJsonConfiguration } from '@nrwl/devkit'; import type { NxJsonConfiguration, ProjectsConfigurations } from '@nrwl/devkit';
import { addInstallTask } from './rules/add-install-task'; import { addInstallTask } from './rules/add-install-task';
import { findNodes } from 'nx/src/utils/typescript'; import { findNodes } from 'nx/src/utils/typescript';
import { getSourceNodes } from '../utilities/typescript/get-source-nodes'; import { getSourceNodes } from '../utilities/typescript/get-source-nodes';
@ -66,6 +71,7 @@ export function sortObjectByKeys(obj: unknown) {
}, {}); }, {});
} }
export { findNodes } from '../utilities/typescript/find-nodes'; // TODO(v16): Remove this
export { getSourceNodes } from '../utilities/typescript/get-source-nodes'; export { getSourceNodes } from '../utilities/typescript/get-source-nodes';
export interface Change { export interface Change {