diff --git a/packages/angular/plugins/component-testing.ts b/packages/angular/plugins/component-testing.ts index f2ea4541711f44..9aa2933edd95a7 100644 --- a/packages/angular/plugins/component-testing.ts +++ b/packages/angular/plugins/component-testing.ts @@ -21,8 +21,7 @@ import { stripIndents, workspaceRoot, } from '@nrwl/devkit'; -import { mapProjectGraphFiles } from '@nrwl/workspace/src/utils/runtime-lint-utils'; -import { lstatSync, mkdirSync, writeFileSync } from 'fs'; +import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs'; import { dirname, join, relative } from 'path'; import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/webpack-browser.impl'; @@ -279,14 +278,15 @@ function withSchemaDefaults(options: any): BrowserBuilderSchema { * this file should get cleaned up via the cypress executor */ function getTempStylesForTailwind(ctExecutorContext: ExecutorContext) { - const mappedGraph = mapProjectGraphFiles(ctExecutorContext.projectGraph); const ctProjectConfig = ctExecutorContext.projectGraph.nodes[ ctExecutorContext.projectName ].data as ProjectConfiguration; // angular only supports `tailwind.config.{js,cjs}` const ctProjectTailwindConfig = join(ctProjectConfig.root, 'tailwind.config'); - const isTailWindInCtProject = !!mappedGraph.allFiles[ctProjectTailwindConfig]; - const isTailWindInRoot = !!mappedGraph.allFiles['tailwind.config']; + const isTailWindInCtProject = existsSync(ctProjectTailwindConfig); + const isTailWindInRoot = existsSync( + join(ctExecutorContext.root, 'tailwind.config') + ); if (isTailWindInRoot || isTailWindInCtProject) { const pathToStyle = getTempTailwindPath(ctExecutorContext); diff --git a/packages/angular/src/generators/component/lib/normalize-options.ts b/packages/angular/src/generators/component/lib/normalize-options.ts index 7a308f429a6637..587e68b18272fb 100644 --- a/packages/angular/src/generators/component/lib/normalize-options.ts +++ b/packages/angular/src/generators/component/lib/normalize-options.ts @@ -1,27 +1,25 @@ import type { Tree } from '@nrwl/devkit'; import { joinPathFragments, + readCachedProjectGraph, readProjectConfiguration, readWorkspaceConfiguration, } from '@nrwl/devkit'; import type { NormalizedSchema, Schema } from '../schema'; -import { getProjectNameFromDirPath } from 'nx/src/utils/project-graph-utils'; - -function getProjectFromPath(path: string) { - try { - return getProjectNameFromDirPath(path); - } catch { - return null; - } -} +import { + createProjectRootMappings, + findProjectForPath, +} from 'nx/src/project-graph/utils/find-project-for-path'; export function normalizeOptions( tree: Tree, options: Schema ): NormalizedSchema { + const projectGraph = readCachedProjectGraph(); + const projectRootMappings = createProjectRootMappings(projectGraph.nodes); const project = options.project ?? - getProjectFromPath(options.path) ?? + findProjectForPath(options.path, projectRootMappings) ?? readWorkspaceConfiguration(tree).defaultProject; const { projectType, root, sourceRoot } = readProjectConfiguration( tree, diff --git a/packages/cypress/plugins/cypress-preset.ts b/packages/cypress/plugins/cypress-preset.ts index 3a687eac206557..6b3230c848bca5 100644 --- a/packages/cypress/plugins/cypress-preset.ts +++ b/packages/cypress/plugins/cypress-preset.ts @@ -8,10 +8,13 @@ import { TargetConfiguration, workspaceRoot, } from '@nrwl/devkit'; -import { mapProjectGraphFiles } from '@nrwl/workspace/src/utils/runtime-lint-utils'; import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph'; import { dirname, extname, join, relative } from 'path'; import { lstatSync } from 'fs'; +import { + createProjectRootMappings, + findProjectForPath, +} from 'nx/src/project-graph/utils/find-project-for-path'; interface BaseCypressPreset { videosFolder: string; @@ -90,9 +93,11 @@ export function getProjectConfigByPath( : configFileFromWorkspaceRoot ); - const mappedGraph = mapProjectGraphFiles(graph); - const componentTestingProjectName = - mappedGraph.allFiles[normalizedPathFromWorkspaceRoot]; + const projectRootMappings = createProjectRootMappings(graph.nodes); + const componentTestingProjectName = findProjectForPath( + normalizedPathFromWorkspaceRoot, + projectRootMappings + ); if ( !componentTestingProjectName || !graph.nodes[componentTestingProjectName]?.data diff --git a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts index 88cd88aa6ddc2c..294b8218d46825 100644 --- a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts +++ b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts @@ -1,6 +1,5 @@ import type { FileData, ProjectGraph } from '@nrwl/devkit'; import { DependencyType } from '@nrwl/devkit'; -import { mapProjectGraphFiles } from '../utils/runtime-lint-utils'; import * as parser from '@typescript-eslint/parser'; import { TSESLint } from '@typescript-eslint/utils'; import { vol } from 'memfs'; @@ -8,6 +7,7 @@ import { TargetProjectLocator } from 'nx/src/utils/target-project-locator'; import enforceModuleBoundaries, { RULE_NAME as enforceModuleBoundariesRuleName, } from '../../src/rules/enforce-module-boundaries'; +import { createProjectRootMappings } from 'nx/src/project-graph/utils/find-project-for-path'; jest.mock('fs', () => require('memfs').fs); @@ -1883,7 +1883,10 @@ function runRule( projectGraph: ProjectGraph ): TSESLint.Linter.LintMessage[] { (global as any).projectPath = `${process.cwd()}/proj`; - (global as any).projectGraph = mapProjectGraphFiles(projectGraph); + (global as any).projectGraph = projectGraph; + (global as any).projectRootMappings = createProjectRootMappings( + projectGraph.nodes + ); (global as any).targetProjectLocator = new TargetProjectLocator( projectGraph.nodes, projectGraph.externalNodes diff --git a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts index 6d1c7af5d57c8b..ecc6b523a15221 100644 --- a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts +++ b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts @@ -15,9 +15,8 @@ import { findConstraintsFor, findDependenciesWithTags, findProjectUsingImport, - findSourceProject, + findProject, findTransitiveExternalDependencies, - findTargetProject, getSourceFilePath, getTargetProjectBasedOnRelativeImport, groupImports, @@ -154,7 +153,7 @@ export default createESLintRule({ ); const fileName = normalizePath(context.getFilename()); - const projectGraph = readProjectGraph(RULE_NAME); + const { projectGraph, projectRootMappings } = readProjectGraph(RULE_NAME); if (!projectGraph) { return {}; @@ -198,7 +197,11 @@ export default createESLintRule({ const sourceFilePath = getSourceFilePath(fileName, projectPath); - const sourceProject = findSourceProject(projectGraph, sourceFilePath); + const sourceProject = findProject( + projectGraph, + projectRootMappings, + sourceFilePath + ); // If source is not part of an nx workspace, return. if (!sourceProject) { return; @@ -210,12 +213,13 @@ export default createESLintRule({ let targetProject: ProjectGraphProjectNode | ProjectGraphExternalNode; if (isAbsoluteImportIntoAnotherProj) { - targetProject = findTargetProject(projectGraph, imp); + targetProject = findProject(projectGraph, projectRootMappings, imp); } else { targetProject = getTargetProjectBasedOnRelativeImport( imp, projectPath, projectGraph, + projectRootMappings, sourceFilePath ); } diff --git a/packages/eslint-plugin-nx/src/rules/nx-plugin-checks.ts b/packages/eslint-plugin-nx/src/rules/nx-plugin-checks.ts index a7024277e46d5c..239d71c9526989 100644 --- a/packages/eslint-plugin-nx/src/rules/nx-plugin-checks.ts +++ b/packages/eslint-plugin-nx/src/rules/nx-plugin-checks.ts @@ -6,10 +6,7 @@ import { readJsonFile, workspaceRoot, } from '@nrwl/devkit'; -import { - findSourceProject, - getSourceFilePath, -} from '../utils/runtime-lint-utils'; +import { findProject, getSourceFilePath } from '../utils/runtime-lint-utils'; import { existsSync } from 'fs'; import { registerTsProject } from 'nx/src/utils/register'; import * as path from 'path'; @@ -87,14 +84,18 @@ export default createESLintRule({ return {}; } - const projectGraph = readProjectGraph(RULE_NAME); + const { projectGraph, projectRootMappings } = readProjectGraph(RULE_NAME); const sourceFilePath = getSourceFilePath( context.getFilename(), workspaceRoot ); - const sourceProject = findSourceProject(projectGraph, sourceFilePath); + const sourceProject = findProject( + projectGraph, + projectRootMappings, + sourceFilePath + ); // If source is not part of an nx workspace, return. if (!sourceProject) { return {}; diff --git a/packages/eslint-plugin-nx/src/utils/project-graph-utils.ts b/packages/eslint-plugin-nx/src/utils/project-graph-utils.ts index 760eebe292b385..26525ed7e6c96b 100644 --- a/packages/eslint-plugin-nx/src/utils/project-graph-utils.ts +++ b/packages/eslint-plugin-nx/src/utils/project-graph-utils.ts @@ -1,10 +1,10 @@ -import { readCachedProjectGraph, readNxJson } from '@nrwl/devkit'; -import { - isTerminalRun, - MappedProjectGraph, - mapProjectGraphFiles, -} from './runtime-lint-utils'; +import { ProjectGraph, readCachedProjectGraph, readNxJson } from '@nrwl/devkit'; +import { isTerminalRun } from './runtime-lint-utils'; import * as chalk from 'chalk'; +import { + createProjectRootMappings, + ProjectRootMappings, +} from 'nx/src/project-graph/utils/find-project-for-path'; export function ensureGlobalProjectGraph(ruleName: string) { /** @@ -20,8 +20,9 @@ export function ensureGlobalProjectGraph(ruleName: string) { * the ProjectGraph may or may not exist by the time the lint rule is invoked for the first time. */ try { - (global as any).projectGraph = mapProjectGraphFiles( - readCachedProjectGraph() + (global as any).projectGraph = readCachedProjectGraph(); + (global as any).projectRootMappings = createProjectRootMappings( + (global as any).projectGraph ); } catch { const WARNING_PREFIX = `${chalk.reset.keyword('orange')('warning')}`; @@ -34,7 +35,13 @@ export function ensureGlobalProjectGraph(ruleName: string) { } } -export function readProjectGraph(ruleName: string) { +export function readProjectGraph(ruleName: string): { + projectGraph: ProjectGraph; + projectRootMappings: ProjectRootMappings; +} { ensureGlobalProjectGraph(ruleName); - return (global as any).projectGraph as MappedProjectGraph; + return { + projectGraph: (global as any).projectGraph, + projectRootMappings: (global as any).projectRootMappings, + }; } diff --git a/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.ts b/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.ts index 475108a6d9679e..09faa14803001d 100644 --- a/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.ts +++ b/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.ts @@ -1,24 +1,24 @@ import * as path from 'path'; +import { join } from 'path'; import { - ProjectGraph, - ProjectGraphDependency, - ProjectGraphProjectNode, - normalizePath, DependencyType, + joinPathFragments, + normalizePath, parseJson, + ProjectGraph, + ProjectGraphDependency, ProjectGraphExternalNode, - joinPathFragments, + ProjectGraphProjectNode, workspaceRoot, } from '@nrwl/devkit'; -import { join } from 'path'; import { getPath, pathExists } from './graph-utils'; import { existsSync } from 'fs'; import { readFileIfExisting } from 'nx/src/project-graph/file-utils'; import { TargetProjectLocator } from 'nx/src/utils/target-project-locator'; - -export type MappedProjectGraph = ProjectGraph & { - allFiles: Record; -}; +import { + findProjectForPath, + ProjectRootMappings, +} from 'nx/src/project-graph/utils/find-project-for-path'; export type Deps = { [projectName: string]: ProjectGraphDependency[] }; export type DepConstraint = { @@ -70,10 +70,6 @@ function hasTag(proj: ProjectGraphProjectNode, tag: string) { return tag === '*' || (proj.data.tags || []).indexOf(tag) > -1; } -export function removeExt(file: string): string { - return file.replace(/(? | undefined { if (!isRelative(imp)) { @@ -115,42 +112,17 @@ export function getTargetProjectBasedOnRelativeImport( projectPath.length + 1 ); - return findTargetProject(projectGraph, targetFile); -} - -export function findProjectUsingFile( - projectGraph: MappedProjectGraph, - file: string -): ProjectGraphProjectNode { - return projectGraph.nodes[projectGraph.allFiles[file]]; + return findProject(projectGraph, projectRootMappings, targetFile); } -export function findSourceProject( - projectGraph: MappedProjectGraph, +export function findProject( + projectGraph: ProjectGraph, + projectRootMappings: ProjectRootMappings, sourceFilePath: string ) { - const targetFile = removeExt(sourceFilePath); - return findProjectUsingFile(projectGraph, targetFile); -} - -export function findTargetProject( - projectGraph: MappedProjectGraph, - targetFile: string -) { - let targetProject = findProjectUsingFile(projectGraph, targetFile); - if (!targetProject) { - targetProject = findProjectUsingFile( - projectGraph, - normalizePath(path.join(targetFile, 'index')) - ); - } - if (!targetProject) { - targetProject = findProjectUsingFile( - projectGraph, - normalizePath(path.join(targetFile, 'src', 'index')) - ); - } - return targetProject; + return projectGraph.nodes[ + findProjectForPath(sourceFilePath, projectRootMappings) + ]; } export function isAbsoluteImportIntoAnotherProject( @@ -166,7 +138,7 @@ export function isAbsoluteImportIntoAnotherProject( } export function findProjectUsingImport( - projectGraph: MappedProjectGraph, + projectGraph: ProjectGraph, targetProjectLocator: TargetProjectLocator, filePath: string, imp: string @@ -353,28 +325,6 @@ export function hasBuildExecutor( ); } -export function mapProjectGraphFiles( - projectGraph: ProjectGraph -): MappedProjectGraph | null { - if (!projectGraph) { - return null; - } - const allFiles: Record = {}; - Object.entries( - projectGraph.nodes as Record - ).forEach(([name, node]) => { - node.data.files.forEach(({ file }) => { - const fileName = removeExt(file); - allFiles[fileName] = name; - }); - }); - - return { - ...projectGraph, - allFiles, - }; -} - const ESLINT_REGEX = /node_modules.*[\/\\]eslint$/; const JEST_REGEX = /node_modules\/.bin\/jest$/; // when we run unit tests in jest const NRWL_CLI_REGEX = /nx[\/\\]bin[\/\\]run-executor\.js$/; diff --git a/packages/nx/src/project-graph/affected/locators/workspace-projects.ts b/packages/nx/src/project-graph/affected/locators/workspace-projects.ts index 27ea2762fa51d7..44aa9ee5041a58 100644 --- a/packages/nx/src/project-graph/affected/locators/workspace-projects.ts +++ b/packages/nx/src/project-graph/affected/locators/workspace-projects.ts @@ -4,8 +4,8 @@ import { NxJsonConfiguration } from '../../../config/nx-json'; import { ProjectGraphProjectNode } from '../../../config/project-graph'; import { createProjectRootMappings, - findMatchingProjectForPath, -} from '../../../utils/target-project-locator'; + findProjectForPath, +} from '../../utils/find-project-for-path'; export const getTouchedProjects: TouchedProjectLocator = ( touchedFiles, @@ -14,7 +14,7 @@ export const getTouchedProjects: TouchedProjectLocator = ( const projectRootMap = createProjectRootMappings(projectGraphNodes); return touchedFiles.reduce((affected, f) => { - const matchingProject = findMatchingProjectForPath(f.file, projectRootMap); + const matchingProject = findProjectForPath(f.file, projectRootMap); if (matchingProject) { affected.push(matchingProject); } diff --git a/packages/nx/src/project-graph/file-map-utils.ts b/packages/nx/src/project-graph/file-map-utils.ts index cd721900781743..0ae505fa3ca1d4 100644 --- a/packages/nx/src/project-graph/file-map-utils.ts +++ b/packages/nx/src/project-graph/file-map-utils.ts @@ -1,57 +1,23 @@ -import { dirname } from 'path'; import { FileData, ProjectFileMap } from '../config/project-graph'; - -function createProjectRootMappings( - workspaceJson: any, - projectFileMap: ProjectFileMap -) { - 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, - file: string -) { - let currentPath = file; - do { - currentPath = dirname(currentPath); - const p = projectRootMappings.get(currentPath); - if (p) { - return p; - } - } while (currentPath != dirname(currentPath)); - - return null; -} +import { + createProjectRootMappingsFromProjectConfigurations, + findProjectForPath, +} from './utils/find-project-for-path'; export function createProjectFileMap( workspaceJson: any, allWorkspaceFiles: FileData[] ): { projectFileMap: ProjectFileMap; allWorkspaceFiles: FileData[] } { const projectFileMap: ProjectFileMap = {}; - const projectRootMappings = createProjectRootMappings( - workspaceJson, - projectFileMap - ); + const projectRootMappings = + createProjectRootMappingsFromProjectConfigurations(workspaceJson.projects); + + for (const projectName of Object.keys(workspaceJson.projects)) { + projectFileMap[projectName] ??= []; + } for (const f of allWorkspaceFiles) { - const matchingProjectFiles = findMatchingProjectFiles( - projectRootMappings, - f.file - ); + const matchingProjectFiles = + projectFileMap[findProjectForPath(f.file, projectRootMappings)]; if (matchingProjectFiles) { matchingProjectFiles.push(f); } @@ -66,16 +32,12 @@ export function updateProjectFileMap( updatedFiles: Map, deletedFiles: string[] ): { projectFileMap: ProjectFileMap; allWorkspaceFiles: FileData[] } { - const projectRootMappings = createProjectRootMappings( - workspaceJson, - projectFileMap - ); + const projectRootMappings = + createProjectRootMappingsFromProjectConfigurations(workspaceJson.projects); for (const f of updatedFiles.keys()) { - const matchingProjectFiles = findMatchingProjectFiles( - projectRootMappings, - f - ); + const matchingProjectFiles = + projectFileMap[findProjectForPath(f, projectRootMappings)] ?? []; if (matchingProjectFiles) { const fileData: FileData = matchingProjectFiles.find((t) => t.file === f); if (fileData) { @@ -100,10 +62,8 @@ export function updateProjectFileMap( } for (const f of deletedFiles) { - const matchingProjectFiles = findMatchingProjectFiles( - projectRootMappings, - f - ); + const matchingProjectFiles = + projectFileMap[findProjectForPath(f, projectRootMappings)] ?? []; if (matchingProjectFiles) { const index = matchingProjectFiles.findIndex((t) => t.file === f); if (index > -1) { diff --git a/packages/nx/src/project-graph/utils/find-project-for-path.spec.ts b/packages/nx/src/project-graph/utils/find-project-for-path.spec.ts new file mode 100644 index 00000000000000..b52879e2ca6d21 --- /dev/null +++ b/packages/nx/src/project-graph/utils/find-project-for-path.spec.ts @@ -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; + 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'); + }); + }); +}); diff --git a/packages/nx/src/project-graph/utils/find-project-for-path.ts b/packages/nx/src/project-graph/utils/find-project-for-path.ts new file mode 100644 index 00000000000000..5b172c5202528a --- /dev/null +++ b/packages/nx/src/project-graph/utils/find-project-for-path.ts @@ -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; + +/** + * 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 +) { + 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 +): ProjectRootMappings { + const projectRootMappings = new Map(); + 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 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; +} diff --git a/packages/nx/src/utils/nx-plugin.ts b/packages/nx/src/utils/nx-plugin.ts index dcd8b66ce1e37e..5d775034302530 100644 --- a/packages/nx/src/utils/nx-plugin.ts +++ b/packages/nx/src/utils/nx-plugin.ts @@ -8,17 +8,19 @@ import { workspaceRoot } from './workspace-root'; import { readJsonFile } from '../utils/fileutils'; import { PackageJson, - readModulePackageJson, readModulePackageJsonWithoutFallbacks, } from './package-json'; import { registerTsProject } from './register'; import { ProjectConfiguration, - TargetConfiguration, ProjectsConfigurations, + TargetConfiguration, } from '../config/workspace-json-project-json'; -import { findMatchingProjectForPath } from './target-project-locator'; import { logger } from './logger'; +import { + createProjectRootMappingsFromProjectConfigurations, + findProjectForPath, +} from '../project-graph/utils/find-project-for-path'; export type ProjectTargetConfigurator = ( file: string @@ -196,12 +198,10 @@ function findNxProjectForImportPath( path.resolve(root, p) ); if (possiblePaths?.length) { - const projectRootMappings = buildProjectRootMap(workspace.projects, root); + const projectRootMappings = + createProjectRootMappingsFromProjectConfigurations(workspace.projects); for (const tsConfigPath of possiblePaths) { - const nxProject = findMatchingProjectForPath( - tsConfigPath, - projectRootMappings - ); + const nxProject = findProjectForPath(tsConfigPath, projectRootMappings); if (nxProject) { return nxProject; } @@ -219,16 +219,6 @@ function findNxProjectForImportPath( } } -function buildProjectRootMap( - projects: Record, - root: string -) { - return Object.entries(projects).reduce((m, [project, config]) => { - m.set(path.resolve(root, config.root), project); - return m; - }, new Map()); -} - let tsconfigPaths: Record; function readTsConfigPaths(root: string = workspaceRoot) { if (!tsconfigPaths) { diff --git a/packages/nx/src/utils/project-graph-utils.spec.ts b/packages/nx/src/utils/project-graph-utils.spec.ts index c46aba480595d7..5a29ca13173cf1 100644 --- a/packages/nx/src/utils/project-graph-utils.spec.ts +++ b/packages/nx/src/utils/project-graph-utils.spec.ts @@ -13,7 +13,6 @@ jest.mock('nx/src/utils/fileutils', () => ({ import { PackageJson } from './package-json'; import { ProjectGraph } from '../config/project-graph'; import { - getProjectNameFromDirPath, getSourceDirOfDependentProjects, mergeNpmScriptsWithTargets, } from './project-graph-utils'; @@ -116,26 +115,6 @@ describe('project graph utils', () => { 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', () => { diff --git a/packages/nx/src/utils/project-graph-utils.ts b/packages/nx/src/utils/project-graph-utils.ts index 90a88709d396c4..a38bda4abf5bc6 100644 --- a/packages/nx/src/utils/project-graph-utils.ts +++ b/packages/nx/src/utils/project-graph-utils.ts @@ -1,8 +1,7 @@ import { buildTargetFromScript, PackageJson } from './package-json'; -import { join, relative } from 'path'; +import { join } from 'path'; import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph'; import { readJsonFile } from './fileutils'; -import { normalizePath } from './path'; import { readCachedProjectGraph } from '../project-graph/project-graph'; 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. * All the external (npm) dependencies will be filtered out diff --git a/packages/nx/src/utils/target-project-locator.spec.ts b/packages/nx/src/utils/target-project-locator.spec.ts index cadf8622bdda81..8ea60158e5d0f9 100644 --- a/packages/nx/src/utils/target-project-locator.spec.ts +++ b/packages/nx/src/utils/target-project-locator.spec.ts @@ -75,6 +75,12 @@ describe('findTargetProjectWithImport', () => { ...nxJson, } as any, fileMap: { + rootProj: [ + { + file: 'index.ts', + hash: 'some-hash', + }, + ], proj: [ { file: 'libs/proj/index.ts', @@ -147,6 +153,14 @@ describe('findTargetProjectWithImport', () => { } as any; projects = { + rootProj: { + name: 'rootProj', + type: 'lib', + data: { + root: '.', + files: [], + }, + }, proj3a: { name: 'proj3a', type: 'lib', @@ -315,11 +329,16 @@ describe('findTargetProjectWithImport', () => { '../proj/../index.ts', 'libs/proj/src/index.ts' ); + const res5 = targetProjectLocator.findProjectWithImport( + '../../../index.ts', + 'libs/proj/src/index.ts' + ); expect(res1).toEqual('proj'); expect(res2).toEqual('proj'); expect(res3).toEqual('proj2'); expect(res4).toEqual('proj'); + expect(res5).toEqual('rootProj'); }); it('should be able to resolve a module by using tsConfig paths', () => { diff --git a/packages/nx/src/utils/target-project-locator.ts b/packages/nx/src/utils/target-project-locator.ts index e5693a196f00a1..42b8dffcdff713 100644 --- a/packages/nx/src/utils/target-project-locator.ts +++ b/packages/nx/src/utils/target-project-locator.ts @@ -6,6 +6,10 @@ import { ProjectGraphExternalNode, ProjectGraphProjectNode, } from '../config/project-graph'; +import { + createProjectRootMappings, + findProjectForPath, +} from '../project-graph/utils/find-project-for-path'; export class TargetProjectLocator { private projectRootMappings = createProjectRootMappings(this.nodes); @@ -174,7 +178,7 @@ export class TargetProjectLocator { } private findMatchingProjectFiles(file: string) { - const project = findMatchingProjectForPath(file, this.projectRootMappings); + const project = findProjectForPath(file, this.projectRootMappings); return this.nodes[project]; } } @@ -197,39 +201,3 @@ function filterRootExternalDependencies( } return nodes; } - -export function createProjectRootMappings( - nodes: Record -) { - const projectRootMappings = new Map(); - 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; -} - -/** - * Locates a project in projectRootMap based on a file within it - * @param filePath path that is inside of projectName - * @param projectRootMap Map - */ -export function findMatchingProjectForPath( - filePath: string, - projectRootMap: Map -): string | null { - for ( - let currentPath = filePath; - currentPath != dirname(currentPath); - currentPath = dirname(currentPath) - ) { - const p = projectRootMap.get(currentPath); - if (p) { - return p; - } - } - return null; -} diff --git a/packages/workspace/src/utilities/generate-globs.ts b/packages/workspace/src/utilities/generate-globs.ts index 775bbf98fb9d67..5892014c1bee5b 100644 --- a/packages/workspace/src/utilities/generate-globs.ts +++ b/packages/workspace/src/utilities/generate-globs.ts @@ -2,12 +2,13 @@ import { joinPathFragments, logger } from '@nrwl/devkit'; import { workspaceRoot } from 'nx/src/utils/workspace-root'; import { dirname, join, relative, resolve } from 'path'; import { readCachedProjectGraph } from 'nx/src/project-graph/project-graph'; -import { - getProjectNameFromDirPath, - getSourceDirOfDependentProjects, -} from 'nx/src/utils/project-graph-utils'; +import { getSourceDirOfDependentProjects } from 'nx/src/utils/project-graph-utils'; import { existsSync, lstatSync, readdirSync, readFileSync } from 'fs'; import ignore, { Ignore } from 'ignore'; +import { + createProjectRootMappings, + findProjectForPath, +} from 'nx/src/project-graph/utils/find-project-for-path'; function configureIgnore() { let ig: Ignore; @@ -31,14 +32,21 @@ export function createGlobPatternsForDependencies( let ig = configureIgnore(); const filenameRelativeToWorkspaceRoot = relative(workspaceRoot, dirPath); const projectGraph = readCachedProjectGraph(); + const projectRootMappings = createProjectRootMappings(projectGraph.nodes); // find the project let projectName; try { - projectName = getProjectNameFromDirPath( + projectName = findProjectForPath( filenameRelativeToWorkspaceRoot, - projectGraph + projectRootMappings ); + + if (!projectName) { + throw new Error( + `Could not find any project containing the file "${filenameRelativeToWorkspaceRoot}" among it's project files` + ); + } } catch (e) { throw new Error( `createGlobPatternsForDependencies: Error when trying to determine main project.\n${e?.message}` diff --git a/packages/workspace/src/utils/ast-utils.ts b/packages/workspace/src/utils/ast-utils.ts index 88db7ea07c00cb..f9316f9a079aff 100644 --- a/packages/workspace/src/utils/ast-utils.ts +++ b/packages/workspace/src/utils/ast-utils.ts @@ -71,7 +71,7 @@ export function sortObjectByKeys(obj: unknown) { }, {}); } -export { findNodes } from '../utilities/typescript/find-nodes'; // TODO(v16): remove this +export { findNodes } from '../utilities/typescript/find-nodes'; export { getSourceNodes } from '../utilities/typescript/get-source-nodes'; export interface Change { diff --git a/packages/workspace/src/utils/rules/rename-package-imports.ts b/packages/workspace/src/utils/rules/rename-package-imports.ts index 73bedcf3fd3361..57405a23334b49 100644 --- a/packages/workspace/src/utils/rules/rename-package-imports.ts +++ b/packages/workspace/src/utils/rules/rename-package-imports.ts @@ -7,8 +7,8 @@ import { } from '@angular-devkit/schematics'; import { getWorkspace } from '../workspace'; import { visitNotIgnoredFiles } from './visit-not-ignored-files'; -import { findNodes } from 'nx/src/utils/typescript'; import { insert, ReplaceChange } from '../ast-utils'; +import { findNodes } from 'nx/src/utils/typescript'; import { normalize } from '@angular-devkit/core'; export interface PackageNameMapping {