From c01b5667283a601d2c45c7b0fa00336f02b25154 Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Sat, 9 Mar 2024 11:30:10 -0500 Subject: [PATCH] feat(core): revert running plugins in isolation (#22246) --- docs/generated/devkit/NxPluginV2.md | 2 +- docs/generated/devkit/logger.md | 17 +- .../src/utils/convert-nx-executor.spec.ts | 7 +- packages/nx/bin/post-install.ts | 13 +- packages/nx/plugins/package-json.ts | 2 +- packages/nx/src/adapter/angular-json.ts | 4 +- packages/nx/src/adapter/ngcli-adapter.ts | 2 +- .../command-line/generate/generator-utils.ts | 2 +- .../nx/src/command-line/run/executor-utils.ts | 2 +- packages/nx/src/config/schema-utils.ts | 2 +- packages/nx/src/config/workspaces.spec.ts | 33 +- .../server/handle-request-project-graph.ts | 2 - packages/nx/src/daemon/server/plugins.ts | 26 - ...project-graph-incremental-recomputation.ts | 19 +- .../nx/src/daemon/server/shutdown-utils.ts | 6 - packages/nx/src/devkit-exports.ts | 9 +- .../executors/utils/convert-nx-executor.ts | 8 - .../generators/utils/project-configuration.ts | 7 +- .../update-15-1-0/set-project-names.ts | 6 +- packages/nx/src/plugins/js/index.ts | 2 +- .../nx/src/plugins/js/lock-file/lock-file.ts | 2 +- .../plugins/js/lock-file/npm-parser.spec.ts | 2 +- .../nx/src/plugins/js/lock-file/npm-parser.ts | 2 +- .../plugins/js/lock-file/pnpm-parser.spec.ts | 2 +- .../src/plugins/js/lock-file/pnpm-parser.ts | 2 +- .../plugins/js/lock-file/yarn-parser.spec.ts | 2 +- .../src/plugins/js/lock-file/yarn-parser.ts | 2 +- .../build-dependencies/build-dependencies.ts | 2 +- ...explicit-package-json-dependencies.spec.ts | 2 +- .../explicit-package-json-dependencies.ts | 2 +- .../explicit-project-dependencies.spec.ts | 9 +- .../explicit-project-dependencies.ts | 2 +- .../package-json-workspaces/create-nodes.ts | 64 ++- .../plugins/package-json-workspaces/index.ts | 1 - .../package-json-next-to-project-json.spec.ts | 2 +- .../package-json-next-to-project-json.ts | 4 +- .../build-nodes/project-json.spec.ts | 2 +- .../project-json/build-nodes/project-json.ts | 4 +- .../target-defaults-plugin.spec.ts | 2 +- .../target-defaults/target-defaults-plugin.ts | 4 +- .../locators/project-glob-changes.spec.ts | 2 +- .../affected/locators/project-glob-changes.ts | 12 +- .../src/project-graph/build-project-graph.ts | 41 +- packages/nx/src/project-graph/file-utils.ts | 19 +- .../nx/src/project-graph/plugins/index.ts | 6 - .../src/project-graph/plugins/internal-api.ts | 117 ---- .../nx/src/project-graph/plugins/messaging.ts | 153 ----- .../src/project-graph/plugins/plugin-pool.ts | 247 -------- .../project-graph/plugins/plugin-worker.ts | 122 ---- .../src/project-graph/plugins/public-api.ts | 119 ---- .../nx/src/project-graph/plugins/utils.ts | 61 -- .../src/project-graph/plugins/worker-api.ts | 290 ---------- .../project-graph/project-graph-builder.ts | 2 +- .../nx/src/project-graph/project-graph.ts | 64 +-- .../utils/normalize-project-nodes.ts | 3 +- .../utils/project-configuration-utils.ts | 105 +++- .../utils/retrieve-workspace-files.spec.ts | 18 +- .../utils/retrieve-workspace-files.ts | 76 ++- packages/nx/src/utils/logger.ts | 5 - packages/nx/src/utils/nx-plugin.deprecated.ts | 12 +- packages/nx/src/utils/nx-plugin.ts | 531 ++++++++++++++++++ .../src/utils/plugins/plugin-capabilities.ts | 35 +- 62 files changed, 871 insertions(+), 1454 deletions(-) delete mode 100644 packages/nx/src/daemon/server/plugins.ts delete mode 100644 packages/nx/src/project-graph/plugins/index.ts delete mode 100644 packages/nx/src/project-graph/plugins/internal-api.ts delete mode 100644 packages/nx/src/project-graph/plugins/messaging.ts delete mode 100644 packages/nx/src/project-graph/plugins/plugin-pool.ts delete mode 100644 packages/nx/src/project-graph/plugins/plugin-worker.ts delete mode 100644 packages/nx/src/project-graph/plugins/public-api.ts delete mode 100644 packages/nx/src/project-graph/plugins/utils.ts delete mode 100644 packages/nx/src/project-graph/plugins/worker-api.ts create mode 100644 packages/nx/src/utils/nx-plugin.ts diff --git a/docs/generated/devkit/NxPluginV2.md b/docs/generated/devkit/NxPluginV2.md index 98d4a37fa4c1d..52eb73cf5ffa8 100644 --- a/docs/generated/devkit/NxPluginV2.md +++ b/docs/generated/devkit/NxPluginV2.md @@ -15,5 +15,5 @@ A plugin for Nx which creates nodes and dependencies for the [ProjectGraph](../. | Name | Type | Description | | :-------------------- | :------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | | `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies)\<`TOptions`\> | Provides a function to analyze files to create dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) | -| `createNodes?` | [`CreateNodes`](../../devkit/documents/CreateNodes)\<`TOptions`\> | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } | +| `createNodes?` | [`CreateNodes`](../../devkit/documents/CreateNodes) | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } | | `name` | `string` | - | diff --git a/docs/generated/devkit/logger.md b/docs/generated/devkit/logger.md index c2cf2a082d1eb..6ef004c9e6645 100644 --- a/docs/generated/devkit/logger.md +++ b/docs/generated/devkit/logger.md @@ -4,12 +4,11 @@ #### Type declaration -| Name | Type | -| :-------- | :-------------------------- | -| `debug` | (...`s`: `any`[]) => `void` | -| `error` | (`s`: `any`) => `void` | -| `fatal` | (...`s`: `any`[]) => `void` | -| `info` | (`s`: `any`) => `void` | -| `log` | (...`s`: `any`[]) => `void` | -| `verbose` | (...`s`: `any`[]) => `void` | -| `warn` | (`s`: `any`) => `void` | +| Name | Type | +| :------ | :-------------------------- | +| `debug` | (...`s`: `any`[]) => `void` | +| `error` | (`s`: `any`) => `void` | +| `fatal` | (...`s`: `any`[]) => `void` | +| `info` | (`s`: `any`) => `void` | +| `log` | (...`s`: `any`[]) => `void` | +| `warn` | (`s`: `any`) => `void` | diff --git a/packages/devkit/src/utils/convert-nx-executor.spec.ts b/packages/devkit/src/utils/convert-nx-executor.spec.ts index 8e89f8d701eff..f4240aa0dbd8b 100644 --- a/packages/devkit/src/utils/convert-nx-executor.spec.ts +++ b/packages/devkit/src/utils/convert-nx-executor.spec.ts @@ -1,22 +1,17 @@ -import { requireNx } from '../../nx'; import { convertNxExecutor } from './convert-nx-executor'; -const { workspaceRoot } = requireNx(); - describe('Convert Nx Executor', () => { it('should convertNxExecutor to builder correctly and produce the same output', async () => { // ARRANGE const { schema } = require('@angular-devkit/core'); const { TestingArchitectHost, - // nx-ignore-next-line - } = require('@angular-devkit/architect/testing') as typeof import('@angular-devkit/architect/testing'); + } = require('@angular-devkit/architect/testing'); const { Architect } = require('@angular-devkit/architect'); const registry = new schema.CoreSchemaRegistry(); registry.addPostTransform(schema.transforms.addUndefinedDefaults); const testArchitectHost = new TestingArchitectHost(); - testArchitectHost.workspaceRoot = workspaceRoot; const architect = new Architect(testArchitectHost, registry); const convertedExecutor = convertNxExecutor(echoExecutor); diff --git a/packages/nx/bin/post-install.ts b/packages/nx/bin/post-install.ts index 06dbac66591b0..eb1d0200f9ceb 100644 --- a/packages/nx/bin/post-install.ts +++ b/packages/nx/bin/post-install.ts @@ -11,10 +11,10 @@ import { readNxJson } from '../src/config/nx-json'; import { setupWorkspaceContext } from '../src/utils/workspace-context'; (async () => { - const start = new Date(); try { setupWorkspaceContext(workspaceRoot); if (isMainNxPackage() && fileExists(join(workspaceRoot, 'nx.json'))) { + const b = new Date(); assertSupportedPlatform(); try { @@ -35,18 +35,15 @@ import { setupWorkspaceContext } from '../src/utils/workspace-context'; }); }) ); + if (process.env.NX_VERBOSE_LOGGING === 'true') { + const a = new Date(); + console.log(`Nx postinstall steps took ${a.getTime() - b.getTime()}ms`); + } } } catch (e) { if (process.env.NX_VERBOSE_LOGGING === 'true') { console.log(e); } - } finally { - if (process.env.NX_VERBOSE_LOGGING === 'true') { - const end = new Date(); - console.log( - `Nx postinstall steps took ${end.getTime() - start.getTime()}ms` - ); - } } })(); diff --git a/packages/nx/plugins/package-json.ts b/packages/nx/plugins/package-json.ts index 936ca3e8970a4..9ba453f652994 100644 --- a/packages/nx/plugins/package-json.ts +++ b/packages/nx/plugins/package-json.ts @@ -1,4 +1,4 @@ -import type { NxPluginV2 } from '../src/project-graph/plugins'; +import type { NxPluginV2 } from '../src/utils/nx-plugin'; import { workspaceRoot } from '../src/utils/workspace-root'; import { createNodeFromPackageJson } from '../src/plugins/package-json-workspaces'; diff --git a/packages/nx/src/adapter/angular-json.ts b/packages/nx/src/adapter/angular-json.ts index 585cd66c1e623..b509554da8874 100644 --- a/packages/nx/src/adapter/angular-json.ts +++ b/packages/nx/src/adapter/angular-json.ts @@ -2,7 +2,7 @@ import { existsSync } from 'fs'; import * as path from 'path'; import { readJsonFile } from '../utils/fileutils'; import { ProjectsConfigurations } from '../config/workspace-json-project-json'; -import { NxPluginV2 } from '../project-graph/plugins'; +import { NxPluginV2 } from '../utils/nx-plugin'; export const NX_ANGULAR_JSON_PLUGIN_NAME = 'nx-angular-json-plugin'; @@ -16,8 +16,6 @@ export const NxAngularJsonPlugin: NxPluginV2 = { ], }; -export default NxAngularJsonPlugin; - export function shouldMergeAngularProjects( root: string, includeProjectsFromAngularJson: boolean diff --git a/packages/nx/src/adapter/ngcli-adapter.ts b/packages/nx/src/adapter/ngcli-adapter.ts index a0445d98c9287..1f1d137ad3e28 100644 --- a/packages/nx/src/adapter/ngcli-adapter.ts +++ b/packages/nx/src/adapter/ngcli-adapter.ts @@ -59,7 +59,7 @@ import { ExecutorsJson, TaskGraphExecutor, } from '../config/misc-interfaces'; -import { readPluginPackageJson } from '../project-graph/plugins'; +import { readPluginPackageJson } from '../utils/nx-plugin'; import { getImplementationFactory, resolveImplementation, diff --git a/packages/nx/src/command-line/generate/generator-utils.ts b/packages/nx/src/command-line/generate/generator-utils.ts index 20d57f4eff259..1fffe47dcbb00 100644 --- a/packages/nx/src/command-line/generate/generator-utils.ts +++ b/packages/nx/src/command-line/generate/generator-utils.ts @@ -10,7 +10,7 @@ import { resolveSchema, } from '../../config/schema-utils'; import { readJsonFile } from '../../utils/fileutils'; -import { readPluginPackageJson } from '../../project-graph/plugins'; +import { readPluginPackageJson } from '../../utils/nx-plugin'; export function getGeneratorInformation( collectionName: string, diff --git a/packages/nx/src/command-line/run/executor-utils.ts b/packages/nx/src/command-line/run/executor-utils.ts index ecbac0c0f8bea..e12f7e752cd83 100644 --- a/packages/nx/src/command-line/run/executor-utils.ts +++ b/packages/nx/src/command-line/run/executor-utils.ts @@ -1,6 +1,6 @@ import { dirname, join } from 'path'; -import { readPluginPackageJson } from '../../project-graph/plugins'; +import { readPluginPackageJson } from '../../utils/nx-plugin'; import { CustomHasher, Executor, diff --git a/packages/nx/src/config/schema-utils.ts b/packages/nx/src/config/schema-utils.ts index 30d7d083130f0..6c92129a643d7 100644 --- a/packages/nx/src/config/schema-utils.ts +++ b/packages/nx/src/config/schema-utils.ts @@ -1,6 +1,6 @@ import { existsSync } from 'fs'; import { extname, join } from 'path'; -import { registerPluginTSTranspiler } from '../project-graph/plugins'; +import { registerPluginTSTranspiler } from '../utils/nx-plugin'; /** * This function is used to get the implementation factory of an executor or generator. diff --git a/packages/nx/src/config/workspaces.spec.ts b/packages/nx/src/config/workspaces.spec.ts index 7f1c2f2e42792..81d3d1a183171 100644 --- a/packages/nx/src/config/workspaces.spec.ts +++ b/packages/nx/src/config/workspaces.spec.ts @@ -1,9 +1,22 @@ -import { toProjectName } from './workspaces'; +import { toProjectName, Workspaces } from './workspaces'; import { TempFs } from '../internal-testing-utils/temp-fs'; import { withEnvironmentVariables } from '../internal-testing-utils/with-environment'; import { retrieveProjectConfigurations } from '../project-graph/utils/retrieve-workspace-files'; import { readNxJson } from './configuration'; -import { loadNxPluginsInIsolation } from '../project-graph/plugins/internal-api'; + +const libConfig = (root, name?: string) => ({ + name: name ?? toProjectName(`${root}/some-file`), + projectType: 'library', + root: `libs/${root}`, + sourceRoot: `libs/${root}/src`, + targets: { + 'nx-release-publish': { + dependsOn: ['^nx-release-publish'], + executor: '@nx/js:release-publish', + options: {}, + }, + }, +}); describe('Workspaces', () => { let fs: TempFs; @@ -35,21 +48,9 @@ describe('Workspaces', () => { const { projects } = await withEnvironmentVariables( { - NX_WORKSPACE_ROOT_PATH: fs.tempDir, + NX_WORKSPACE_ROOT: fs.tempDir, }, - async () => { - const [plugins, cleanup] = await loadNxPluginsInIsolation( - readNxJson(fs.tempDir).plugins, - fs.tempDir - ); - const res = retrieveProjectConfigurations( - plugins, - fs.tempDir, - readNxJson(fs.tempDir) - ); - cleanup(); - return res; - } + () => retrieveProjectConfigurations(fs.tempDir, readNxJson(fs.tempDir)) ); expect(projects['my-package']).toEqual({ name: 'my-package', diff --git a/packages/nx/src/daemon/server/handle-request-project-graph.ts b/packages/nx/src/daemon/server/handle-request-project-graph.ts index 1dc4507383c82..449d57a693805 100644 --- a/packages/nx/src/daemon/server/handle-request-project-graph.ts +++ b/packages/nx/src/daemon/server/handle-request-project-graph.ts @@ -3,8 +3,6 @@ import { serializeResult } from '../socket-utils'; import { serverLogger } from './logger'; import { getCachedSerializedProjectGraphPromise } from './project-graph-incremental-recomputation'; import { HandlerResult } from './server'; -import { getPlugins } from './plugins'; -import { readNxJson } from '../../config/nx-json'; export async function handleRequestProjectGraph(): Promise { try { diff --git a/packages/nx/src/daemon/server/plugins.ts b/packages/nx/src/daemon/server/plugins.ts deleted file mode 100644 index ddf57fdcb393c..0000000000000 --- a/packages/nx/src/daemon/server/plugins.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { readNxJson } from '../../config/nx-json'; -import { - RemotePlugin, - loadNxPluginsInIsolation, -} from '../../project-graph/plugins/internal-api'; -import { workspaceRoot } from '../../utils/workspace-root'; - -let loadedPlugins: Promise; -let cleanup: () => void; - -export async function getPlugins() { - if (loadedPlugins) { - return loadedPlugins; - } - const pluginsConfiguration = readNxJson().plugins ?? []; - const [result, cleanupFn] = await loadNxPluginsInIsolation( - pluginsConfiguration, - workspaceRoot - ); - cleanup = cleanupFn; - return result; -} - -export function cleanupPlugins() { - cleanup(); -} diff --git a/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts b/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts index 94a55b5ae2f0e..c1a316de06c0f 100644 --- a/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts +++ b/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts @@ -29,8 +29,6 @@ import { workspaceRoot } from '../../utils/workspace-root'; import { notifyFileWatcherSockets } from './file-watching/file-watcher-sockets'; import { serverLogger } from './logger'; import { NxWorkspaceFilesExternals } from '../../native'; -import { RemotePlugin } from '../../project-graph/plugins/internal-api'; -import { getPlugins } from './plugins'; interface SerializedProjectGraph { error: Error | null; @@ -71,15 +69,14 @@ export async function getCachedSerializedProjectGraphPromise(): Promise 0) { @@ -202,9 +199,7 @@ async function processCollectedUpdatedAndDeletedFiles( } } -async function processFilesAndCreateAndSerializeProjectGraph( - plugins: RemotePlugin[] -): Promise { +async function processFilesAndCreateAndSerializeProjectGraph(): Promise { try { performance.mark('hash-watched-changes-start'); const updatedFiles = [...collectedUpdatedFiles.values()]; @@ -222,9 +217,9 @@ async function processFilesAndCreateAndSerializeProjectGraph( serverLogger.requestLog([...updatedFiles.values()]); serverLogger.requestLog([...deletedFiles]); const nxJson = readNxJson(workspaceRoot); + // Set this globally to allow plugins to know if they are being called from the project graph creation global.NX_GRAPH_CREATION = true; const graphNodes = await retrieveProjectConfigurations( - plugins, workspaceRoot, nxJson ); @@ -234,6 +229,7 @@ async function processFilesAndCreateAndSerializeProjectGraph( deletedFiles ); const g = createAndSerializeProjectGraph(graphNodes); + delete global.NX_GRAPH_CREATION; return g; } catch (err) { @@ -281,8 +277,7 @@ async function createAndSerializeProjectGraph({ allWorkspaceFiles, rustReferences, currentProjectFileMapCache || readFileMapCache(), - true, - await getPlugins() + true ); currentProjectFileMapCache = projectFileMapCache; currentProjectGraph = projectGraph; diff --git a/packages/nx/src/daemon/server/shutdown-utils.ts b/packages/nx/src/daemon/server/shutdown-utils.ts index 2a53e907f699d..22affc1b46a84 100644 --- a/packages/nx/src/daemon/server/shutdown-utils.ts +++ b/packages/nx/src/daemon/server/shutdown-utils.ts @@ -4,26 +4,21 @@ import { serverLogger } from './logger'; import { serializeResult } from '../socket-utils'; import { deleteDaemonJsonProcessCache } from '../cache'; import type { Watcher } from '../../native'; -import { cleanupPlugins } from './plugins'; export const SERVER_INACTIVITY_TIMEOUT_MS = 10800000 as const; // 10800000 ms = 3 hours let watcherInstance: Watcher | undefined; - export function storeWatcherInstance(instance: Watcher) { watcherInstance = instance; } - export function getWatcherInstance() { return watcherInstance; } let outputWatcherInstance: Watcher | undefined; - export function storeOutputWatcherInstance(instance: Watcher) { outputWatcherInstance = instance; } - export function getOutputWatcherInstance() { return outputWatcherInstance; } @@ -40,7 +35,6 @@ export async function handleServerProcessTermination({ try { server.close(); deleteDaemonJsonProcessCache(); - cleanupPlugins(); if (watcherInstance) { await watcherInstance.stop(); diff --git a/packages/nx/src/devkit-exports.ts b/packages/nx/src/devkit-exports.ts index 4680022e19875..5beaa8a1dd0e4 100644 --- a/packages/nx/src/devkit-exports.ts +++ b/packages/nx/src/devkit-exports.ts @@ -47,19 +47,16 @@ export { workspaceLayout } from './config/configuration'; export type { NxPlugin, + NxPluginV1, NxPluginV2, + ProjectTargetConfigurator, CreateNodes, CreateNodesFunction, CreateNodesResult, CreateNodesContext, CreateDependencies, CreateDependenciesContext, -} from './project-graph/plugins'; - -export type { - NxPluginV1, - ProjectTargetConfigurator, -} from './utils/nx-plugin.deprecated'; +} from './utils/nx-plugin'; /** * @category Workspace diff --git a/packages/nx/src/executors/utils/convert-nx-executor.ts b/packages/nx/src/executors/utils/convert-nx-executor.ts index 76d0d9dbd3322..40b2566d91c10 100644 --- a/packages/nx/src/executors/utils/convert-nx-executor.ts +++ b/packages/nx/src/executors/utils/convert-nx-executor.ts @@ -7,7 +7,6 @@ import { readNxJson } from '../../config/nx-json'; import { Executor, ExecutorContext } from '../../config/misc-interfaces'; import { retrieveProjectConfigurations } from '../../project-graph/utils/retrieve-workspace-files'; import { ProjectsConfigurations } from '../../config/workspace-json-project-json'; -import { loadNxPluginsInIsolation } from '../../project-graph/plugins/internal-api'; /** * Convert an Nx Executor into an Angular Devkit Builder @@ -18,22 +17,15 @@ export function convertNxExecutor(executor: Executor) { const builderFunction = (options, builderContext) => { const promise = async () => { const nxJsonConfiguration = readNxJson(builderContext.workspaceRoot); - - const [plugins, cleanup] = await loadNxPluginsInIsolation( - nxJsonConfiguration.plugins, - builderContext.workspaceRoot - ); const projectsConfigurations: ProjectsConfigurations = { version: 2, projects: ( await retrieveProjectConfigurations( - plugins, builderContext.workspaceRoot, nxJsonConfiguration ) ).projects, }; - cleanup(); const context: ExecutorContext = { root: builderContext.workspaceRoot, projectName: builderContext.target.project, diff --git a/packages/nx/src/generators/utils/project-configuration.ts b/packages/nx/src/generators/utils/project-configuration.ts index 1f93b6a07aa75..172f71abcd120 100644 --- a/packages/nx/src/generators/utils/project-configuration.ts +++ b/packages/nx/src/generators/utils/project-configuration.ts @@ -4,7 +4,7 @@ import { basename, join, relative } from 'path'; import { buildProjectConfigurationFromPackageJson, getGlobPatternsFromPackageManagerWorkspaces, - createNodes as packageJsonWorkspacesCreateNodes, + getNxPackageJsonWorkspacesPlugin, } from '../../plugins/package-json-workspaces'; import { buildProjectFromProjectJson, @@ -28,7 +28,6 @@ import { readJson, writeJson } from './json'; import { readNxJson } from './nx-json'; import type { Tree } from '../tree'; -import { NxPlugin } from '../../project-graph/plugins'; export { readNxJson, updateNxJson } from './nx-json'; @@ -201,8 +200,8 @@ function readAndCombineAllProjectConfigurations(tree: Tree): { ), ]; const projectGlobPatterns = configurationGlobs([ - ProjectJsonProjectsPlugin, - { createNodes: packageJsonWorkspacesCreateNodes } as NxPlugin, + { plugin: ProjectJsonProjectsPlugin }, + { plugin: getNxPackageJsonWorkspacesPlugin(tree.root) }, ]); const globbedFiles = globWithWorkspaceContext(tree.root, projectGlobPatterns); const createdFiles = findCreatedProjectFiles(tree, patterns); diff --git a/packages/nx/src/migrations/update-15-1-0/set-project-names.ts b/packages/nx/src/migrations/update-15-1-0/set-project-names.ts index 01ca3b2afde80..24c9e426ac331 100644 --- a/packages/nx/src/migrations/update-15-1-0/set-project-names.ts +++ b/packages/nx/src/migrations/update-15-1-0/set-project-names.ts @@ -4,13 +4,13 @@ import { dirname } from 'path'; import { readJson, writeJson } from '../../generators/utils/json'; import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available'; import { retrieveProjectConfigurationPaths } from '../../project-graph/utils/retrieve-workspace-files'; -import { loadPlugins } from '../../project-graph/plugins/internal-api'; +import { loadNxPlugins } from '../../utils/nx-plugin'; export default async function (tree: Tree) { const nxJson = readNxJson(tree); - const projectFiles = retrieveProjectConfigurationPaths( + const projectFiles = await retrieveProjectConfigurationPaths( tree.root, - (await loadPlugins(nxJson?.plugins ?? [], tree.root)).map((p) => p.plugin) + await loadNxPlugins(nxJson?.plugins) ); const projectJsons = projectFiles.filter((f) => f.endsWith('project.json')); diff --git a/packages/nx/src/plugins/js/index.ts b/packages/nx/src/plugins/js/index.ts index a815762c523dc..466340e1f2b28 100644 --- a/packages/nx/src/plugins/js/index.ts +++ b/packages/nx/src/plugins/js/index.ts @@ -9,7 +9,7 @@ import { CreateDependencies, CreateDependenciesContext, CreateNodes, -} from '../../project-graph/plugins'; +} from '../../utils/nx-plugin'; import { getLockFileDependencies, getLockFileName, diff --git a/packages/nx/src/plugins/js/lock-file/lock-file.ts b/packages/nx/src/plugins/js/lock-file/lock-file.ts index 10f91d44a39af..068dcdb9bc37a 100644 --- a/packages/nx/src/plugins/js/lock-file/lock-file.ts +++ b/packages/nx/src/plugins/js/lock-file/lock-file.ts @@ -37,7 +37,7 @@ import { import { pruneProjectGraph } from './project-graph-pruning'; import { normalizePackageJson } from './utils/package-json'; import { readJsonFile } from '../../../utils/fileutils'; -import { CreateDependenciesContext } from '../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../utils/nx-plugin'; const YARN_LOCK_FILE = 'yarn.lock'; const NPM_LOCK_FILE = 'package-lock.json'; diff --git a/packages/nx/src/plugins/js/lock-file/npm-parser.spec.ts b/packages/nx/src/plugins/js/lock-file/npm-parser.spec.ts index 817e100c023d7..2ebb0dbb3a8b7 100644 --- a/packages/nx/src/plugins/js/lock-file/npm-parser.spec.ts +++ b/packages/nx/src/plugins/js/lock-file/npm-parser.spec.ts @@ -8,7 +8,7 @@ import { pruneProjectGraph } from './project-graph-pruning'; import { vol } from 'memfs'; import { ProjectGraph } from '../../../config/project-graph'; import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder'; -import { CreateDependenciesContext } from '../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../utils/nx-plugin'; jest.mock('fs', () => { const memFs = require('memfs').fs; diff --git a/packages/nx/src/plugins/js/lock-file/npm-parser.ts b/packages/nx/src/plugins/js/lock-file/npm-parser.ts index 00ce0290631a9..c048ce515c44b 100644 --- a/packages/nx/src/plugins/js/lock-file/npm-parser.ts +++ b/packages/nx/src/plugins/js/lock-file/npm-parser.ts @@ -13,7 +13,7 @@ import { ProjectGraphExternalNode, } from '../../../config/project-graph'; import { hashArray } from '../../../hasher/file-hasher'; -import { CreateDependenciesContext } from '../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../utils/nx-plugin'; /** * NPM diff --git a/packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts b/packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts index 52ee056631895..bdbcea297d715 100644 --- a/packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts +++ b/packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts @@ -11,7 +11,7 @@ import { ProjectGraphBuilder, RawProjectGraphDependency, } from '../../../project-graph/project-graph-builder'; -import { CreateDependenciesContext } from '../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../utils/nx-plugin'; jest.mock('fs', () => { const memFs = require('memfs').fs; diff --git a/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts b/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts index 068fddaae4e5e..8f6a6692ccaf3 100644 --- a/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts +++ b/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts @@ -25,7 +25,7 @@ import { ProjectGraphExternalNode, } from '../../../config/project-graph'; import { hashArray } from '../../../hasher/file-hasher'; -import { CreateDependenciesContext } from '../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../utils/nx-plugin'; // we use key => node map to avoid duplicate work when parsing keys let keyMap = new Map(); diff --git a/packages/nx/src/plugins/js/lock-file/yarn-parser.spec.ts b/packages/nx/src/plugins/js/lock-file/yarn-parser.spec.ts index 2e920bb5c12ea..41e83ac85c542 100644 --- a/packages/nx/src/plugins/js/lock-file/yarn-parser.spec.ts +++ b/packages/nx/src/plugins/js/lock-file/yarn-parser.spec.ts @@ -9,7 +9,7 @@ import { vol } from 'memfs'; import { ProjectGraph } from '../../../config/project-graph'; import { PackageJson } from '../../../utils/package-json'; import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder'; -import { CreateDependenciesContext } from '../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../utils/nx-plugin'; jest.mock('fs', () => { const memFs = require('memfs').fs; diff --git a/packages/nx/src/plugins/js/lock-file/yarn-parser.ts b/packages/nx/src/plugins/js/lock-file/yarn-parser.ts index d96f3e5125335..7098220352778 100644 --- a/packages/nx/src/plugins/js/lock-file/yarn-parser.ts +++ b/packages/nx/src/plugins/js/lock-file/yarn-parser.ts @@ -14,7 +14,7 @@ import { } from '../../../config/project-graph'; import { hashArray } from '../../../hasher/file-hasher'; import { sortObjectByKeys } from '../../../utils/object-sort'; -import { CreateDependenciesContext } from '../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../utils/nx-plugin'; /** * Yarn diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/build-dependencies.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/build-dependencies.ts index 87530bc624ca8..09d8da40396e6 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/build-dependencies.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/build-dependencies.ts @@ -1,6 +1,6 @@ import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies'; import { buildExplicitPackageJsonDependencies } from './explicit-package-json-dependencies'; -import { CreateDependenciesContext } from '../../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../../utils/nx-plugin'; import { RawProjectGraphDependency } from '../../../../project-graph/project-graph-builder'; export function buildExplicitDependencies( diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts index bd253f3c7ce91..5cf832fb6f26e 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts @@ -6,7 +6,7 @@ import { buildExplicitPackageJsonDependencies } from './explicit-package-json-de import { ProjectGraphProjectNode } from '../../../../config/project-graph'; import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder'; import { createFileMap } from '../../../../project-graph/file-map-utils'; -import { CreateDependenciesContext } from '../../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../../utils/nx-plugin'; import { getAllFileDataInContext } from '../../../../utils/workspace-context'; describe('explicit package json dependencies', () => { diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts index 3724c4498a4a3..58ba45c8843b8 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts @@ -9,7 +9,7 @@ import { } from '../../../../config/workspace-json-project-json'; import { NxJsonConfiguration } from '../../../../config/nx-json'; import { PackageJson } from '../../../../utils/package-json'; -import { CreateDependenciesContext } from '../../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../../utils/nx-plugin'; import { RawProjectGraphDependency, validateDependency, diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.spec.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.spec.ts index 2c79667bdac9e..0cf2c22dd9d0b 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.spec.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.spec.ts @@ -1,17 +1,15 @@ import { TempFs } from '../../../../internal-testing-utils/temp-fs'; - const tempFs = new TempFs('explicit-project-deps'); import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder'; import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies'; import { + retrieveProjectConfigurationPaths, retrieveProjectConfigurations, retrieveWorkspaceFiles, } from '../../../../project-graph/utils/retrieve-workspace-files'; -import { CreateDependenciesContext } from '../../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../../utils/nx-plugin'; import { setupWorkspaceContext } from '../../../../utils/workspace-context'; -import ProjectJsonProjectsPlugin from '../../../project-json/build-nodes/project-json'; -import { loadNxPluginsInIsolation } from '../../../../project-graph/plugins/internal-api'; // projectName => tsconfig import path const dependencyProjectNamesToImportPaths = { @@ -566,13 +564,10 @@ async function createContext( setupWorkspaceContext(tempFs.tempDir); - const [plugins, cleanup] = await loadNxPluginsInIsolation([], tempFs.tempDir); const { projects, projectRootMap } = await retrieveProjectConfigurations( - plugins, tempFs.tempDir, nxJson ); - cleanup(); const { fileMap } = await retrieveWorkspaceFiles( tempFs.tempDir, diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.ts index cffc478a3a685..948e917fd09b0 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.ts @@ -6,7 +6,7 @@ import { import { join, relative } from 'path'; import { workspaceRoot } from '../../../../utils/workspace-root'; import { normalizePath } from '../../../../utils/path'; -import { CreateDependenciesContext } from '../../../../project-graph/plugins'; +import { CreateDependenciesContext } from '../../../../utils/nx-plugin'; import { RawProjectGraphDependency, validateDependency, diff --git a/packages/nx/src/plugins/package-json-workspaces/create-nodes.ts b/packages/nx/src/plugins/package-json-workspaces/create-nodes.ts index de78d677347a7..d1547b5deb09f 100644 --- a/packages/nx/src/plugins/package-json-workspaces/create-nodes.ts +++ b/packages/nx/src/plugins/package-json-workspaces/create-nodes.ts @@ -8,42 +8,48 @@ import { toProjectName } from '../../config/workspaces'; import { readJsonFile, readYamlFile } from '../../utils/fileutils'; import { combineGlobPatterns } from '../../utils/globs'; import { NX_PREFIX } from '../../utils/logger'; +import { NxPluginV2 } from '../../utils/nx-plugin'; import { output } from '../../utils/output'; import { PackageJson, readTargetsFromPackageJson, } from '../../utils/package-json'; import { joinPathFragments } from '../../utils/path'; -import { workspaceRoot } from '../../utils/workspace-root'; -import { CreateNodes } from '../../project-graph/plugins'; - -const readJson = (f) => readJsonFile(join(workspaceRoot, f)); -const patterns = getGlobPatternsFromPackageManagerWorkspaces( - workspaceRoot, - readJson -); -const negativePatterns = patterns.filter((p) => p.startsWith('!')); -const positivePatterns = patterns.filter((p) => !p.startsWith('!')); -if ( - // There are some negative patterns - negativePatterns.length > 0 && - // No positive patterns - (positivePatterns.length === 0 || - // Or only a single positive pattern that is the default coming from root package - (positivePatterns.length === 1 && positivePatterns[0] === 'package.json')) -) { - positivePatterns.push('**/package.json'); + +export function getNxPackageJsonWorkspacesPlugin(root: string): NxPluginV2 { + const readJson = (f) => readJsonFile(join(root, f)); + const patterns = getGlobPatternsFromPackageManagerWorkspaces(root, readJson); + + // If the user only specified a negative pattern, we should find all package.json + // files and only return those that don't match a negative pattern. + const negativePatterns = patterns.filter((p) => p.startsWith('!')); + let positivePatterns = patterns.filter((p) => !p.startsWith('!')); + + if ( + // There are some negative patterns + negativePatterns.length > 0 && + // No positive patterns + (positivePatterns.length === 0 || + // Or only a single positive pattern that is the default coming from root package + (positivePatterns.length === 1 && positivePatterns[0] === 'package.json')) + ) { + positivePatterns.push('**/package.json'); + } + + return { + name: 'nx/core/package-json-workspaces', + createNodes: [ + combineGlobPatterns(positivePatterns), + (p) => { + if (!negativePatterns.some((negative) => minimatch(p, negative))) { + return createNodeFromPackageJson(p, root); + } + // A negative pattern matched, so we should not create a node for this package.json + return {}; + }, + ], + }; } -export const createNodes: CreateNodes = [ - combineGlobPatterns(positivePatterns), - (p, _, { workspaceRoot }) => { - if (!negativePatterns.some((negative) => minimatch(p, negative))) { - return createNodeFromPackageJson(p, workspaceRoot); - } - // A negative pattern matched, so we should not create a node for this package.json - return {}; - }, -]; export function createNodeFromPackageJson(pkgJsonPath: string, root: string) { const json: PackageJson = readJsonFile(join(root, pkgJsonPath)); diff --git a/packages/nx/src/plugins/package-json-workspaces/index.ts b/packages/nx/src/plugins/package-json-workspaces/index.ts index 7ac34ae661e87..e675dd81f1475 100644 --- a/packages/nx/src/plugins/package-json-workspaces/index.ts +++ b/packages/nx/src/plugins/package-json-workspaces/index.ts @@ -1,2 +1 @@ export * from './create-nodes'; -export const name = 'nx/core/package-json-workspaces'; diff --git a/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.spec.ts b/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.spec.ts index e4b3dd0d81895..688c85af1b493 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.spec.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.spec.ts @@ -3,7 +3,7 @@ import * as memfs from 'memfs'; import '../../../internal-testing-utils/mock-fs'; import { PackageJsonProjectsNextToProjectJsonPlugin } from './package-json-next-to-project-json'; -import { CreateNodesContext } from '../../../project-graph/plugins'; +import { CreateNodesContext } from '../../../utils/nx-plugin'; const { createNodes } = PackageJsonProjectsNextToProjectJsonPlugin; describe('nx project.json plugin', () => { diff --git a/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.ts b/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.ts index 19421aea54b1f..bb241cf307a2b 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.ts @@ -1,6 +1,6 @@ import { dirname, join } from 'path'; import { existsSync } from 'fs'; -import { NxPluginV2 } from '../../../project-graph/plugins'; +import { NxPluginV2 } from '../../../utils/nx-plugin'; import { readJsonFile } from '../../../utils/fileutils'; import { ProjectConfiguration } from '../../../config/workspace-json-project-json'; import { @@ -33,8 +33,6 @@ export const PackageJsonProjectsNextToProjectJsonPlugin: NxPluginV2 = { ], }; -export default PackageJsonProjectsNextToProjectJsonPlugin; - function createProjectFromPackageJsonNextToProjectJson( projectJsonPath: string, workspaceRoot: string diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts index f6baa247e974d..138be521698e9 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts @@ -3,7 +3,7 @@ import * as memfs from 'memfs'; import '../../../internal-testing-utils/mock-fs'; import { ProjectJsonProjectsPlugin } from './project-json'; -import { CreateNodesContext } from '../../../project-graph/plugins'; +import { CreateNodesContext } from '../../../utils/nx-plugin'; const { createNodes } = ProjectJsonProjectsPlugin; describe('nx project.json plugin', () => { diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts index 9dfc44fcbc262..90048a133f8b6 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts @@ -3,7 +3,7 @@ import { dirname, join } from 'node:path'; import { ProjectConfiguration } from '../../../config/workspace-json-project-json'; import { toProjectName } from '../../../config/workspaces'; import { readJsonFile } from '../../../utils/fileutils'; -import { NxPluginV2 } from '../../../project-graph/plugins'; +import { NxPluginV2 } from '../../../utils/nx-plugin'; export const ProjectJsonProjectsPlugin: NxPluginV2 = { name: 'nx/core/project-json', @@ -23,8 +23,6 @@ export const ProjectJsonProjectsPlugin: NxPluginV2 = { ], }; -export default ProjectJsonProjectsPlugin; - export function buildProjectFromProjectJson( json: Partial, path: string diff --git a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts index 2c08278075c4f..80a24485f2fc2 100644 --- a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts +++ b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts @@ -3,7 +3,7 @@ import * as memfs from 'memfs'; import '../../../src/internal-testing-utils/mock-fs'; import { getTargetInfo, TargetDefaultsPlugin } from './target-defaults-plugin'; -import { CreateNodesContext } from '../../project-graph/plugins'; +import { CreateNodesContext } from '../../utils/nx-plugin'; const { createNodes: [, createNodesFn], } = TargetDefaultsPlugin; diff --git a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts index f31c5ec1b8a32..e16c81e9abf6e 100644 --- a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts +++ b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts @@ -8,7 +8,7 @@ import { } from '../../config/workspace-json-project-json'; import { readJsonFile } from '../../utils/fileutils'; import { combineGlobPatterns } from '../../utils/globs'; -import { NxPluginV2 } from '../../project-graph/plugins'; +import { NxPluginV2 } from '../../utils/nx-plugin'; import { PackageJson, readTargetsFromPackageJson, @@ -127,8 +127,6 @@ export const TargetDefaultsPlugin: NxPluginV2 = { ], }; -export default TargetDefaultsPlugin; - function getExecutorToTargetMap( packageJsonTargets: Record, projectJsonTargets: Record diff --git a/packages/nx/src/project-graph/affected/locators/project-glob-changes.spec.ts b/packages/nx/src/project-graph/affected/locators/project-glob-changes.spec.ts index 31578fd73cb10..2c8d3078cc9bc 100644 --- a/packages/nx/src/project-graph/affected/locators/project-glob-changes.spec.ts +++ b/packages/nx/src/project-graph/affected/locators/project-glob-changes.spec.ts @@ -1,7 +1,7 @@ import { ProjectGraphProjectNode } from '../../../config/project-graph'; import { ProjectConfiguration } from '../../../config/workspace-json-project-json'; -import * as nxPlugin from '../../../project-graph/plugins'; +import * as nxPlugin from '../../../utils/nx-plugin'; import { DeletedFileChange } from '../../file-utils'; import { getTouchedProjectsFromProjectGlobChanges } from './project-glob-changes'; diff --git a/packages/nx/src/project-graph/affected/locators/project-glob-changes.ts b/packages/nx/src/project-graph/affected/locators/project-glob-changes.ts index aaabf121c1ff5..afd7c4f8d7db0 100644 --- a/packages/nx/src/project-graph/affected/locators/project-glob-changes.ts +++ b/packages/nx/src/project-graph/affected/locators/project-glob-changes.ts @@ -1,17 +1,23 @@ import { TouchedProjectLocator } from '../affected-project-graph-models'; import { minimatch } from 'minimatch'; import { workspaceRoot } from '../../../utils/workspace-root'; +import { getNxRequirePaths } from '../../../utils/installation-directory'; import { join } from 'path'; import { existsSync } from 'fs'; import { configurationGlobs } from '../../utils/retrieve-workspace-files'; -import { loadPlugins } from '../../plugins/internal-api'; +import { loadNxPlugins } from '../../../utils/nx-plugin'; import { combineGlobPatterns } from '../../../utils/globs'; export const getTouchedProjectsFromProjectGlobChanges: TouchedProjectLocator = async (touchedFiles, projectGraphNodes, nxJson): Promise => { - const plugins = await loadPlugins(nxJson?.plugins ?? [], workspaceRoot); const globPattern = combineGlobPatterns( - configurationGlobs(plugins.map((p) => p.plugin)) + configurationGlobs( + await loadNxPlugins( + nxJson?.plugins, + getNxRequirePaths(workspaceRoot), + workspaceRoot + ) + ) ); const touchedProjects = new Set(); diff --git a/packages/nx/src/project-graph/build-project-graph.ts b/packages/nx/src/project-graph/build-project-graph.ts index 11f7addb46fd7..babf987381234 100644 --- a/packages/nx/src/project-graph/build-project-graph.ts +++ b/packages/nx/src/project-graph/build-project-graph.ts @@ -13,9 +13,12 @@ import { } from './nx-deps-cache'; import { applyImplicitDependencies } from './utils/implicit-project-dependencies'; import { normalizeProjectNodes } from './utils/normalize-project-nodes'; -import { RemotePlugin } from './plugins/internal-api'; -import { isNxPluginV1, isNxPluginV2 } from './plugins/utils'; -import { CreateDependenciesContext } from './plugins'; +import { + CreateDependenciesContext, + isNxPluginV1, + isNxPluginV2, + loadNxPlugins, +} from '../utils/nx-plugin'; import { getRootTsConfigPath } from '../plugins/js/utils/typescript'; import { FileMap, @@ -29,8 +32,9 @@ import { ProjectConfiguration } from '../config/workspace-json-project-json'; import { readNxJson } from '../config/configuration'; import { existsSync } from 'fs'; import { PackageJson } from '../utils/package-json'; +import { getNxRequirePaths } from '../utils/installation-directory'; import { output } from '../utils/output'; -import { NxWorkspaceFilesExternals } from '../native'; +import { ExternalObject, NxWorkspaceFilesExternals } from '../native'; let storedFileMap: FileMap | null = null; let storedAllWorkspaceFiles: FileData[] | null = null; @@ -66,8 +70,7 @@ export async function buildProjectGraphUsingProjectFileMap( allWorkspaceFiles: FileData[], rustReferences: NxWorkspaceFilesExternals, fileMapCache: FileMapCache | null, - shouldWriteCache: boolean, - plugins: RemotePlugin[] + shouldWriteCache: boolean ): Promise<{ projectGraph: ProjectGraph; projectFileMapCache: FileMapCache; @@ -117,8 +120,7 @@ export async function buildProjectGraphUsingProjectFileMap( externalNodes, context, cachedFileData, - projectGraphVersion, - plugins + projectGraphVersion ); const projectFileMapCache = createProjectFileMapCache( nxJson, @@ -164,8 +166,7 @@ async function buildProjectGraphUsingContext( knownExternalNodes: Record, ctx: CreateDependenciesContext, cachedFileData: CachedFileData, - projectGraphVersion: string, - plugins: RemotePlugin[] + projectGraphVersion: string ) { performance.mark('build project graph:start'); @@ -178,7 +179,7 @@ async function buildProjectGraphUsingContext( await normalizeProjectNodes(ctx, builder); const initProjectGraph = builder.getUpdatedProjectGraph(); - const r = await updateProjectGraphWithPlugins(ctx, initProjectGraph, plugins); + const r = await updateProjectGraphWithPlugins(ctx, initProjectGraph); const updatedBuilder = new ProjectGraphBuilder(r, ctx.fileMap.projectFileMap); for (const proj of Object.keys(cachedFileData.projectFileMap)) { @@ -235,11 +236,16 @@ function createContext( async function updateProjectGraphWithPlugins( context: CreateDependenciesContext, - initProjectGraph: ProjectGraph, - plugins: RemotePlugin[] + initProjectGraph: ProjectGraph ) { + const plugins = await loadNxPlugins( + context.nxJsonConfiguration?.plugins, + getNxRequirePaths(), + context.workspaceRoot, + context.projects + ); let graph = initProjectGraph; - for (const plugin of plugins) { + for (const { plugin } of plugins) { try { if ( isNxPluginV1(plugin) && @@ -291,15 +297,14 @@ async function updateProjectGraphWithPlugins( ); const createDependencyPlugins = plugins.filter( - (plugin) => isNxPluginV2(plugin) && plugin.createDependencies + ({ plugin }) => isNxPluginV2(plugin) && plugin.createDependencies ); await Promise.all( - createDependencyPlugins.map(async (plugin) => { + createDependencyPlugins.map(async ({ plugin, options }) => { performance.mark(`${plugin.name}:createDependencies - start`); try { - // TODO: we shouldn't have to pass null here - const dependencies = await plugin.createDependencies(null, { + const dependencies = await plugin.createDependencies(options, { ...context, }); diff --git a/packages/nx/src/project-graph/file-utils.ts b/packages/nx/src/project-graph/file-utils.ts index 86d2aedc35922..c07c9edfe8221 100644 --- a/packages/nx/src/project-graph/file-utils.ts +++ b/packages/nx/src/project-graph/file-utils.ts @@ -27,6 +27,7 @@ import { getDefaultPluginsSync } from '../utils/nx-plugin.deprecated'; import { minimatch } from 'minimatch'; import { CreateNodesResult } from '../devkit-exports'; import { PackageJsonProjectsNextToProjectJsonPlugin } from '../plugins/project-json/build-nodes/package-json-next-to-project-json'; +import { LoadedNxPlugin } from '../utils/nx-plugin'; export interface Change { type: string; @@ -183,9 +184,9 @@ export { readNxJson, workspaceLayout } from '../config/configuration'; function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) { const projectFiles = retrieveProjectConfigurationPaths( root, - getDefaultPluginsSync(root).map((p) => p.plugin) + getDefaultPluginsSync(root) ); - const plugins = [ + const plugins: LoadedNxPlugin[] = [ { plugin: PackageJsonProjectsNextToProjectJsonPlugin }, ...getDefaultPluginsSync(root), ]; @@ -193,21 +194,17 @@ function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) { const projectRootMap: Map = new Map(); // We iterate over plugins first - this ensures that plugins specified first take precedence. - for (const { plugin } of plugins) { + for (const { plugin, options } of plugins) { const [pattern, createNodes] = plugin.createNodes ?? []; if (!pattern) { continue; } for (const file of projectFiles) { if (minimatch(file, pattern, { dot: true })) { - let r = createNodes( - file, - {}, - { - nxJsonConfiguration: nxJson, - workspaceRoot: root, - } - ) as CreateNodesResult; + let r = createNodes(file, options, { + nxJsonConfiguration: nxJson, + workspaceRoot: root, + }) as CreateNodesResult; for (const node in r.projects) { const project = { root: node, diff --git a/packages/nx/src/project-graph/plugins/index.ts b/packages/nx/src/project-graph/plugins/index.ts deleted file mode 100644 index 0c939a25f6db0..0000000000000 --- a/packages/nx/src/project-graph/plugins/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './public-api'; - -export { - readPluginPackageJson, - registerPluginTSTranspiler, -} from './worker-api'; diff --git a/packages/nx/src/project-graph/plugins/internal-api.ts b/packages/nx/src/project-graph/plugins/internal-api.ts deleted file mode 100644 index a6fe1ceaeb7ba..0000000000000 --- a/packages/nx/src/project-graph/plugins/internal-api.ts +++ /dev/null @@ -1,117 +0,0 @@ -// This file contains the bits and bobs of the internal API for loading and interacting with Nx plugins. -// For the public API, used by plugin authors, see `./public-api.ts`. - -import { join } from 'path'; - -import { workspaceRoot } from '../../utils/workspace-root'; -import { PluginConfiguration } from '../../config/nx-json'; -import { NxPluginV1 } from '../../utils/nx-plugin.deprecated'; -import { shouldMergeAngularProjects } from '../../adapter/angular-json'; - -import { loadRemoteNxPlugin } from './plugin-pool'; -import { - CreateNodesContext, - CreateNodesResult, - NxPluginV2, -} from './public-api'; - -export { loadPlugins, loadPlugin } from './worker-api'; - -export type CreateNodesResultWithContext = CreateNodesResult & { - file: string; - pluginName: string; -}; - -export type NormalizedPlugin = NxPluginV2 & - Pick; - -// This represents a plugin loaded in a plugin-worker. This is not an API for plugin authors, -// rather an internal representation of how to interact with a loaded plugin. -export type RemotePlugin = - // A remote plugin is a v2 plugin, with a slightly different API for create nodes. - Omit & { - createNodes: [ - filePattern: string, - // The create nodes function takes all matched files instead of just one, and includes - // the result's context. - fn: ( - matchedFiles: string[], - context: CreateNodesContext - ) => Promise - ]; - }; - -// Short lived cache (cleared between cmd runs) -// holding resolved nx plugin objects. -// Allows loaded plugins to not be reloaded when -// referenced multiple times. -export const nxPluginCache: Map, () => void]> = - new Map(); - -/** - * This loads plugins in isolation in their own worker so that they do not disturb other workers or the main process. - */ -export async function loadNxPluginsInIsolation( - plugins: PluginConfiguration[], - root = workspaceRoot -): Promise<[RemotePlugin[], () => void]> { - const result: Promise[] = []; - - plugins ??= []; - - plugins.unshift( - join( - __dirname, - '../../plugins/project-json/build-nodes/package-json-next-to-project-json' - ) - ); - - // We push the nx core node plugins onto the end, s.t. it overwrites any other plugins - plugins.push(...(await getDefaultPlugins(root))); - - const cleanupFunctions: Array<() => void> = []; - for (const plugin of plugins) { - const [loadedPluginPromise, cleanup] = loadNxPluginInIsolation( - plugin, - root - ); - result.push(loadedPluginPromise); - cleanupFunctions.push(cleanup); - } - - return [ - await Promise.all(result), - () => { - for (const fn of cleanupFunctions) { - fn(); - } - }, - ]; -} - -export function loadNxPluginInIsolation( - plugin: PluginConfiguration, - root = workspaceRoot -): [Promise, () => void] { - const cacheKey = JSON.stringify(plugin); - - if (nxPluginCache.has(cacheKey)) { - return nxPluginCache.get(cacheKey); - } - - const [loadingPlugin, cleanup] = loadRemoteNxPlugin(plugin, root); - nxPluginCache.set(cacheKey, [loadingPlugin, cleanup]); - return [loadingPlugin, cleanup]; -} - -export async function getDefaultPlugins(root: string) { - return [ - join(__dirname, '../../plugins/js'), - join(__dirname, '../../plugins/target-defaults/target-defaults-plugin'), - ...(shouldMergeAngularProjects(root, false) - ? [join(__dirname, '../../adapter/angular-json')] - : []), - join(__dirname, '../../plugins/package-json-workspaces'), - join(__dirname, '../../plugins/project-json/build-nodes/project-json'), - ]; -} diff --git a/packages/nx/src/project-graph/plugins/messaging.ts b/packages/nx/src/project-graph/plugins/messaging.ts deleted file mode 100644 index fca8310bf6dcc..0000000000000 --- a/packages/nx/src/project-graph/plugins/messaging.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { - ProjectGraph, - ProjectGraphProcessorContext, -} from '../../config/project-graph'; -import { PluginConfiguration } from '../../config/nx-json'; -import { CreateDependenciesContext, CreateNodesContext } from './public-api'; -import { RemotePlugin } from './internal-api'; - -export interface PluginWorkerLoadMessage { - type: 'load'; - payload: { - plugin: PluginConfiguration; - root: string; - }; -} - -export interface PluginWorkerLoadResult { - type: 'load-result'; - payload: - | { - name: string; - createNodesPattern: string; - hasCreateDependencies: boolean; - hasProcessProjectGraph: boolean; - success: true; - } - | { - success: false; - error: string; - }; -} - -export interface PluginWorkerCreateNodesMessage { - type: 'createNodes'; - payload: { - configFiles: string[]; - context: CreateNodesContext; - tx: string; - }; -} - -export interface PluginWorkerCreateNodesResult { - type: 'createNodesResult'; - payload: - | { - success: true; - result: Awaited>; - tx: string; - } - | { - success: false; - error: string; - tx: string; - }; -} - -export interface PluginCreateDependenciesMessage { - type: 'createDependencies'; - payload: { - context: CreateDependenciesContext; - tx: string; - }; -} - -export interface PluginCreateDependenciesResult { - type: 'createDependenciesResult'; - payload: - | { - dependencies: ReturnType; - success: true; - tx: string; - } - | { - success: false; - error: string; - tx: string; - }; -} - -export interface PluginWorkerProcessProjectGraphMessage { - type: 'processProjectGraph'; - payload: { - graph: ProjectGraph; - ctx: ProjectGraphProcessorContext; - tx: string; - }; -} - -export interface PluginWorkerProcessProjectGraphResult { - type: 'processProjectGraphResult'; - payload: - | { - graph: ProjectGraph; - success: true; - tx: string; - } - | { - success: false; - error: string; - tx: string; - }; -} - -export type PluginWorkerMessage = - | PluginWorkerLoadMessage - | PluginWorkerCreateNodesMessage - | PluginCreateDependenciesMessage - | PluginWorkerProcessProjectGraphMessage; - -export type PluginWorkerResult = - | PluginWorkerLoadResult - | PluginWorkerCreateNodesResult - | PluginCreateDependenciesResult - | PluginWorkerProcessProjectGraphResult; - -type MaybePromise = T | Promise; - -// The handler can return a message to be sent back to the process from which the message originated -type MessageHandlerReturn = - T extends PluginWorkerResult - ? MaybePromise - : MaybePromise; - -// Takes a message and a map of handlers and calls the appropriate handler -// type safe and requires all handlers to be handled -export async function consumeMessage< - T extends PluginWorkerMessage | PluginWorkerResult ->( - raw: string | T, - handlers: { - [K in T['type']]: ( - // Extract restricts the type of payload to the payload of the message with the type K - payload: Extract['payload'] - ) => MessageHandlerReturn; - } -) { - const message: T = typeof raw === 'string' ? JSON.parse(raw) : raw; - const handler = handlers[message.type]; - if (handler) { - const response = await handler(message.payload); - if (response) { - process.send!(createMessage(response)); - } - } else { - throw new Error(`Unhandled message type: ${message.type}`); - } -} - -export function createMessage( - message: PluginWorkerMessage | PluginWorkerResult -): string { - return JSON.stringify(message); -} diff --git a/packages/nx/src/project-graph/plugins/plugin-pool.ts b/packages/nx/src/project-graph/plugins/plugin-pool.ts deleted file mode 100644 index 376671e48b3d5..0000000000000 --- a/packages/nx/src/project-graph/plugins/plugin-pool.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { ChildProcess, fork } from 'child_process'; -import path = require('path'); - -import { PluginConfiguration } from '../../config/nx-json'; - -// TODO (@AgentEnder): After scoped verbose logging is implemented, re-add verbose logs here. -// import { logger } from '../../utils/logger'; - -import { RemotePlugin, nxPluginCache } from './internal-api'; -import { PluginWorkerResult, consumeMessage, createMessage } from './messaging'; - -const cleanupFunctions = new Set<() => void>(); - -const pluginNames = new Map(); - -interface PendingPromise { - promise: Promise; - resolver: (result: any) => void; - rejector: (err: any) => void; -} - -export function loadRemoteNxPlugin(plugin: PluginConfiguration, root: string) { - // this should only really be true when running unit tests within - // the Nx repo. We still need to start the worker in this case, - // but its typescript. - const isWorkerTypescript = path.extname(__filename) === '.ts'; - const workerPath = path.join(__dirname, 'plugin-worker'); - const worker = fork(workerPath, [], { - stdio: ['ignore', 'inherit', 'inherit', 'ipc'], - env: { - ...process.env, - ...(isWorkerTypescript - ? { - // Ensures that the worker uses the same tsconfig as the main process - TS_NODE_PROJECT: path.join(__dirname, '../../../tsconfig.lib.json'), - } - : {}), - }, - execArgv: [ - ...process.execArgv, - // If the worker is typescript, we need to register ts-node - ...(isWorkerTypescript ? ['-r', 'ts-node/register'] : []), - ], - }); - worker.send(createMessage({ type: 'load', payload: { plugin, root } })); - - // logger.verbose(`[plugin-worker] started worker: ${worker.pid}`); - - const pendingPromises = new Map(); - - const exitHandler = createWorkerExitHandler(worker, pendingPromises); - - const cleanupFunction = () => { - worker.off('exit', exitHandler); - shutdownPluginWorker(worker, pendingPromises); - }; - - cleanupFunctions.add(cleanupFunction); - - return [ - new Promise((res, rej) => { - worker.on( - 'message', - createWorkerHandler(worker, pendingPromises, res, rej) - ); - worker.on('exit', exitHandler); - }), - () => { - cleanupFunction(); - cleanupFunctions.delete(cleanupFunction); - }, - ] as const; -} - -async function shutdownPluginWorker( - worker: ChildProcess, - pendingPromises: Map -) { - // Clears the plugin cache so no refs to the workers are held - nxPluginCache.clear(); - - // logger.verbose(`[plugin-pool] starting worker shutdown`); - - // Other things may be interacting with the worker. - // Wait for all pending promises to be done before killing the worker - await Promise.all( - Array.from(pendingPromises.values()).map(({ promise }) => promise) - ); - - worker.kill('SIGINT'); -} - -/** - * Creates a message handler for the given worker. - * @param worker Instance of plugin-worker - * @param pending Set of pending promises - * @param onload Resolver for RemotePlugin promise - * @param onloadError Rejecter for RemotePlugin promise - * @returns Function to handle messages from the worker - */ -function createWorkerHandler( - worker: ChildProcess, - pending: Map, - onload: (plugin: RemotePlugin) => void, - onloadError: (err?: unknown) => void -) { - let pluginName: string; - - return function (message: string) { - const parsed = JSON.parse(message); - // logger.verbose( - // `[plugin-pool] received message: ${parsed.type} from ${ - // pluginName ?? worker.pid - // }` - // ); - consumeMessage(parsed, { - 'load-result': (result) => { - if (result.success) { - const { name, createNodesPattern } = result; - pluginName = name; - pluginNames.set(worker, pluginName); - onload({ - name, - createNodes: createNodesPattern - ? [ - createNodesPattern, - (configFiles, ctx) => { - const tx = pluginName + ':createNodes:' + performance.now(); - return registerPendingPromise(tx, pending, () => { - worker.send( - createMessage({ - type: 'createNodes', - payload: { configFiles, context: ctx, tx }, - }) - ); - }); - }, - ] - : undefined, - createDependencies: result.hasCreateDependencies - ? (opts, ctx) => { - const tx = - pluginName + ':createDependencies:' + performance.now(); - return registerPendingPromise(tx, pending, () => { - worker.send( - createMessage({ - type: 'createDependencies', - payload: { context: ctx, tx }, - }) - ); - }); - } - : undefined, - processProjectGraph: result.hasProcessProjectGraph - ? (graph, ctx) => { - const tx = - pluginName + ':processProjectGraph:' + performance.now(); - return registerPendingPromise(tx, pending, () => { - worker.send( - createMessage({ - type: 'processProjectGraph', - payload: { graph, ctx, tx }, - }) - ); - }); - } - : undefined, - }); - } else if (result.success === false) { - onloadError(result.error); - } - }, - createDependenciesResult: ({ tx, ...result }) => { - const { resolver, rejector } = pending.get(tx); - if (result.success) { - resolver(result.dependencies); - } else if (result.success === false) { - rejector(result.error); - } - }, - createNodesResult: ({ tx, ...result }) => { - const { resolver, rejector } = pending.get(tx); - if (result.success) { - resolver(result.result); - } else if (result.success === false) { - rejector(result.error); - } - }, - processProjectGraphResult: ({ tx, ...result }) => { - const { resolver, rejector } = pending.get(tx); - if (result.success) { - resolver(result.graph); - } else if (result.success === false) { - rejector(result.error); - } - }, - }); - }; -} - -function createWorkerExitHandler( - worker: ChildProcess, - pendingPromises: Map -) { - return () => { - for (const [_, pendingPromise] of pendingPromises) { - pendingPromise.rejector( - new Error( - `Plugin worker ${ - pluginNames.get(worker) ?? worker.pid - } exited unexpectedly with code ${worker.exitCode}` - ) - ); - } - }; -} - -process.on('exit', () => { - for (const fn of cleanupFunctions) { - fn(); - } -}); - -function registerPendingPromise( - tx: string, - pending: Map, - callback: () => void -): Promise { - let resolver, rejector; - - const promise = new Promise((res, rej) => { - resolver = res; - rejector = rej; - - callback(); - }).finally(() => { - pending.delete(tx); - }); - - pending.set(tx, { - promise, - resolver, - rejector, - }); - - return promise; -} diff --git a/packages/nx/src/project-graph/plugins/plugin-worker.ts b/packages/nx/src/project-graph/plugins/plugin-worker.ts deleted file mode 100644 index 268a35a071eee..0000000000000 --- a/packages/nx/src/project-graph/plugins/plugin-worker.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { consumeMessage, PluginWorkerMessage } from './messaging'; -import { CreateNodesResultWithContext, NormalizedPlugin } from './internal-api'; -import { CreateNodesContext } from './public-api'; -import { CreateNodesError } from './utils'; -import { loadPlugin } from './worker-api'; - -global.NX_GRAPH_CREATION = true; - -let plugin: NormalizedPlugin; -let pluginOptions: unknown; - -process.on('message', async (message: string) => { - consumeMessage(message, { - load: async ({ plugin: pluginConfiguration, root }) => { - process.chdir(root); - try { - ({ plugin, options: pluginOptions } = await loadPlugin( - pluginConfiguration, - root - )); - return { - type: 'load-result', - payload: { - name: plugin.name, - createNodesPattern: plugin.createNodes?.[0], - hasCreateDependencies: - 'createDependencies' in plugin && !!plugin.createDependencies, - hasProcessProjectGraph: - 'processProjectGraph' in plugin && !!plugin.processProjectGraph, - success: true, - }, - }; - } catch (e) { - return { - type: 'load-result', - payload: { - success: false, - error: `Could not load plugin ${plugin} \n ${ - e instanceof Error ? e.stack : '' - }`, - }, - }; - } - }, - createNodes: async ({ configFiles, context, tx }) => { - try { - const result = await runCreateNodesInParallel(configFiles, context); - return { - type: 'createNodesResult', - payload: { result, success: true, tx }, - }; - } catch (e) { - return { - type: 'createNodesResult', - payload: { success: false, error: e.stack, tx }, - }; - } - }, - createDependencies: async ({ context, tx }) => { - try { - const result = await plugin.createDependencies(pluginOptions, context); - return { - type: 'createDependenciesResult', - payload: { dependencies: result, success: true, tx }, - }; - } catch (e) { - return { - type: 'createDependenciesResult', - payload: { success: false, error: e.stack, tx }, - }; - } - }, - processProjectGraph: async ({ graph, ctx, tx }) => { - try { - const result = await plugin.processProjectGraph(graph, ctx); - return { - type: 'processProjectGraphResult', - payload: { graph: result, success: true, tx }, - }; - } catch (e) { - return { - type: 'processProjectGraphResult', - payload: { success: false, error: e.stack, tx }, - }; - } - }, - }); -}); - -function runCreateNodesInParallel( - configFiles: string[], - context: CreateNodesContext -): Promise { - const promises: Array< - CreateNodesResultWithContext | Promise - > = configFiles.map((file) => { - performance.mark(`${plugin.name}:createNodes:${file} - start`); - // Result is either static or a promise, using Promise.resolve lets us - // handle both cases with same logic - const value = Promise.resolve( - plugin.createNodes[1](file, pluginOptions, context) - ); - return value - .catch((e) => { - performance.mark(`${plugin.name}:createNodes:${file} - end`); - throw new CreateNodesError( - `Unable to create nodes for ${file} using plugin ${plugin.name}.`, - e - ); - }) - .then((r) => { - performance.mark(`${plugin.name}:createNodes:${file} - end`); - performance.measure( - `${plugin.name}:createNodes:${file}`, - `${plugin.name}:createNodes:${file} - start`, - `${plugin.name}:createNodes:${file} - end` - ); - return { ...r, pluginName: plugin.name, file }; - }); - }); - return Promise.all(promises); -} diff --git a/packages/nx/src/project-graph/plugins/public-api.ts b/packages/nx/src/project-graph/plugins/public-api.ts deleted file mode 100644 index a1bcd6b25a4fa..0000000000000 --- a/packages/nx/src/project-graph/plugins/public-api.ts +++ /dev/null @@ -1,119 +0,0 @@ -// This file represents the public API for plugins which live in nx.json's plugins array. -// For methods to interact with plugins from within Nx, see `./internal-api.ts`. - -import { NxPluginV1 } from '../../utils/nx-plugin.deprecated'; -import { - FileMap, - ProjectGraph, - ProjectGraphExternalNode, -} from '../../config/project-graph'; - -import { ProjectConfiguration } from '../../config/workspace-json-project-json'; - -import { NxJsonConfiguration } from '../../config/nx-json'; -import { RawProjectGraphDependency } from '../project-graph-builder'; - -/** - * Context for {@link CreateNodesFunction} - */ -export interface CreateNodesContext { - readonly nxJsonConfiguration: NxJsonConfiguration; - readonly workspaceRoot: string; -} - -/** - * A function which parses a configuration file into a set of nodes. - * Used for creating nodes for the {@link ProjectGraph} - */ -export type CreateNodesFunction = ( - projectConfigurationFile: string, - options: T | undefined, - context: CreateNodesContext -) => CreateNodesResult | Promise; - -export type Optional = Omit & Partial>; - -export interface CreateNodesResult { - /** - * A map of project root -> project configuration - */ - projects?: Record>; - - /** - * A map of external node name -> external node. External nodes do not have a root, so the key is their name. - */ - externalNodes?: Record; -} - -/** - * A pair of file patterns and {@link CreateNodesFunction} - */ -export type CreateNodes = readonly [ - projectFilePattern: string, - createNodesFunction: CreateNodesFunction -]; - -/** - * Context for {@link CreateDependencies} - */ -export interface CreateDependenciesContext { - /** - * The external nodes that have been added to the graph. - */ - readonly externalNodes: ProjectGraph['externalNodes']; - - /** - * The configuration of each project in the workspace. - */ - readonly projects: Record; - - /** - * The `nx.json` configuration from the workspace - */ - readonly nxJsonConfiguration: NxJsonConfiguration; - - /** - * All files in the workspace - */ - readonly fileMap: FileMap; - - /** - * Files changes since last invocation - */ - readonly filesToProcess: FileMap; - - readonly workspaceRoot: string; -} - -/** - * A function which parses files in the workspace to create dependencies in the {@link ProjectGraph} - * Use {@link validateDependency} to validate dependencies - */ -export type CreateDependencies = ( - options: T | undefined, - context: CreateDependenciesContext -) => RawProjectGraphDependency[] | Promise; - -/** - * A plugin for Nx which creates nodes and dependencies for the {@link ProjectGraph} - */ -export type NxPluginV2 = { - name: string; - - /** - * Provides a file pattern and function that retrieves configuration info from - * those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile } - */ - createNodes?: CreateNodes; - - // Todo(@AgentEnder): This shouldn't be a full processor, since its only responsible for defining edges between projects. What do we want the API to be? - /** - * Provides a function to analyze files to create dependencies for the {@link ProjectGraph} - */ - createDependencies?: CreateDependencies; -}; - -/** - * A plugin for Nx - */ -export type NxPlugin = NxPluginV1 | NxPluginV2; diff --git a/packages/nx/src/project-graph/plugins/utils.ts b/packages/nx/src/project-graph/plugins/utils.ts deleted file mode 100644 index 4d5b839d658cc..0000000000000 --- a/packages/nx/src/project-graph/plugins/utils.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { dirname } from 'node:path'; - -import { toProjectName } from '../../config/workspaces'; -import { combineGlobPatterns } from '../../utils/globs'; - -import type { NxPluginV1 } from '../../utils/nx-plugin.deprecated'; -import type { NormalizedPlugin, RemotePlugin } from './internal-api'; -import type { NxPlugin, NxPluginV2 } from './public-api'; - -export function isNxPluginV2(plugin: NxPlugin): plugin is NxPluginV2 { - return 'createNodes' in plugin || 'createDependencies' in plugin; -} - -export function isNxPluginV1( - plugin: NxPlugin | RemotePlugin -): plugin is NxPluginV1 { - return 'processProjectGraph' in plugin || 'projectFilePatterns' in plugin; -} - -export function normalizeNxPlugin(plugin: NxPlugin): NormalizedPlugin { - if (isNxPluginV2(plugin)) { - return plugin; - } - if (isNxPluginV1(plugin) && plugin.projectFilePatterns) { - return { - ...plugin, - createNodes: [ - `*/**/${combineGlobPatterns(plugin.projectFilePatterns)}`, - (configFilePath) => { - const root = dirname(configFilePath); - return { - projects: { - [root]: { - name: toProjectName(configFilePath), - targets: plugin.registerProjectTargets?.(configFilePath), - }, - }, - }; - }, - ], - }; - } - return plugin; -} - -export class CreateNodesError extends Error { - constructor(msg, cause: Error | unknown) { - const message = `${msg} ${ - !cause - ? '' - : cause instanceof Error - ? `\n\n\t Inner Error: ${cause.stack}` - : cause - }`; - // These errors are thrown during a JS callback which is invoked via rust. - // The errors messaging gets lost in the rust -> js -> rust transition, but - // logging the error here will ensure that it is visible in the console. - console.error(message); - super(message, { cause }); - } -} diff --git a/packages/nx/src/project-graph/plugins/worker-api.ts b/packages/nx/src/project-graph/plugins/worker-api.ts deleted file mode 100644 index 8b2750a0d9aa0..0000000000000 --- a/packages/nx/src/project-graph/plugins/worker-api.ts +++ /dev/null @@ -1,290 +0,0 @@ -// This file contains methods and utilities that should **only** be used by the plugin worker. - -import { ProjectConfiguration } from '../../config/workspace-json-project-json'; - -import { join } from 'node:path/posix'; -import { getNxRequirePaths } from '../../utils/installation-directory'; -import { - PackageJson, - readModulePackageJsonWithoutFallbacks, -} from '../../utils/package-json'; -import { readJsonFile } from '../../utils/fileutils'; -import { workspaceRoot } from '../../utils/workspace-root'; -import { existsSync } from 'node:fs'; -import { readTsConfig } from '../../utils/typescript'; -import { - registerTranspiler, - registerTsConfigPaths, -} from '../../plugins/js/utils/register'; -import { - createProjectRootMappingsFromProjectConfigurations, - findProjectForPath, -} from '../utils/find-project-for-path'; -import { normalizePath } from '../../utils/path'; -import { logger } from '../../utils/logger'; - -import type * as ts from 'typescript'; -import { extname } from 'node:path'; -import { NxPlugin } from './public-api'; -import path = require('node:path/posix'); -import { PluginConfiguration } from '../../config/nx-json'; -import { retrieveProjectConfigurationsWithoutPluginInference } from '../utils/retrieve-workspace-files'; -import { normalizeNxPlugin } from './utils'; - -export function readPluginPackageJson( - pluginName: string, - projects: Record, - paths = getNxRequirePaths() -): { - path: string; - json: PackageJson; -} { - try { - const result = readModulePackageJsonWithoutFallbacks(pluginName, paths); - return { - json: result.packageJson, - path: result.path, - }; - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - const localPluginPath = resolveLocalNxPlugin(pluginName, projects); - if (localPluginPath) { - const localPluginPackageJson = path.join( - localPluginPath.path, - 'package.json' - ); - return { - path: localPluginPackageJson, - json: readJsonFile(localPluginPackageJson), - }; - } - } - throw e; - } -} - -export function resolveLocalNxPlugin( - importPath: string, - projects: Record, - root = workspaceRoot -): { path: string; projectConfig: ProjectConfiguration } | null { - return lookupLocalPlugin(importPath, projects, root); -} - -/** - * Register swc-node or ts-node if they are not currently registered - * with some default settings which work well for Nx plugins. - */ -export function registerPluginTSTranspiler() { - // Get the first tsconfig that matches the allowed set - const tsConfigName = [ - join(workspaceRoot, 'tsconfig.base.json'), - join(workspaceRoot, 'tsconfig.json'), - ].find((x) => existsSync(x)); - - const tsConfig: Partial = tsConfigName - ? readTsConfig(tsConfigName) - : {}; - - registerTsConfigPaths(tsConfigName); - registerTranspiler({ - experimentalDecorators: true, - emitDecoratorMetadata: true, - ...tsConfig.options, - }); -} - -function lookupLocalPlugin( - importPath: string, - projects: Record, - root = workspaceRoot -) { - const plugin = findNxProjectForImportPath(importPath, projects, root); - if (!plugin) { - return null; - } - - const projectConfig: ProjectConfiguration = projects[plugin]; - return { path: path.join(root, projectConfig.root), projectConfig }; -} - -function findNxProjectForImportPath( - importPath: string, - projects: Record, - root = workspaceRoot -): string | null { - const tsConfigPaths: Record = readTsConfigPaths(root); - const possiblePaths = tsConfigPaths[importPath]?.map((p) => - normalizePath(path.relative(root, path.join(root, p))) - ); - if (possiblePaths?.length) { - const projectRootMappings = - createProjectRootMappingsFromProjectConfigurations(projects); - for (const tsConfigPath of possiblePaths) { - const nxProject = findProjectForPath(tsConfigPath, projectRootMappings); - if (nxProject) { - return nxProject; - } - } - logger.verbose( - 'Unable to find local plugin', - possiblePaths, - projectRootMappings - ); - throw new Error( - 'Unable to resolve local plugin with import path ' + importPath - ); - } -} - -let tsconfigPaths: Record; - -function readTsConfigPaths(root: string = workspaceRoot) { - if (!tsconfigPaths) { - const tsconfigPath: string | null = ['tsconfig.base.json', 'tsconfig.json'] - .map((x) => path.join(root, x)) - .filter((x) => existsSync(x))[0]; - if (!tsconfigPath) { - throw new Error('unable to find tsconfig.base.json or tsconfig.json'); - } - const { compilerOptions } = readJsonFile(tsconfigPath); - tsconfigPaths = compilerOptions?.paths; - } - return tsconfigPaths ?? {}; -} - -function readPluginMainFromProjectConfiguration( - plugin: ProjectConfiguration -): string | null { - const { main } = - Object.values(plugin.targets).find((x) => - [ - '@nx/js:tsc', - '@nrwl/js:tsc', - '@nx/js:swc', - '@nrwl/js:swc', - '@nx/node:package', - '@nrwl/node:package', - ].includes(x.executor) - )?.options || - plugin.targets?.build?.options || - {}; - return main; -} - -export function getPluginPathAndName( - moduleName: string, - paths: string[], - projects: Record, - root: string -) { - let pluginPath: string; - let registerTSTranspiler = false; - try { - pluginPath = require.resolve(moduleName, { - paths, - }); - const extension = path.extname(pluginPath); - registerTSTranspiler = extension === '.ts'; - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - const plugin = resolveLocalNxPlugin(moduleName, projects, root); - if (plugin) { - registerTSTranspiler = true; - const main = readPluginMainFromProjectConfiguration( - plugin.projectConfig - ); - pluginPath = main ? path.join(root, main) : plugin.path; - } else { - logger.error(`Plugin listed in \`nx.json\` not found: ${moduleName}`); - throw e; - } - } else { - throw e; - } - } - const packageJsonPath = path.join(pluginPath, 'package.json'); - - // Register the ts-transpiler if we are pointing to a - // plain ts file that's not part of a plugin project - if (registerTSTranspiler) { - registerPluginTSTranspiler(); - } - - const { name } = - !['.ts', '.js'].some((x) => extname(moduleName) === x) && // Not trying to point to a ts or js file - existsSync(packageJsonPath) // plugin has a package.json - ? readJsonFile(packageJsonPath) // read name from package.json - : { name: moduleName }; - return { pluginPath, name }; -} - -let projectsWithoutInference: Record; - -export async function loadPlugins( - plugins: PluginConfiguration[], - root: string -): Promise { - return await Promise.all(plugins.map((p) => loadPlugin(p, root))); -} - -export async function loadPlugin(plugin: PluginConfiguration, root: string) { - try { - require.resolve(typeof plugin === 'string' ? plugin : plugin.plugin); - } catch { - // If a plugin cannot be resolved, we will need projects to resolve it - projectsWithoutInference ??= - await retrieveProjectConfigurationsWithoutPluginInference(root); - } - return await loadNxPluginAsync( - plugin, - getNxRequirePaths(root), - projectsWithoutInference, - root - ); -} - -export type LoadedNxPlugin = { - plugin: NxPlugin; - options?: unknown; -}; - -export async function loadNxPluginAsync( - pluginConfiguration: PluginConfiguration, - paths: string[], - projects: Record, - root: string -): Promise { - const { plugin: moduleName, options } = - typeof pluginConfiguration === 'object' - ? pluginConfiguration - : { plugin: pluginConfiguration, options: undefined }; - - performance.mark(`Load Nx Plugin: ${moduleName} - start`); - let { pluginPath, name } = await getPluginPathAndName( - moduleName, - paths, - projects, - root - ); - const plugin = normalizeNxPlugin(await importPluginModule(pluginPath)); - plugin.name ??= name; - performance.mark(`Load Nx Plugin: ${moduleName} - end`); - performance.measure( - `Load Nx Plugin: ${moduleName}`, - `Load Nx Plugin: ${moduleName} - start`, - `Load Nx Plugin: ${moduleName} - end` - ); - return { plugin, options }; -} - -async function importPluginModule(pluginPath: string): Promise { - const m = await import(pluginPath); - if ( - m.default && - ('createNodes' in m.default || 'createDependencies' in m.default) - ) { - return m.default; - } - return m; -} diff --git a/packages/nx/src/project-graph/project-graph-builder.ts b/packages/nx/src/project-graph/project-graph-builder.ts index d136c4f1064ba..39978d05ceeeb 100644 --- a/packages/nx/src/project-graph/project-graph-builder.ts +++ b/packages/nx/src/project-graph/project-graph-builder.ts @@ -15,7 +15,7 @@ import { ProjectGraphProjectNode, } from '../config/project-graph'; import { ProjectConfiguration } from '../config/workspace-json-project-json'; -import { CreateDependenciesContext } from './plugins'; +import { CreateDependenciesContext } from '../utils/nx-plugin'; import { getFileMap } from './build-project-graph'; /** diff --git a/packages/nx/src/project-graph/project-graph.ts b/packages/nx/src/project-graph/project-graph.ts index 0ff72b3c3a659..7da1ded77ccca 100644 --- a/packages/nx/src/project-graph/project-graph.ts +++ b/packages/nx/src/project-graph/project-graph.ts @@ -17,7 +17,7 @@ import { retrieveWorkspaceFiles, } from './utils/retrieve-workspace-files'; import { readNxJson } from '../config/nx-json'; -import { loadNxPluginsInIsolation, RemotePlugin } from './plugins/internal-api'; +import { unregisterPluginTSTranspiler } from '../utils/nx-plugin'; /** * Synchronously reads the latest cached copy of the workspace's ProjectGraph. @@ -78,41 +78,39 @@ export function readProjectsConfigurationFromProjectGraph( } export async function buildProjectGraphAndSourceMapsWithoutDaemon() { + // Set this globally to allow plugins to know if they are being called from the project graph creation global.NX_GRAPH_CREATION = true; const nxJson = readNxJson(); - const [plugins, cleanup] = await loadNxPluginsInIsolation(nxJson.plugins); - try { - performance.mark('retrieve-project-configurations:start'); - const { projects, externalNodes, sourceMaps, projectRootMap } = - await retrieveProjectConfigurations(plugins, workspaceRoot, nxJson); - performance.mark('retrieve-project-configurations:end'); - - performance.mark('retrieve-workspace-files:start'); - const { allWorkspaceFiles, fileMap, rustReferences } = - await retrieveWorkspaceFiles(workspaceRoot, projectRootMap); - performance.mark('retrieve-workspace-files:end'); - - const cacheEnabled = process.env.NX_CACHE_PROJECT_GRAPH !== 'false'; - performance.mark('build-project-graph-using-project-file-map:start'); - const projectGraph = ( - await buildProjectGraphUsingProjectFileMap( - projects, - externalNodes, - fileMap, - allWorkspaceFiles, - rustReferences, - cacheEnabled ? readFileMapCache() : null, - cacheEnabled, - plugins - ) - ).projectGraph; - performance.mark('build-project-graph-using-project-file-map:end'); - delete global.NX_GRAPH_CREATION; - return { projectGraph, sourceMaps }; - } finally { - cleanup(); - } + performance.mark('retrieve-project-configurations:start'); + const { projects, externalNodes, sourceMaps, projectRootMap } = + await retrieveProjectConfigurations(workspaceRoot, nxJson); + performance.mark('retrieve-project-configurations:end'); + + performance.mark('retrieve-workspace-files:start'); + const { allWorkspaceFiles, fileMap, rustReferences } = + await retrieveWorkspaceFiles(workspaceRoot, projectRootMap); + performance.mark('retrieve-workspace-files:end'); + + const cacheEnabled = process.env.NX_CACHE_PROJECT_GRAPH !== 'false'; + performance.mark('build-project-graph-using-project-file-map:start'); + const projectGraph = ( + await buildProjectGraphUsingProjectFileMap( + projects, + externalNodes, + fileMap, + allWorkspaceFiles, + rustReferences, + cacheEnabled ? readFileMapCache() : null, + cacheEnabled + ) + ).projectGraph; + performance.mark('build-project-graph-using-project-file-map:end'); + + unregisterPluginTSTranspiler(); + delete global.NX_GRAPH_CREATION; + + return { projectGraph, sourceMaps }; } function handleProjectGraphError(opts: { exitOnError: boolean }, e) { diff --git a/packages/nx/src/project-graph/utils/normalize-project-nodes.ts b/packages/nx/src/project-graph/utils/normalize-project-nodes.ts index 4493df96640d5..c7e12a17e085f 100644 --- a/packages/nx/src/project-graph/utils/normalize-project-nodes.ts +++ b/packages/nx/src/project-graph/utils/normalize-project-nodes.ts @@ -5,8 +5,9 @@ import { TargetConfiguration, } from '../../config/workspace-json-project-json'; import { findMatchingProjects } from '../../utils/find-matching-projects'; +import { NX_PREFIX } from '../../utils/logger'; import { resolveNxTokensInOptions } from '../utils/project-configuration-utils'; -import { CreateDependenciesContext } from '../plugins'; +import { CreateDependenciesContext } from '../../utils/nx-plugin'; export async function normalizeProjectNodes( ctx: CreateDependenciesContext, diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.ts index 05f08e16c542c..275cea0f6c853 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -5,6 +5,7 @@ import { TargetConfiguration, } from '../../config/workspace-json-project-json'; import { NX_PREFIX } from '../../utils/logger'; +import { CreateNodesResult, LoadedNxPlugin } from '../../utils/nx-plugin'; import { readJsonFile } from '../../utils/fileutils'; import { workspaceRoot } from '../../utils/workspace-root'; import { @@ -14,11 +15,6 @@ import { import { minimatch } from 'minimatch'; import { join } from 'path'; -import { CreateNodesError } from '../plugins/utils'; -import { - CreateNodesResultWithContext, - RemotePlugin, -} from '../plugins/internal-api'; export type SourceInformation = [file: string, plugin: string]; export type ConfigurationSourceMaps = Record< @@ -204,34 +200,90 @@ export type ConfigurationResult = { export function buildProjectsConfigurationsFromProjectPathsAndPlugins( nxJson: NxJsonConfiguration, projectFiles: string[], // making this parameter allows devkit to pick up newly created projects - plugins: RemotePlugin[], + plugins: LoadedNxPlugin[], root: string = workspaceRoot ): Promise { + type CreateNodesResultWithContext = CreateNodesResult & { + file: string; + pluginName: string; + }; + const results: Array>> = []; // We iterate over plugins first - this ensures that plugins specified first take precedence. - for (const plugin of plugins) { + for (const { plugin, options } of plugins) { const [pattern, createNodes] = plugin.createNodes ?? []; + const pluginResults: Array< + CreateNodesResultWithContext | Promise + > = []; + performance.mark(`${plugin.name}:createNodes - start`); if (!pattern) { continue; } - const matchedFiles = []; - - performance.mark(`${plugin.name}:createNodes - start`); - for (const file of projectFiles) { + performance.mark(`${plugin.name}:createNodes:${file} - start`); if (minimatch(file, pattern, { dot: true })) { - matchedFiles.push(file); + try { + let r = createNodes(file, options, { + nxJsonConfiguration: nxJson, + workspaceRoot: root, + }); + + if (r instanceof Promise) { + pluginResults.push( + r + .catch((e) => { + performance.mark(`${plugin.name}:createNodes:${file} - end`); + throw new CreateNodesError( + `Unable to create nodes for ${file} using plugin ${plugin.name}.`, + e + ); + }) + .then((r) => { + performance.mark(`${plugin.name}:createNodes:${file} - end`); + performance.measure( + `${plugin.name}:createNodes:${file}`, + `${plugin.name}:createNodes:${file} - start`, + `${plugin.name}:createNodes:${file} - end` + ); + return { ...r, file, pluginName: plugin.name }; + }) + ); + } else { + performance.mark(`${plugin.name}:createNodes:${file} - end`); + performance.measure( + `${plugin.name}:createNodes:${file}`, + `${plugin.name}:createNodes:${file} - start`, + `${plugin.name}:createNodes:${file} - end` + ); + pluginResults.push({ + ...r, + file, + pluginName: plugin.name, + }); + } + } catch (e) { + throw new CreateNodesError( + `Unable to create nodes for ${file} using plugin ${plugin.name}.`, + e + ); + } } } - let r = createNodes(matchedFiles, { - nxJsonConfiguration: nxJson, - workspaceRoot: root, - }); - - results.push(r); + // If there are no promises (counter undefined) or all promises have resolved (counter === 0) + results.push( + Promise.all(pluginResults).then((results) => { + performance.mark(`${plugin.name}:createNodes - end`); + performance.measure( + `${plugin.name}:createNodes`, + `${plugin.name}:createNodes - start`, + `${plugin.name}:createNodes - end` + ); + return results; + }) + ); } return Promise.all(results).then((results) => { @@ -345,6 +397,23 @@ export function readProjectConfigurationsFromRootMap( return projects; } +class CreateNodesError extends Error { + constructor(msg, cause: Error | unknown) { + const message = `${msg} ${ + !cause + ? '' + : cause instanceof Error + ? `\n\n\t Inner Error: ${cause.stack}` + : cause + }`; + // These errors are thrown during a JS callback which is invoked via rust. + // The errors messaging gets lost in the rust -> js -> rust transition, but + // logging the error here will ensure that it is visible in the console. + console.error(message); + super(message, { cause }); + } +} + /** * Merges two targets. * diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.spec.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.spec.ts index fc49dacc6b305..54eaa75de789a 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.spec.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.spec.ts @@ -1,3 +1,4 @@ +import { getDefaultPlugins } from '../../utils/nx-plugin'; import { TempFs } from '../../internal-testing-utils/temp-fs'; import { retrieveProjectConfigurationPaths } from './retrieve-workspace-files'; @@ -25,19 +26,10 @@ describe('retrieveProjectConfigurationPaths', () => { }) ); - const configPaths = retrieveProjectConfigurationPaths(fs.tempDir, [ - { - name: 'test', - createNodes: [ - '{project.json,**/project.json}', - () => { - return { - projects: {}, - }; - }, - ], - }, - ]); + const configPaths = await retrieveProjectConfigurationPaths( + fs.tempDir, + await getDefaultPlugins(fs.tempDir) + ); expect(configPaths).not.toContain('not-projects/project.json'); expect(configPaths).toContain('projects/project.json'); diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts index 428533d895acd..271f4746b58eb 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts @@ -1,26 +1,29 @@ import { performance } from 'perf_hooks'; +import { getNxRequirePaths } from '../../utils/installation-directory'; import { ProjectConfiguration } from '../../config/workspace-json-project-json'; import { NX_ANGULAR_JSON_PLUGIN_NAME, + NxAngularJsonPlugin, shouldMergeAngularProjects, } from '../../adapter/angular-json'; import { NxJsonConfiguration, readNxJson } from '../../config/nx-json'; import { ProjectGraphExternalNode } from '../../config/project-graph'; +import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces'; import { buildProjectsConfigurationsFromProjectPathsAndPlugins, ConfigurationSourceMaps, } from './project-configuration-utils'; import { - RemotePlugin, - loadNxPluginsInIsolation, -} from '../plugins/internal-api'; + getDefaultPlugins, + LoadedNxPlugin, + loadNxPlugins, +} from '../../utils/nx-plugin'; +import { ProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json'; import { getNxWorkspaceFilesFromContext, globWithWorkspaceContext, } from '../../utils/workspace-context'; import { buildAllWorkspaceFiles } from './build-all-workspace-files'; -import { join } from 'path'; -import { NxPlugin } from '../plugins'; /** * Walks the workspace directory to create the `projectFileMap`, `ProjectConfigurations` and `allWorkspaceFiles` @@ -63,49 +66,41 @@ export async function retrieveWorkspaceFiles( /** * Walk through the workspace and return `ProjectConfigurations`. Only use this if the projectFileMap is not needed. + * + * @param workspaceRoot + * @param nxJson */ export async function retrieveProjectConfigurations( - plugins: RemotePlugin[], workspaceRoot: string, nxJson: NxJsonConfiguration ): Promise { - const projects = await _retrieveProjectConfigurations( - workspaceRoot, - nxJson, - plugins + const plugins = await loadNxPlugins( + nxJson?.plugins ?? [], + getNxRequirePaths(workspaceRoot), + workspaceRoot ); - return projects; + + return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins); } export async function retrieveProjectConfigurationsWithAngularProjects( workspaceRoot: string, nxJson: NxJsonConfiguration ): Promise { - const pluginsToLoad = nxJson?.plugins ?? []; + const plugins = await loadNxPlugins( + nxJson?.plugins ?? [], + getNxRequirePaths(workspaceRoot), + workspaceRoot + ); if ( shouldMergeAngularProjects(workspaceRoot, true) && - !pluginsToLoad.some( - (p) => - p === NX_ANGULAR_JSON_PLUGIN_NAME || - (typeof p === 'object' && p.plugin === NX_ANGULAR_JSON_PLUGIN_NAME) - ) + !plugins.some((p) => p.plugin.name === NX_ANGULAR_JSON_PLUGIN_NAME) ) { - pluginsToLoad.push(join(__dirname, '../../adapter/angular-json')); + plugins.push({ plugin: NxAngularJsonPlugin }); } - const [plugins, cleanup] = await loadNxPluginsInIsolation( - nxJson?.plugins ?? [], - workspaceRoot - ); - - const res = _retrieveProjectConfigurations( - workspaceRoot, - nxJson, - await plugins - ); - cleanup(); - return res; + return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins); } export type RetrievedGraphNodes = { @@ -118,7 +113,7 @@ export type RetrievedGraphNodes = { function _retrieveProjectConfigurations( workspaceRoot: string, nxJson: NxJsonConfiguration, - plugins: RemotePlugin[] + plugins: LoadedNxPlugin[] ): Promise { const globPatterns = configurationGlobs(plugins); const projectFiles = globWithWorkspaceContext(workspaceRoot, globPatterns); @@ -133,7 +128,7 @@ function _retrieveProjectConfigurations( export function retrieveProjectConfigurationPaths( root: string, - plugins: NxPlugin[] + plugins: LoadedNxPlugin[] ): string[] { const projectGlobPatterns = configurationGlobs(plugins); return globWithWorkspaceContext(root, projectGlobPatterns); @@ -149,7 +144,7 @@ export async function retrieveProjectConfigurationsWithoutPluginInference( root: string ): Promise> { const nxJson = readNxJson(root); - const [plugins, cleanup] = await loadNxPluginsInIsolation([]); // only load default plugins + const plugins = await getDefaultPlugins(root); const projectGlobPatterns = retrieveProjectConfigurationPaths(root, plugins); const cacheKey = root + ',' + projectGlobPatterns.join(','); @@ -162,13 +157,14 @@ export async function retrieveProjectConfigurationsWithoutPluginInference( root, nxJson, projectFiles, - plugins + [ + { plugin: getNxPackageJsonWorkspacesPlugin(root) }, + { plugin: ProjectJsonProjectsPlugin }, + ] ); projectsWithoutPluginCache.set(cacheKey, projects); - cleanup(); - return projects; } @@ -176,7 +172,7 @@ export async function createProjectConfigurations( workspaceRoot: string, nxJson: NxJsonConfiguration, configFiles: string[], - plugins: RemotePlugin[] + plugins: LoadedNxPlugin[] ): Promise { performance.mark('build-project-configs:start'); @@ -203,10 +199,10 @@ export async function createProjectConfigurations( }; } -export function configurationGlobs(plugins: Array): string[] { +export function configurationGlobs(plugins: LoadedNxPlugin[]): string[] { const globPatterns = []; - for (const plugin of plugins) { - if ('createNodes' in plugin && plugin.createNodes) { + for (const { plugin } of plugins) { + if (plugin.createNodes) { globPatterns.push(plugin.createNodes[0]); } } diff --git a/packages/nx/src/utils/logger.ts b/packages/nx/src/utils/logger.ts index 54b03cd730a23..8096d5dc478bf 100644 --- a/packages/nx/src/utils/logger.ts +++ b/packages/nx/src/utils/logger.ts @@ -31,11 +31,6 @@ export const logger = { fatal: (...s) => { console.error(...s); }, - verbose: (...s) => { - if (process.env.NX_VERBOSE_LOGGING) { - console.log(...s); - } - }, }; export function stripIndent(str: string): string { diff --git a/packages/nx/src/utils/nx-plugin.deprecated.ts b/packages/nx/src/utils/nx-plugin.deprecated.ts index f3f370fcc5a24..c5c24129b964c 100644 --- a/packages/nx/src/utils/nx-plugin.deprecated.ts +++ b/packages/nx/src/utils/nx-plugin.deprecated.ts @@ -1,10 +1,10 @@ import { shouldMergeAngularProjects } from '../adapter/angular-json'; import { ProjectGraphProcessor } from '../config/project-graph'; import { TargetConfiguration } from '../config/workspace-json-project-json'; -import ProjectJsonProjectsPlugin from '../plugins/project-json/build-nodes/project-json'; -import TargetDefaultsPlugin from '../plugins/target-defaults/target-defaults-plugin'; -import * as PackageJsonWorkspacesPlugin from '../plugins/package-json-workspaces'; -import { NxPluginV2 } from '../project-graph/plugins'; +import { ProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; +import { TargetDefaultsPlugin } from '../plugins/target-defaults/target-defaults-plugin'; +import { getNxPackageJsonWorkspacesPlugin } from '../plugins/package-json-workspaces'; +import { LoadedNxPlugin, NxPluginV2 } from './nx-plugin'; /** * @deprecated Add targets to the projects in a {@link CreateNodes} function instead. This will be removed in Nx 19 @@ -39,14 +39,14 @@ export type NxPluginV1 = { /** * @todo(@agentender) v19: Remove this fn when we remove readWorkspaceConfig */ -export function getDefaultPluginsSync(root: string) { +export function getDefaultPluginsSync(root: string): LoadedNxPlugin[] { const plugins: NxPluginV2[] = [ require('../plugins/js'), ...(shouldMergeAngularProjects(root, false) ? [require('../adapter/angular-json').NxAngularJsonPlugin] : []), TargetDefaultsPlugin, - PackageJsonWorkspacesPlugin, + getNxPackageJsonWorkspacesPlugin(root), ProjectJsonProjectsPlugin, ]; diff --git a/packages/nx/src/utils/nx-plugin.ts b/packages/nx/src/utils/nx-plugin.ts new file mode 100644 index 0000000000000..836dfe23337fb --- /dev/null +++ b/packages/nx/src/utils/nx-plugin.ts @@ -0,0 +1,531 @@ +import { existsSync } from 'fs'; +import * as path from 'path'; +import { + FileMap, + ProjectGraph, + ProjectGraphExternalNode, +} from '../config/project-graph'; +import { toProjectName } from '../config/workspaces'; + +import { workspaceRoot } from './workspace-root'; +import { readJsonFile } from '../utils/fileutils'; +import { + PackageJson, + readModulePackageJsonWithoutFallbacks, +} from './package-json'; +import { + registerTranspiler, + registerTsConfigPaths, +} from '../plugins/js/utils/register'; +import { ProjectConfiguration } from '../config/workspace-json-project-json'; +import { logger } from './logger'; +import { + createProjectRootMappingsFromProjectConfigurations, + findProjectForPath, +} from '../project-graph/utils/find-project-for-path'; +import { normalizePath } from './path'; +import { dirname, join } from 'path'; +import { getNxRequirePaths } from './installation-directory'; +import { readTsConfig } from '../plugins/js/utils/typescript'; +import { + NxJsonConfiguration, + PluginConfiguration, + readNxJson, +} from '../config/nx-json'; + +import type * as ts from 'typescript'; +import { NxPluginV1 } from './nx-plugin.deprecated'; +import { RawProjectGraphDependency } from '../project-graph/project-graph-builder'; +import { combineGlobPatterns } from './globs'; +import { shouldMergeAngularProjects } from '../adapter/angular-json'; +import { getNxPackageJsonWorkspacesPlugin } from '../plugins/package-json-workspaces'; +import { ProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; +import { PackageJsonProjectsNextToProjectJsonPlugin } from '../plugins/project-json/build-nodes/package-json-next-to-project-json'; +import { retrieveProjectConfigurationsWithoutPluginInference } from '../project-graph/utils/retrieve-workspace-files'; +import { TargetDefaultsPlugin } from '../plugins/target-defaults/target-defaults-plugin'; + +/** + * Context for {@link CreateNodesFunction} + */ +export interface CreateNodesContext { + readonly nxJsonConfiguration: NxJsonConfiguration; + readonly workspaceRoot: string; +} + +/** + * A function which parses a configuration file into a set of nodes. + * Used for creating nodes for the {@link ProjectGraph} + */ +export type CreateNodesFunction = ( + projectConfigurationFile: string, + options: T | undefined, + context: CreateNodesContext +) => CreateNodesResult | Promise; + +export interface CreateNodesResult { + /** + * A map of project root -> project configuration + */ + projects?: Record>; + + /** + * A map of external node name -> external node. External nodes do not have a root, so the key is their name. + */ + externalNodes?: Record; +} + +/** + * A pair of file patterns and {@link CreateNodesFunction} + */ +export type CreateNodes = readonly [ + projectFilePattern: string, + createNodesFunction: CreateNodesFunction +]; + +/** + * Context for {@link CreateDependencies} + */ +export interface CreateDependenciesContext { + /** + * The external nodes that have been added to the graph. + */ + readonly externalNodes: ProjectGraph['externalNodes']; + + /** + * The configuration of each project in the workspace. + */ + readonly projects: Record; + + /** + * The `nx.json` configuration from the workspace + */ + readonly nxJsonConfiguration: NxJsonConfiguration; + + /** + * All files in the workspace + */ + readonly fileMap: FileMap; + + /** + * Files changes since last invocation + */ + readonly filesToProcess: FileMap; + + readonly workspaceRoot: string; +} + +/** + * A function which parses files in the workspace to create dependencies in the {@link ProjectGraph} + * Use {@link validateDependency} to validate dependencies + */ +export type CreateDependencies = ( + options: T | undefined, + context: CreateDependenciesContext +) => RawProjectGraphDependency[] | Promise; + +/** + * A plugin for Nx which creates nodes and dependencies for the {@link ProjectGraph} + */ +export type NxPluginV2 = { + name: string; + + /** + * Provides a file pattern and function that retrieves configuration info from + * those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile } + */ + createNodes?: CreateNodes; + + // Todo(@AgentEnder): This shouldn't be a full processor, since its only responsible for defining edges between projects. What do we want the API to be? + /** + * Provides a function to analyze files to create dependencies for the {@link ProjectGraph} + */ + createDependencies?: CreateDependencies; +}; + +export * from './nx-plugin.deprecated'; + +/** + * A plugin for Nx + */ +export type NxPlugin = NxPluginV1 | NxPluginV2; + +export type LoadedNxPlugin = { + plugin: NxPluginV2 & Pick; + options?: unknown; +}; + +// Short lived cache (cleared between cmd runs) +// holding resolved nx plugin objects. +// Allows loadNxPlugins to be called multiple times w/o +// executing resolution mulitple times. +export const nxPluginCache: Map = new Map(); + +export function getPluginPathAndName( + moduleName: string, + paths: string[], + projects: Record, + root: string +) { + let pluginPath: string; + try { + pluginPath = require.resolve(moduleName, { + paths, + }); + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + const plugin = resolveLocalNxPlugin( + moduleName, + readNxJson(root), + projects, + root + ); + if (plugin) { + const main = readPluginMainFromProjectConfiguration( + plugin.projectConfig + ); + pluginPath = main ? path.join(root, main) : plugin.path; + } else { + logger.error(`Plugin listed in \`nx.json\` not found: ${moduleName}`); + throw e; + } + } else { + throw e; + } + } + const packageJsonPath = path.join(pluginPath, 'package.json'); + + const extension = path.extname(pluginPath); + + // Register the ts-transpiler if we are pointing to a + // plain ts file that's not part of a plugin project + if (extension === '.ts' && !tsNodeAndPathsUnregisterCallback) { + registerPluginTSTranspiler(); + } + + const { name } = + !['.ts', '.js'].some((x) => x === extension) && // Not trying to point to a ts or js file + existsSync(packageJsonPath) // plugin has a package.json + ? readJsonFile(packageJsonPath) // read name from package.json + : { name: moduleName }; + return { pluginPath, name }; +} + +export async function loadNxPluginAsync( + pluginConfiguration: PluginConfiguration, + paths: string[], + projects: Record, + root: string +): Promise { + const { plugin: moduleName, options } = + typeof pluginConfiguration === 'object' + ? pluginConfiguration + : { plugin: pluginConfiguration, options: undefined }; + let pluginModule = nxPluginCache.get(moduleName); + if (pluginModule) { + return { plugin: pluginModule, options }; + } + performance.mark(`Load Nx Plugin: ${moduleName} - start`); + let { pluginPath, name } = await getPluginPathAndName( + moduleName, + paths, + projects, + root + ); + const plugin = ensurePluginIsV2( + (await import(pluginPath)) as LoadedNxPlugin['plugin'] + ); + plugin.name ??= name; + nxPluginCache.set(moduleName, plugin); + performance.mark(`Load Nx Plugin: ${moduleName} - end`); + performance.measure( + `Load Nx Plugin: ${moduleName}`, + `Load Nx Plugin: ${moduleName} - start`, + `Load Nx Plugin: ${moduleName} - end` + ); + return { plugin, options }; +} + +export async function loadNxPlugins( + plugins: PluginConfiguration[], + paths = getNxRequirePaths(), + root = workspaceRoot, + projects?: Record +): Promise { + const result: LoadedNxPlugin[] = [ + { plugin: PackageJsonProjectsNextToProjectJsonPlugin }, + ]; + + plugins ??= []; + + // When loading plugins for `createNodes`, we don't know what projects exist yet. + // Try resolving plugins + for (const plugin of plugins) { + try { + require.resolve(typeof plugin === 'string' ? plugin : plugin.plugin); + } catch { + // If a plugin cannot be resolved, we will need projects to resolve it + projects ??= await retrieveProjectConfigurationsWithoutPluginInference( + root + ); + break; + } + } + for (const plugin of plugins) { + result.push(await loadNxPluginAsync(plugin, paths, projects, root)); + } + + // We push the nx core node plugins onto the end, s.t. it overwrites any other plugins + result.push(...(await getDefaultPlugins(root))); + + return result; +} + +export function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 { + if (isNxPluginV2(plugin)) { + return plugin; + } + if (isNxPluginV1(plugin) && plugin.projectFilePatterns) { + return { + ...plugin, + createNodes: [ + `*/**/${combineGlobPatterns(plugin.projectFilePatterns)}`, + (configFilePath) => { + const root = dirname(configFilePath); + return { + projects: { + [root]: { + name: toProjectName(configFilePath), + root, + targets: plugin.registerProjectTargets?.(configFilePath), + }, + }, + }; + }, + ], + }; + } + return plugin; +} + +export function isNxPluginV2(plugin: NxPlugin): plugin is NxPluginV2 { + return 'createNodes' in plugin || 'createDependencies' in plugin; +} + +export function isNxPluginV1(plugin: NxPlugin): plugin is NxPluginV1 { + return 'processProjectGraph' in plugin || 'projectFilePatterns' in plugin; +} + +export function readPluginPackageJson( + pluginName: string, + projects: Record, + paths = getNxRequirePaths() +): { + path: string; + json: PackageJson; +} { + try { + const result = readModulePackageJsonWithoutFallbacks(pluginName, paths); + return { + json: result.packageJson, + path: result.path, + }; + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + const nxJson = readNxJson(); + const localPluginPath = resolveLocalNxPlugin( + pluginName, + nxJson, + projects + ); + if (localPluginPath) { + const localPluginPackageJson = path.join( + localPluginPath.path, + 'package.json' + ); + return { + path: localPluginPackageJson, + json: readJsonFile(localPluginPackageJson), + }; + } + } + throw e; + } +} + +/** + * Builds a plugin package and returns the path to output + * @param importPath What is the import path that refers to a potential plugin? + * @returns The path to the built plugin, or null if it doesn't exist + */ +const localPluginCache: Record< + string, + { path: string; projectConfig: ProjectConfiguration } +> = {}; + +export function resolveLocalNxPlugin( + importPath: string, + nxJsonConfiguration: NxJsonConfiguration, + projects: Record, + root = workspaceRoot +): { path: string; projectConfig: ProjectConfiguration } | null { + localPluginCache[importPath] ??= lookupLocalPlugin( + importPath, + nxJsonConfiguration, + projects, + root + ); + return localPluginCache[importPath]; +} + +let tsNodeAndPathsUnregisterCallback: (() => void) | undefined = undefined; + +/** + * Register swc-node or ts-node if they are not currently registered + * with some default settings which work well for Nx plugins. + */ +export function registerPluginTSTranspiler() { + if (!tsNodeAndPathsUnregisterCallback) { + // nx-ignore-next-line + const ts: typeof import('typescript') = require('typescript'); + + // Get the first tsconfig that matches the allowed set + const tsConfigName = [ + join(workspaceRoot, 'tsconfig.base.json'), + join(workspaceRoot, 'tsconfig.json'), + ].find((x) => existsSync(x)); + + const tsConfig: Partial = tsConfigName + ? readTsConfig(tsConfigName) + : {}; + + const unregisterTsConfigPaths = registerTsConfigPaths(tsConfigName); + const unregisterTranspiler = registerTranspiler({ + experimentalDecorators: true, + emitDecoratorMetadata: true, + ...tsConfig.options, + }); + tsNodeAndPathsUnregisterCallback = () => { + unregisterTsConfigPaths(); + unregisterTranspiler(); + }; + } +} + +/** + * Unregister the ts-node transpiler if it is registered + */ +export function unregisterPluginTSTranspiler() { + if (tsNodeAndPathsUnregisterCallback) { + tsNodeAndPathsUnregisterCallback(); + tsNodeAndPathsUnregisterCallback = undefined; + } +} + +function lookupLocalPlugin( + importPath: string, + nxJsonConfiguration: NxJsonConfiguration, + projects: Record, + root = workspaceRoot +) { + const plugin = findNxProjectForImportPath(importPath, projects, root); + if (!plugin) { + return null; + } + + if (!tsNodeAndPathsUnregisterCallback) { + registerPluginTSTranspiler(); + } + + const projectConfig: ProjectConfiguration = projects[plugin]; + return { path: path.join(root, projectConfig.root), projectConfig }; +} + +function findNxProjectForImportPath( + importPath: string, + projects: Record, + root = workspaceRoot +): string | null { + const tsConfigPaths: Record = readTsConfigPaths(root); + const possiblePaths = tsConfigPaths[importPath]?.map((p) => + normalizePath(path.relative(root, path.join(root, p))) + ); + if (possiblePaths?.length) { + const projectRootMappings = + createProjectRootMappingsFromProjectConfigurations(projects); + for (const tsConfigPath of possiblePaths) { + const nxProject = findProjectForPath(tsConfigPath, projectRootMappings); + if (nxProject) { + return nxProject; + } + } + if (process.env.NX_VERBOSE_LOGGING) { + console.log( + 'Unable to find local plugin', + possiblePaths, + projectRootMappings + ); + } + throw new Error( + 'Unable to resolve local plugin with import path ' + importPath + ); + } +} + +let tsconfigPaths: Record; + +function readTsConfigPaths(root: string = workspaceRoot) { + if (!tsconfigPaths) { + const tsconfigPath: string | null = ['tsconfig.base.json', 'tsconfig.json'] + .map((x) => path.join(root, x)) + .filter((x) => existsSync(x))[0]; + if (!tsconfigPath) { + throw new Error('unable to find tsconfig.base.json or tsconfig.json'); + } + const { compilerOptions } = readJsonFile(tsconfigPath); + tsconfigPaths = compilerOptions?.paths; + } + return tsconfigPaths ?? {}; +} + +function readPluginMainFromProjectConfiguration( + plugin: ProjectConfiguration +): string | null { + const { main } = + Object.values(plugin.targets).find((x) => + [ + '@nx/js:tsc', + '@nrwl/js:tsc', + '@nx/js:swc', + '@nrwl/js:swc', + '@nx/node:package', + '@nrwl/node:package', + ].includes(x.executor) + )?.options || + plugin.targets?.build?.options || + {}; + return main; +} + +export async function getDefaultPlugins( + root: string +): Promise { + const plugins: NxPluginV2[] = [ + await import('../plugins/js'), + TargetDefaultsPlugin, + ...(shouldMergeAngularProjects(root, false) + ? [ + await import('../adapter/angular-json').then( + (m) => m.NxAngularJsonPlugin + ), + ] + : []), + getNxPackageJsonWorkspacesPlugin(root), + ProjectJsonProjectsPlugin, + ]; + + return plugins.map((p) => ({ + plugin: p, + })); +} + +type Optional = Omit & Partial>; diff --git a/packages/nx/src/utils/plugins/plugin-capabilities.ts b/packages/nx/src/utils/plugins/plugin-capabilities.ts index 3d9fdb22eae03..156cc111415b8 100644 --- a/packages/nx/src/utils/plugins/plugin-capabilities.ts +++ b/packages/nx/src/utils/plugins/plugin-capabilities.ts @@ -1,18 +1,19 @@ +import { workspaceRoot } from '../workspace-root'; import * as chalk from 'chalk'; import { dirname, join } from 'path'; - -import { ProjectConfiguration } from '../../config/workspace-json-project-json'; -import { NxPlugin, readPluginPackageJson } from '../../project-graph/plugins'; -import { loadPlugin } from '../../project-graph/plugins/internal-api'; +import { output } from '../output'; +import type { PluginCapabilities } from './models'; +import { hasElements } from './shared'; import { readJsonFile } from '../fileutils'; +import { getPackageManagerCommand } from '../package-manager'; +import { + loadNxPluginAsync, + NxPlugin, + readPluginPackageJson, +} from '../nx-plugin'; import { getNxRequirePaths } from '../installation-directory'; -import { output } from '../output'; import { PackageJson } from '../package-json'; -import { getPackageManagerCommand } from '../package-manager'; -import { workspaceRoot } from '../workspace-root'; -import { hasElements } from './shared'; - -import type { PluginCapabilities } from './models'; +import { ProjectConfiguration } from '../../config/workspace-json-project-json'; function tryGetCollection( packageJsonPath: string, @@ -45,7 +46,7 @@ export async function getPluginCapabilities( getNxRequirePaths(workspaceRoot) ); const pluginModule = includeRuntimeCapabilities - ? await tryGetModule(packageJson, workspaceRoot) + ? await tryGetModule(packageJson, workspaceRoot, projects) : ({} as Record); return { name: pluginName, @@ -98,7 +99,8 @@ export async function getPluginCapabilities( async function tryGetModule( packageJson: PackageJson, - workspaceRoot: string + workspaceRoot: string, + projects: Record ): Promise { try { return packageJson.generators ?? @@ -106,7 +108,14 @@ async function tryGetModule( packageJson['nx-migrations'] ?? packageJson['schematics'] ?? packageJson['builders'] - ? (await loadPlugin(packageJson.name, workspaceRoot)).plugin + ? ( + await loadNxPluginAsync( + packageJson.name, + getNxRequirePaths(workspaceRoot), + projects, + workspaceRoot + ) + ).plugin : ({ name: packageJson.name, } as NxPlugin);