From 32b8fbf80689905fa1cc309e08f853f20e3751ec Mon Sep 17 00:00:00 2001 From: Tine Kondo Date: Sun, 18 Feb 2024 17:07:35 +0000 Subject: [PATCH] fix: fix Project Graph generation when creating projects in nested folders --- packages/common-jvm/src/lib/utils/utils.ts | 2 +- packages/common/src/lib/workspace/models.ts | 2 +- .../common/src/lib/workspace/project-graph.ts | 122 +++++++++--------- packages/common/src/lib/workspace/utils.ts | 14 +- packages/common/testing/e2e/index.ts | 36 ++---- packages/nx-flutter/src/graph/create-nodes.ts | 3 +- packages/nx-ktor/src/graph/create-nodes.ts | 3 +- .../nx-micronaut/src/graph/create-nodes.ts | 3 +- packages/nx-quarkus/src/graph/create-nodes.ts | 3 +- .../nx-spring-boot/src/graph/create-nodes.ts | 3 +- 10 files changed, 100 insertions(+), 91 deletions(-) diff --git a/packages/common-jvm/src/lib/utils/utils.ts b/packages/common-jvm/src/lib/utils/utils.ts index 5a618c17..5ec84148 100644 --- a/packages/common-jvm/src/lib/utils/utils.ts +++ b/packages/common-jvm/src/lib/utils/utils.ts @@ -1,4 +1,4 @@ -import { createProjectGraphAsync, logger, ProjectGraph, readCachedProjectGraph, Tree } from '@nx/devkit'; +import { createProjectGraphAsync, joinPathFragments, logger, ProjectGraph, readCachedProjectGraph, Tree } from '@nx/devkit'; import { execSync } from 'child_process'; import { fileExists } from '@nx/workspace/src/utilities/fileutils'; diff --git a/packages/common/src/lib/workspace/models.ts b/packages/common/src/lib/workspace/models.ts index 506b5a5b..7ff7aae2 100644 --- a/packages/common/src/lib/workspace/models.ts +++ b/packages/common/src/lib/workspace/models.ts @@ -13,7 +13,7 @@ export interface PackageInfo { */ export interface WorkspacePackageInfoConfiguration { projects: { - [projectName: string]: PackageInfo; + [projectRoot: string]: PackageInfo; }; packages: { diff --git a/packages/common/src/lib/workspace/project-graph.ts b/packages/common/src/lib/workspace/project-graph.ts index 1d71ee18..63c1c259 100644 --- a/packages/common/src/lib/workspace/project-graph.ts +++ b/packages/common/src/lib/workspace/project-graph.ts @@ -15,9 +15,9 @@ import { import { minimatch } from 'minimatch'; import { PackageInfo, WorkspacePackageInfoConfiguration } from './models'; -import { getNameAndRoot } from './utils'; +import { getNameAndRoot, getProjectRootFromFile } from './utils'; -export function getPackageInfosForNxProjects( +function getPackageInfosForNxProjects( pluginName: string, projectFilter: (project: ProjectConfiguration) => boolean, getPackageInfo: (project: ProjectConfiguration) => PackageInfo, @@ -38,7 +38,7 @@ export function getPackageInfosForNxProjects( try { const pkgInfo = getPackageInfo(project); - workspacePackageInfo.projects[projectName] = pkgInfo; + workspacePackageInfo.projects[project.root] = pkgInfo; workspacePackageInfo.packages[pkgInfo.packageId] = projectName; } catch (e) { if (process.env['NX_VERBOSE_LOGGING'] === 'true') { @@ -53,62 +53,49 @@ export function getPackageInfosForNxProjects( return workspacePackageInfo; } -export function addDependenciesForProject( +function getDependenciesForProject( pluginName: string, - rootProjectFolder: string, - rootProjectName: string, - rootPkgInfo: PackageInfo, - builder: ProjectGraphBuilder, - workspace: WorkspacePackageInfoConfiguration -): void { - - const dependencies = getDependenciesForProject(pluginName, rootProjectFolder, rootProjectName, rootPkgInfo, workspace); - for (const dep of dependencies) { - builder.addDependency(dep.source, dep.target, dep.type, dep.source); - } -} - - -export function getDependenciesForProject( - pluginName: string, - rootProjectFolder: string, - rootProjectName: string, - rootPkgInfo: PackageInfo, + filePath: string, + sourceProjectName: string, workspace: WorkspacePackageInfoConfiguration ): RawProjectGraphDependency[] { if (process.env['NX_VERBOSE_LOGGING'] === 'true') { logger.debug( - `[${pluginName}]: Getting dependencies for project '${rootProjectName}'...` + `[${pluginName}]: Getting dependencies for project '${sourceProjectName}'...` ); } const dependencies: RawProjectGraphDependency[] = []; - rootPkgInfo.dependencies?.forEach((depPkgInfo) => { - const depProjectName = workspace.packages[depPkgInfo.packageId]; + const sourceProjectRoot = getProjectRootFromFile(filePath); + const sourcePkgInfo = workspace.projects[sourceProjectRoot]; - if (depProjectName) { + + sourcePkgInfo.dependencies?.forEach((depPkgInfo) => { + const targetProjectName = workspace.packages[depPkgInfo.packageId]; + + if (targetProjectName) { dependencies.push( { - source: rootProjectName, - target: depProjectName, + source: sourceProjectName, + target: targetProjectName, type: DependencyType.static, - sourceFile: joinPathFragments(rootProjectFolder, rootPkgInfo.packageFile), + sourceFile: joinPathFragments(sourceProjectRoot, sourcePkgInfo.packageFile), } ); } }); - rootPkgInfo.modules?.forEach((moduleId) => { + sourcePkgInfo.modules?.forEach((moduleId) => { const depProjectName = workspace.packages[moduleId]; if (depProjectName) { dependencies.push( { - source: rootProjectName, + source: sourceProjectName, target: depProjectName, type: DependencyType.static, - sourceFile: joinPathFragments(rootProjectFolder, rootPkgInfo.packageFile) + sourceFile: joinPathFragments(sourceProjectRoot, sourcePkgInfo.packageFile) } ); } @@ -117,12 +104,14 @@ export function getDependenciesForProject( return dependencies; } +// Project Graph V1 + export function getProjectGraph( pluginName: string, projectFilter: (project: { root: string }) => boolean, getPackageInfo: (project: { root: string }) => PackageInfo, graph: ProjectGraph, - context: ProjectGraphProcessorContext + ctx: ProjectGraphProcessorContext ): ProjectGraph { const builder = new ProjectGraphBuilder(graph); if (process.env['NX_VERBOSE_LOGGING'] === 'true') { @@ -130,28 +119,38 @@ export function getProjectGraph( `[${pluginName}]: Looking related projects inside the workspace...` ); } - const workspace = getPackageInfosForNxProjects( - pluginName, - projectFilter, - getPackageInfo, - context.projectsConfigurations - ); - Object.entries(workspace.projects).forEach(([projectName, pkgInfo]) => { - addDependenciesForProject( - pluginName, - graph.nodes[projectName].data.root, - projectName, - pkgInfo, - builder, - workspace - ); - }); + let workspace = undefined; + let dependencies: RawProjectGraphDependency[] = []; + + for (const source in ctx.filesToProcess) { + const changed = ctx.filesToProcess[source]; + for (const file of changed) { + // we only create the workspace map once and only if changed file is of interest + workspace ??= getPackageInfosForNxProjects( + pluginName, + projectFilter, + getPackageInfo, + ctx.projectsConfigurations + ); + + dependencies = dependencies.concat( + getDependenciesForProject(pluginName, file.file, source, workspace), + ); + } + } + + for (const dep of dependencies) { + builder.addDependency(dep.source, dep.target, dep.type, dep.source); + } + return builder.getUpdatedProjectGraph(); + } // Project Graph V2 -export const createNodesFor = (projectFiles: string[], pluginName: string) => + +export const createNodesFor = (projectFiles: string[], projectFilter: (project: { root: string }) => boolean, pluginName: string) => [ `**/{${projectFiles.join(',')}}` as string, ( @@ -160,6 +159,10 @@ export const createNodesFor = (projectFiles: string[], pluginName: string) => context: CreateNodesContext, ): CreateNodesResult => { + if (!projectFilter({ root: getProjectRootFromFile(file) })) { + return {}; // back off if the file/project does not match the criteria + } + const { root, name } = getNameAndRoot(file); return { @@ -185,12 +188,7 @@ export const createDependenciesIf = (pluginName: string, `[${pluginName}]: Looking related projects inside the workspace...` ); } - const workspace = getPackageInfosForNxProjects( - pluginName, - projectFilter, - getPackageInfo, - { projects: ctx.projects } - ); + let workspace = undefined; let dependencies: RawProjectGraphDependency[] = []; @@ -199,10 +197,16 @@ export const createDependenciesIf = (pluginName: string, const changed = ctx.filesToProcess.projectFileMap[source]; for (const file of changed) { if (minimatch(file.file, projectFilePattern)) { - const { root, name } = getNameAndRoot(file.file); + // we only create the workspace map once and only if changed file is of interest + workspace ??= getPackageInfosForNxProjects( + pluginName, + projectFilter, + getPackageInfo, + { projects: ctx.projects } + ); dependencies = dependencies.concat( - getDependenciesForProject(pluginName, root, name, workspace.projects[name], workspace), + getDependenciesForProject(pluginName, file.file, source, workspace), ); } } diff --git a/packages/common/src/lib/workspace/utils.ts b/packages/common/src/lib/workspace/utils.ts index 3b1840af..d73e5ce3 100644 --- a/packages/common/src/lib/workspace/utils.ts +++ b/packages/common/src/lib/workspace/utils.ts @@ -1,6 +1,6 @@ -import { basename, dirname, join, resolve } from 'path'; +import { basename, dirname, isAbsolute, join, relative, resolve } from 'path'; -import { workspaceRoot } from '@nx/devkit'; +import { normalizePath, workspaceRoot } from '@nx/devkit'; import { readFileSync } from 'fs'; export function getProjectRoot(project: { root: string }) { @@ -38,4 +38,14 @@ export function getCurrentAndParentFolder(cwd: string) { const parentFolder = dirname(cwd); return { currentFolder, parentFolder }; +} + + +export function getProjectRootFromFile(filePath: string){ + + const absoluteFilePath = isAbsolute(filePath) ? filePath : resolve(workspaceRoot, filePath); + + const projectRootFilePath = relative(workspaceRoot, absoluteFilePath); + + return normalizePath(dirname(projectRootFilePath)); } \ No newline at end of file diff --git a/packages/common/testing/e2e/index.ts b/packages/common/testing/e2e/index.ts index f6aa0b8c..6b1f3cd1 100644 --- a/packages/common/testing/e2e/index.ts +++ b/packages/common/testing/e2e/index.ts @@ -11,7 +11,7 @@ export const isWin = process.platform === 'win32'; * Creates a test project with create-nx-workspace and installs the plugin * @returns The directory where the test project was created */ -export function createTestProject(pkgManager='npm', projectName='test-project', workspaceVersion: 'latest' | 'local' = 'local') { +export function createTestProject(pkgManager: PackageManager = 'npm', projectName = 'test-project', workspaceVersion: 'latest' | 'local' = 'local') { const projectDirectory = join(process.cwd(), 'tmp', projectName); // Ensure projectDirectory is empty @@ -23,27 +23,17 @@ export function createTestProject(pkgManager='npm', projectName='test-project', recursive: true, }); - const nxVersion = workspaceVersion === 'local' ? readLocalNxWorkspaceVersion(): 'latest'; - let createCommand; - switch (pkgManager) { - case "pnpm": - createCommand = 'pnpm dlx create-nx-workspace' - break; - case "npm": - case "yarn": - createCommand = 'npx --yes create-nx-workspace'; - break; - default: - throw new Error(`Unsupported package manager: ${pkgManager}`); - } + const nxVersion = workspaceVersion === 'local' ? readLocalNxWorkspaceVersion() : 'latest'; + execSync( - `${createCommand}@${nxVersion} ${projectName} --preset apps --nxCloud=skip --no-interactive --pm ${pkgManager}`, + `${getPackageManagerCommand(pkgManager).dlx} create-nx-workspace@${nxVersion} ${projectName} --preset apps --nxCloud=skip --no-interactive --pm ${pkgManager}`, { cwd: dirname(projectDirectory), stdio: 'inherit', env: process.env, } ); + console.log(`Created test project in "${projectDirectory}"`); return projectDirectory; @@ -53,8 +43,8 @@ export function createTestProject(pkgManager='npm', projectName='test-project', * Creates a test project with create-nx-workspace and installs the plugin * @returns The directory where the test project was created */ -export function createCLITestProject(createPkgName:string, extraArgs = '', createPkgVersion='0.0.0-e2e', pkgManager: PackageManager='npm', projectName = 'test-project') { - +export function createCLITestProject(createPkgName: string, extraArgs = '', createPkgVersion = '0.0.0-e2e', pkgManager: PackageManager = 'npm', projectName = 'test-project') { + const projectDirectory = join(process.cwd(), 'tmp', projectName); // Ensure projectDirectory is empty @@ -66,7 +56,7 @@ export function createCLITestProject(createPkgName:string, extraArgs = '', creat recursive: true, }); - execSync(`${getPackageManagerCommand(pkgManager).exec} --yes ${createPkgName}@${createPkgVersion} ${projectName} ${extraArgs}`, { + execSync(`${getPackageManagerCommand(pkgManager).dlx} ${createPkgName}@${createPkgVersion} ${projectName} ${extraArgs}`, { cwd: dirname(projectDirectory), stdio: 'inherit', env: process.env, @@ -127,7 +117,7 @@ export async function runNxCommandAsync(command: string, pkgManagerExec = 'npx', return runNxCommand(command, pkgManagerExec, opts); } -export function readJson(path: string, options?: JsonParseOptions): T{ +export function readJson(path: string, options?: JsonParseOptions): T { return parseJson(readFile(path), options); } @@ -136,18 +126,18 @@ export function readFile(path: string): string { return readFileSync(filePath, 'utf-8'); } -export function getLatestNxWorkspaceVersion():string{ +export function getLatestNxWorkspaceVersion(): string { return getPackageLatestNpmVersion("nx"); } -export function readLocalNxWorkspaceVersion():string{ +export function readLocalNxWorkspaceVersion(): string { const pkgJsonPath = joinPathFragments(workspaceRoot, 'package.json'); if (!existsSync(pkgJsonPath)) { throw new Error( 'Could not find root package.json to determine dependency versions.' ); } - + return readJsonFile(pkgJsonPath).devDependencies["nx"]; } @@ -155,7 +145,7 @@ export function readLocalNxWorkspaceVersion():string{ * Checks if local Nx version matches latest feature version of Nx * @returns `true` if on same feature version, `false` otherwise */ -export function isLocalNxMatchingLatestFeatureVersion(){ +export function isLocalNxMatchingLatestFeatureVersion() { const localNxVersion = readLocalNxWorkspaceVersion().split('.'); const latestNxVersion = getLatestNxWorkspaceVersion().split('.'); diff --git a/packages/nx-flutter/src/graph/create-nodes.ts b/packages/nx-flutter/src/graph/create-nodes.ts index 015bf02d..0f93162c 100644 --- a/packages/nx-flutter/src/graph/create-nodes.ts +++ b/packages/nx-flutter/src/graph/create-nodes.ts @@ -1,5 +1,6 @@ import { createNodesFor } from "@nxrocks/common"; import { NX_FLUTTER_PKG } from "../index"; +import { isFlutterProject } from "../utils/flutter-utils"; // wrapped into a () to avoid the 'Cannot access 'NX_FLUTTER_PKG' before initialization' -export const createNodesFn = () => createNodesFor(['pubspec.yaml'], NX_FLUTTER_PKG); \ No newline at end of file +export const createNodesFn = () => createNodesFor(['pubspec.yaml'], isFlutterProject, NX_FLUTTER_PKG); \ No newline at end of file diff --git a/packages/nx-ktor/src/graph/create-nodes.ts b/packages/nx-ktor/src/graph/create-nodes.ts index 734f33b7..3ce45f84 100644 --- a/packages/nx-ktor/src/graph/create-nodes.ts +++ b/packages/nx-ktor/src/graph/create-nodes.ts @@ -1,5 +1,6 @@ import { JVM_PROJECT_FILES, createNodesFor } from "@nxrocks/common-jvm"; import { NX_KTOR_PKG } from "../index"; +import { isKtorProject } from "../utils/ktor-utils"; // wrapped into a () to avoid the 'Cannot access 'NX_KTOR_PKG' before initialization' -export const createNodesFn = () => createNodesFor(JVM_PROJECT_FILES, NX_KTOR_PKG); \ No newline at end of file +export const createNodesFn = () => createNodesFor(JVM_PROJECT_FILES, isKtorProject, NX_KTOR_PKG); \ No newline at end of file diff --git a/packages/nx-micronaut/src/graph/create-nodes.ts b/packages/nx-micronaut/src/graph/create-nodes.ts index df7106b4..cc7060a3 100644 --- a/packages/nx-micronaut/src/graph/create-nodes.ts +++ b/packages/nx-micronaut/src/graph/create-nodes.ts @@ -1,5 +1,6 @@ import { JVM_PROJECT_FILES, createNodesFor } from "@nxrocks/common-jvm"; import { NX_MICRONAUT_PKG } from "../index"; +import { isMicronautProject } from "../utils/micronaut-utils"; // wrapped into a () to avoid the 'Cannot access 'NX_MICRONAUT_PKG' before initialization' -export const createNodesFn = () => createNodesFor(JVM_PROJECT_FILES, NX_MICRONAUT_PKG); \ No newline at end of file +export const createNodesFn = () => createNodesFor(JVM_PROJECT_FILES, isMicronautProject, NX_MICRONAUT_PKG); \ No newline at end of file diff --git a/packages/nx-quarkus/src/graph/create-nodes.ts b/packages/nx-quarkus/src/graph/create-nodes.ts index 6474201d..a5a223e6 100644 --- a/packages/nx-quarkus/src/graph/create-nodes.ts +++ b/packages/nx-quarkus/src/graph/create-nodes.ts @@ -1,5 +1,6 @@ import { JVM_PROJECT_FILES, createNodesFor } from "@nxrocks/common-jvm"; import { NX_QUARKUS_PKG } from "../index"; +import { isQuarkusProject } from "../utils/quarkus-utils"; // wrapped into a () to avoid the 'Cannot access 'NX_QUARKUS_PKG' before initialization' -export const createNodesFn = () => createNodesFor(JVM_PROJECT_FILES, NX_QUARKUS_PKG); \ No newline at end of file +export const createNodesFn = () => createNodesFor(JVM_PROJECT_FILES, isQuarkusProject, NX_QUARKUS_PKG); \ No newline at end of file diff --git a/packages/nx-spring-boot/src/graph/create-nodes.ts b/packages/nx-spring-boot/src/graph/create-nodes.ts index 1731c716..047f1088 100644 --- a/packages/nx-spring-boot/src/graph/create-nodes.ts +++ b/packages/nx-spring-boot/src/graph/create-nodes.ts @@ -1,5 +1,6 @@ import { JVM_PROJECT_FILES, createNodesFor } from "@nxrocks/common-jvm"; import { NX_SPRING_BOOT_PKG } from "../index"; +import { isBootProject } from "../utils/boot-utils"; // wrapped into a () to avoid the 'Cannot access 'NX_SPRING_BOOT_PKG' before initialization' -export const createNodesFn = () => createNodesFor(JVM_PROJECT_FILES, NX_SPRING_BOOT_PKG); \ No newline at end of file +export const createNodesFn = () => createNodesFor(JVM_PROJECT_FILES, isBootProject, NX_SPRING_BOOT_PKG); \ No newline at end of file