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';
@ -74,7 +73,7 @@ ${e.stack ? e.stack : e}`
const buildTarget = getBuildableTarget(ctContext); const buildTarget = getBuildableTarget(ctContext);
if (!buildTarget.project && !graph.nodes?.[buildTarget.project]?.data) { if (!buildTarget.project && !graph.nodes?.[buildTarget.project]?.data) {
throw new Error(stripIndents`Unable to find project configuration for build target. throw new Error(stripIndents`Unable to find project configuration for build target.
Project Name? ${buildTarget.project} Project Name? ${buildTarget.project}
Has project config? ${!!graph.nodes?.[buildTarget.project]?.data}`); Has project config? ${!!graph.nodes?.[buildTarget.project]?.data}`);
} }
@ -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,16 +93,18 @@ 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
) { ) {
throw new Error( throw new Error(
stripIndents`Unable to find the project configuration that includes ${normalizedPathFromWorkspaceRoot}. stripIndents`Unable to find the project configuration that includes ${normalizedPathFromWorkspaceRoot}.
Found project name? ${componentTestingProjectName}. Found project name? ${componentTestingProjectName}.
Graph has data? ${!!graph.nodes[componentTestingProjectName]?.data}` Graph has 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

@ -58,9 +58,9 @@ function reactWebpack5Check(options: CommonNxStorybookConfig) {
It looks like you use Webpack 5 but your Storybook setup is not configured to leverage that It looks like you use Webpack 5 but your Storybook setup is not configured to leverage that
and thus falls back to Webpack 4. and thus falls back to Webpack 4.
Make sure you upgrade your Storybook config to use Webpack 5. Make sure you upgrade your Storybook config to use Webpack 5.
- https://gist.github.com/shilman/8856ea1786dcd247139b47b270912324#upgrade - https://gist.github.com/shilman/8856ea1786dcd247139b47b270912324#upgrade
`); `);
} }
} }

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 {