diff --git a/packages/nx/src/config/workspaces.spec.ts b/packages/nx/src/config/workspaces.spec.ts index 7f1c2f2e427927..f7145e5fc5b603 100644 --- a/packages/nx/src/config/workspaces.spec.ts +++ b/packages/nx/src/config/workspaces.spec.ts @@ -3,7 +3,7 @@ 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'; +import { loadNxPlugins } from '../project-graph/plugins/internal-api'; describe('Workspaces', () => { let fs: TempFs; @@ -38,7 +38,7 @@ describe('Workspaces', () => { NX_WORKSPACE_ROOT_PATH: fs.tempDir, }, async () => { - const [plugins, cleanup] = await loadNxPluginsInIsolation( + const [plugins, cleanup] = await loadNxPlugins( readNxJson(fs.tempDir).plugins, fs.tempDir ); @@ -51,6 +51,7 @@ describe('Workspaces', () => { return res; } ); + console.log(projects); expect(projects['my-package']).toEqual({ name: 'my-package', root: 'packages/my-package', diff --git a/packages/nx/src/daemon/server/plugins.ts b/packages/nx/src/daemon/server/plugins.ts index ddf57fdcb393cb..38f4b598678e8a 100644 --- a/packages/nx/src/daemon/server/plugins.ts +++ b/packages/nx/src/daemon/server/plugins.ts @@ -1,11 +1,11 @@ import { readNxJson } from '../../config/nx-json'; import { - RemotePlugin, - loadNxPluginsInIsolation, + LoadedNxPlugin, + loadNxPlugins, } from '../../project-graph/plugins/internal-api'; import { workspaceRoot } from '../../utils/workspace-root'; -let loadedPlugins: Promise; +let loadedPlugins: Promise; let cleanup: () => void; export async function getPlugins() { @@ -13,7 +13,7 @@ export async function getPlugins() { return loadedPlugins; } const pluginsConfiguration = readNxJson().plugins ?? []; - const [result, cleanupFn] = await loadNxPluginsInIsolation( + const [result, cleanupFn] = await loadNxPlugins( pluginsConfiguration, workspaceRoot ); 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 b40f08f73828f4..fbc260c696dce0 100644 --- a/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts +++ b/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts @@ -34,11 +34,11 @@ import { serverLogger } from './logger'; import { NxWorkspaceFilesExternals } from '../../native'; import { ConfigurationResult, - ProjectConfigurationsError, } from '../../project-graph/utils/project-configuration-utils'; import { DaemonProjectGraphError } from '../daemon-project-graph-error'; import { LoadedNxPlugin } from '../../project-graph/plugins/internal-api'; import { getPlugins } from './plugins'; +import { ProjectConfigurationsError } from 'nx/src/project-graph/error-types'; interface SerializedProjectGraph { error: Error | null; diff --git a/packages/nx/src/executors/utils/convert-nx-executor.ts b/packages/nx/src/executors/utils/convert-nx-executor.ts index 76d0d9dbd3322e..7b7d618f4c76a9 100644 --- a/packages/nx/src/executors/utils/convert-nx-executor.ts +++ b/packages/nx/src/executors/utils/convert-nx-executor.ts @@ -7,7 +7,7 @@ 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'; +import { loadNxPlugins } from '../../project-graph/plugins/internal-api'; /** * Convert an Nx Executor into an Angular Devkit Builder @@ -19,7 +19,7 @@ export function convertNxExecutor(executor: Executor) { const promise = async () => { const nxJsonConfiguration = readNxJson(builderContext.workspaceRoot); - const [plugins, cleanup] = await loadNxPluginsInIsolation( + const [plugins, cleanup] = await loadNxPlugins( nxJsonConfiguration.plugins, builderContext.workspaceRoot ); diff --git a/packages/nx/src/generators/utils/project-configuration.ts b/packages/nx/src/generators/utils/project-configuration.ts index 1f93b6a07aa750..8cb7b51fa6f29c 100644 --- a/packages/nx/src/generators/utils/project-configuration.ts +++ b/packages/nx/src/generators/utils/project-configuration.ts @@ -202,7 +202,7 @@ function readAndCombineAllProjectConfigurations(tree: Tree): { ]; const projectGlobPatterns = configurationGlobs([ ProjectJsonProjectsPlugin, - { createNodes: packageJsonWorkspacesCreateNodes } as NxPlugin, + { createNodes: packageJsonWorkspacesCreateNodes }, ]); 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 01ca3b2afde80f..aed9f9b5f6965d 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,14 @@ 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 '../../project-graph/plugins/internal-api'; export default async function (tree: Tree) { const nxJson = readNxJson(tree); + const [plugins, cleanup] = (await loadNxPlugins(nxJson?.plugins ?? [], tree.root)) const projectFiles = retrieveProjectConfigurationPaths( tree.root, - (await loadPlugins(nxJson?.plugins ?? [], tree.root)).map((p) => p.plugin) + plugins ); const projectJsons = projectFiles.filter((f) => f.endsWith('project.json')); @@ -22,6 +23,7 @@ export default async function (tree: Tree) { } } await formatChangedFilesWithPrettierIfAvailable(tree); + cleanup(); } function toProjectName(directory: string, nxJson: any): string { 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 2c79667bdac9e0..d6b5e5a0f77d53 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 @@ -10,8 +10,7 @@ import { } from '../../../../project-graph/utils/retrieve-workspace-files'; import { CreateDependenciesContext } from '../../../../project-graph/plugins'; import { setupWorkspaceContext } from '../../../../utils/workspace-context'; -import ProjectJsonProjectsPlugin from '../../../project-json/build-nodes/project-json'; -import { loadNxPluginsInIsolation } from '../../../../project-graph/plugins/internal-api'; +import { loadNxPlugins } from '../../../../project-graph/plugins/internal-api'; // projectName => tsconfig import path const dependencyProjectNamesToImportPaths = { @@ -566,7 +565,7 @@ async function createContext( setupWorkspaceContext(tempFs.tempDir); - const [plugins, cleanup] = await loadNxPluginsInIsolation([], tempFs.tempDir); + const [plugins, cleanup] = await loadNxPlugins([], tempFs.tempDir); const { projects, projectRootMap } = await retrieveProjectConfigurations( plugins, tempFs.tempDir, 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 de78d677347a72..cff3d81eeedad9 100644 --- a/packages/nx/src/plugins/package-json-workspaces/create-nodes.ts +++ b/packages/nx/src/plugins/package-json-workspaces/create-nodes.ts @@ -22,6 +22,7 @@ const patterns = getGlobPatternsFromPackageManagerWorkspaces( workspaceRoot, readJson ); + const negativePatterns = patterns.filter((p) => p.startsWith('!')); const positivePatterns = patterns.filter((p) => !p.startsWith('!')); if ( diff --git a/packages/nx/src/plugins/target-defaults/symbols.ts b/packages/nx/src/plugins/target-defaults/symbols.ts new file mode 100644 index 00000000000000..b47b7e77e9c9f9 --- /dev/null +++ b/packages/nx/src/plugins/target-defaults/symbols.ts @@ -0,0 +1,18 @@ +/** + * This marks that a target provides information which should modify a target already registered + * on the project via other plugins. If the target has not already been registered, and this symbol is true, + * the information provided by it will be discarded. + * + * NOTE: This cannot be a symbol, as they are not serialized in JSON the communication + * between the plugin-worker and the main process. + */ +export const ONLY_MODIFIES_EXISTING_TARGET = 'NX_ONLY_MODIFIES_EXISTING_TARGET'; + +/** + * This is used to override the source file for the target defaults plugin. + * This allows the plugin to use the project files as the context, but point to nx.json as the source file. + * + * NOTE: This cannot be a symbol, as they are not serialized in JSON the communication + * between the plugin-worker and the main process. + */ +export const OVERRIDE_SOURCE_FILE = 'NX_OVERRIDE_SOURCE_FILE'; \ No newline at end of file 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 f31c5ec1b8a32b..f383e2c477fd8b 100644 --- a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts +++ b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts @@ -14,25 +14,7 @@ import { readTargetsFromPackageJson, } from '../../utils/package-json'; import { getGlobPatternsFromPackageManagerWorkspaces } from '../package-json-workspaces'; - -/** - * This marks that a target provides information which should modify a target already registered - * on the project via other plugins. If the target has not already been registered, and this symbol is true, - * the information provided by it will be discarded. - * - * NOTE: This cannot be a symbol, as they are not serialized in JSON the communication - * between the plugin-worker and the main process. - */ -export const ONLY_MODIFIES_EXISTING_TARGET = 'NX_ONLY_MODIFIES_EXISTING_TARGET'; - -/** - * This is used to override the source file for the target defaults plugin. - * This allows the plugin to use the project files as the context, but point to nx.json as the source file. - * - * NOTE: This cannot be a symbol, as they are not serialized in JSON the communication - * between the plugin-worker and the main process. - */ -export const OVERRIDE_SOURCE_FILE = 'NX_OVERRIDE_SOURCE_FILE'; +import { ONLY_MODIFIES_EXISTING_TARGET, OVERRIDE_SOURCE_FILE } from './symbols'; export const TargetDefaultsPlugin: NxPluginV2 = { name: 'nx/core/target-defaults', 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 aaabf121c1ff5b..6c54137366a25f 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 @@ -4,15 +4,13 @@ import { workspaceRoot } from '../../../utils/workspace-root'; import { join } from 'path'; import { existsSync } from 'fs'; import { configurationGlobs } from '../../utils/retrieve-workspace-files'; -import { loadPlugins } from '../../plugins/internal-api'; +import { loadNxPlugins } from '../../plugins/internal-api'; 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)) - ); + const [plugins] = await loadNxPlugins(nxJson?.plugins ?? [], workspaceRoot); + const globPattern = combineGlobPatterns(configurationGlobs(plugins)); const touchedProjects = new Set(); for (const touchedFile of touchedFiles) { diff --git a/packages/nx/src/project-graph/build-project-graph.ts b/packages/nx/src/project-graph/build-project-graph.ts index ec9fd0e1403756..841226a26d1899 100644 --- a/packages/nx/src/project-graph/build-project-graph.ts +++ b/packages/nx/src/project-graph/build-project-graph.ts @@ -256,7 +256,7 @@ async function updateProjectGraphWithPlugins( ) { let graph = initProjectGraph; const errors: Array = []; - for (const { plugin } of plugins) { + for (const plugin of plugins) { try { if ( isNxPluginV1(plugin) && @@ -307,15 +307,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, options}) => { + createDependencyPlugins.map(async (plugin) => { performance.mark(`${plugin.name}:createDependencies - start`); try { - // TODO: we shouldn't have to pass null here - const dependencies = await plugin.createDependencies(options, { + const dependencies = await plugin.createDependencies({ ...context, }); diff --git a/packages/nx/src/project-graph/error-types.ts b/packages/nx/src/project-graph/error-types.ts new file mode 100644 index 00000000000000..2ef3193b45325c --- /dev/null +++ b/packages/nx/src/project-graph/error-types.ts @@ -0,0 +1,98 @@ +import { CreateNodesResultWithContext } from './plugins/internal-api'; +import { ConfigurationResult } from './utils/project-configuration-utils'; + +export class ProjectConfigurationsError extends Error { + constructor( + public readonly errors: Array, + public readonly partialProjectConfigurationsResult: ConfigurationResult + ) { + super('Failed to create project configurations'); + this.name = this.constructor.name; + } +} + +export class CreateNodesError extends Error { + file: string; + pluginName: string; + + constructor({ + file, + pluginName, + error, + }: { + file: string; + pluginName: string; + error: Error; + }) { + const msg = `The "${pluginName}" plugin threw an error while creating nodes from ${file}:`; + + super(msg, { cause: error }); + this.name = this.constructor.name; + this.file = file; + this.pluginName = pluginName; + this.stack = `${this.message}\n ${error.stack.split('\n').join('\n ')}`; + } +} + +export class AggregateCreateNodesError extends Error { + constructor( + public readonly pluginName: string, + public readonly errors: Array, + public readonly partialResults: Array + ) { + super('Failed to create nodes'); + this.name = this.constructor.name; + } +} + +export class MergeNodesError extends Error { + file: string; + pluginName: string; + + constructor({ + file, + pluginName, + error, + }: { + file: string; + pluginName: string; + error: Error; + }) { + const msg = `The nodes created from ${file} by the "${pluginName}" could not be merged into the project graph:`; + + super(msg, { cause: error }); + this.name = this.constructor.name; + this.file = file; + this.pluginName = pluginName; + this.stack = `${this.message}\n ${error.stack.split('\n').join('\n ')}`; + } +} + +export function isCreateNodesError(e: unknown): e is CreateNodesError { + return ( + e instanceof CreateNodesError || + (typeof e === 'object' && + 'name' in e && + e?.name === CreateNodesError.prototype.name) + ); +} + +export function isAggregateCreateNodesError( + e: unknown +): e is AggregateCreateNodesError { + return ( + e instanceof AggregateCreateNodesError || + (typeof e === 'object' && + 'name' in e && + e?.name === AggregateCreateNodesError.prototype.name) + ); +} + +export function isMergeNodesError(e: unknown): e is MergeNodesError { + return ( + e instanceof MergeNodesError || + (typeof e === 'object' && + 'name' in e && + e?.name === MergeNodesError.prototype.name) + ); +} \ No newline at end of file diff --git a/packages/nx/src/project-graph/file-utils.ts b/packages/nx/src/project-graph/file-utils.ts index 64c6cb161fe3d6..3d73a37a05ece0 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 './plugins/internal-api'; export interface Change { type: string; @@ -183,17 +184,17 @@ export { readNxJson, workspaceLayout } from '../config/configuration'; function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) { const allConfigFiles = retrieveProjectConfigurationPaths( root, - getDefaultPluginsSync(root).map((p) => p.plugin) + getDefaultPluginsSync(root) ); const plugins = [ - { plugin: PackageJsonProjectsNextToProjectJsonPlugin }, + PackageJsonProjectsNextToProjectJsonPlugin, ...getDefaultPluginsSync(root), ]; 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 of plugins) { const [pattern, createNodes] = plugin.createNodes ?? []; if (!pattern) { continue; diff --git a/packages/nx/src/project-graph/plugins/index.ts b/packages/nx/src/project-graph/plugins/index.ts index 0c939a25f6db09..d22ae00ed187b3 100644 --- a/packages/nx/src/project-graph/plugins/index.ts +++ b/packages/nx/src/project-graph/plugins/index.ts @@ -3,4 +3,4 @@ export * from './public-api'; export { readPluginPackageJson, registerPluginTSTranspiler, -} from './worker-api'; +} from './loader'; diff --git a/packages/nx/src/project-graph/plugins/internal-api.ts b/packages/nx/src/project-graph/plugins/internal-api.ts index 47f7b19782d7ea..375a515e18b985 100644 --- a/packages/nx/src/project-graph/plugins/internal-api.ts +++ b/packages/nx/src/project-graph/plugins/internal-api.ts @@ -8,33 +8,62 @@ 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 { + CreateDependencies, + CreateDependenciesContext, CreateNodesContext, CreateNodesResult, - NxPlugin, NxPluginV2, } from './public-api'; - -export { loadPlugins, loadPlugin } from './worker-api'; - -export type LoadedNxPlugin = { - plugin: // 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 - ]; - }, - options?: unknown; - include?: string[]; - exclude?: string[]; -}; +import { ProjectGraphProcessor } from 'nx/src/config/project-graph'; +import { runCreateNodesInParallel } from './utils'; +import { loadNxPluginInIsolation } from './isolation'; +import { loadNxPlugin, unregisterPluginTSTranspiler } from './loader'; + +export class LoadedNxPlugin { + readonly name: string; + readonly 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 + ]; + readonly createDependencies?: ( + context: CreateDependenciesContext + ) => ReturnType; + readonly processProjectGraph?: ProjectGraphProcessor; + + readonly options?: unknown; + readonly include?: string[]; + readonly exclude?: string[]; + + constructor(plugin: NormalizedPlugin, pluginDefinition: PluginConfiguration) { + this.name = plugin.name; + if (typeof pluginDefinition !== 'string') { + this.options = pluginDefinition.options; + this.include = pluginDefinition.include; + this.exclude = pluginDefinition.exclude; + } + + if (plugin.createNodes) { + this.createNodes = [ + plugin.createNodes[0], + (files, context) => + runCreateNodesInParallel(files, plugin, this.options, context), + ]; + } + + if (plugin.createDependencies) { + this.createDependencies = (context) => + plugin.createDependencies(this.options, context); + } + + this.processProjectGraph = plugin.processProjectGraph; + } +} export type CreateNodesResultWithContext = CreateNodesResult & { file: string; @@ -48,36 +77,29 @@ export type NormalizedPlugin = NxPluginV2 & // holding resolved nx plugin objects. // Allows loaded plugins to not be reloaded when // referenced multiple times. -export const nxPluginCache: Map, () => void]> = - new Map(); +export const nxPluginCache: Map< + unknown, + [Promise, () => 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( +export async function loadNxPlugins( plugins: PluginConfiguration[], root = workspaceRoot ): Promise<[LoadedNxPlugin[], () => void]> { const result: Promise[] = []; - plugins ??= []; + const loadingMethod = + process.env.NX_PLUGIN_ISOLATION === 'true' + ? loadNxPluginInIsolation + : loadNxPlugin; - plugins.unshift( - join( - __dirname, - '../../plugins/project-json/build-nodes/package-json-next-to-project-json' - ) - ); + console.log(loadingMethod === loadNxPluginInIsolation ? 'isolated' : 'not isolated') - // We push the nx core node plugins onto the end, s.t. it overwrites any other plugins - plugins.push(...(await getDefaultPlugins(root))); + plugins = await normalizePlugins(plugins, root); const cleanupFunctions: Array<() => void> = []; for (const plugin of plugins) { - const [loadedPluginPromise, cleanup] = loadNxPluginInIsolation( - plugin, - root - ); + const [loadedPluginPromise, cleanup] = loadingMethod(plugin, root); result.push(loadedPluginPromise); cleanupFunctions.push(cleanup); } @@ -88,23 +110,27 @@ export async function loadNxPluginsInIsolation( for (const fn of cleanupFunctions) { fn(); } + if (unregisterPluginTSTranspiler) { + unregisterPluginTSTranspiler(); + } }, - ]; + ] as const; } -export function loadNxPluginInIsolation( - plugin: PluginConfiguration, - root = workspaceRoot -): [Promise, () => void] { - const cacheKey = JSON.stringify(plugin); - - if (nxPluginCache.has(cacheKey)) { - return nxPluginCache.get(cacheKey); - } +async function normalizePlugins(plugins: PluginConfiguration[], root: string) { + plugins ??= []; - const [loadingPlugin, cleanup] = loadRemoteNxPlugin(plugin, root); - nxPluginCache.set(cacheKey, [loadingPlugin, cleanup]); - return [loadingPlugin, cleanup]; + return [ + // This plugin adds targets that we want to be able to overwrite + // in any user-land plugin, so it has to be first :). + join( + __dirname, + '../../plugins/project-json/build-nodes/package-json-next-to-project-json' + ), + ...plugins, + // Most of the nx core node plugins go on the end, s.t. it overwrites any other plugins + ...(await getDefaultPlugins(root)), + ]; } export async function getDefaultPlugins(root: string) { diff --git a/packages/nx/src/project-graph/plugins/isolation/index.ts b/packages/nx/src/project-graph/plugins/isolation/index.ts new file mode 100644 index 00000000000000..f70b2e4001338b --- /dev/null +++ b/packages/nx/src/project-graph/plugins/isolation/index.ts @@ -0,0 +1,24 @@ +import { workspaceRoot } from '../../../utils/workspace-root'; +import { PluginConfiguration } from '../../../config/nx-json'; +import { LoadedNxPlugin } from '../internal-api'; +import { loadRemoteNxPlugin } from './plugin-pool'; + +const remotePluginCache = new Map< + string, + [Promise, () => void] +>(); + +export function loadNxPluginInIsolation( + plugin: PluginConfiguration, + root = workspaceRoot +): [Promise, () => void] { + const cacheKey = JSON.stringify(plugin); + + if (remotePluginCache.has(cacheKey)) { + return remotePluginCache.get(cacheKey); + } + + const [loadingPlugin, cleanup] = loadRemoteNxPlugin(plugin, root); + remotePluginCache.set(cacheKey, [loadingPlugin, cleanup]); + return [loadingPlugin, cleanup]; +} diff --git a/packages/nx/src/project-graph/plugins/messaging.ts b/packages/nx/src/project-graph/plugins/isolation/messaging.ts similarity index 91% rename from packages/nx/src/project-graph/plugins/messaging.ts rename to packages/nx/src/project-graph/plugins/isolation/messaging.ts index fca8310bf6dcc7..8ee05a0a1d69de 100644 --- a/packages/nx/src/project-graph/plugins/messaging.ts +++ b/packages/nx/src/project-graph/plugins/isolation/messaging.ts @@ -1,10 +1,10 @@ import { ProjectGraph, ProjectGraphProcessorContext, -} from '../../config/project-graph'; -import { PluginConfiguration } from '../../config/nx-json'; -import { CreateDependenciesContext, CreateNodesContext } from './public-api'; -import { RemotePlugin } from './internal-api'; +} from '../../../config/project-graph'; +import { PluginConfiguration } from '../../../config/nx-json'; +import { CreateDependenciesContext, CreateNodesContext } from '../public-api'; +import { LoadedNxPlugin } from '../internal-api'; export interface PluginWorkerLoadMessage { type: 'load'; @@ -44,7 +44,7 @@ export interface PluginWorkerCreateNodesResult { payload: | { success: true; - result: Awaited>; + result: Awaited>; tx: string; } | { @@ -66,7 +66,7 @@ export interface PluginCreateDependenciesResult { type: 'createDependenciesResult'; payload: | { - dependencies: ReturnType; + dependencies: ReturnType; success: true; tx: string; } diff --git a/packages/nx/src/project-graph/plugins/plugin-pool.ts b/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts similarity index 94% rename from packages/nx/src/project-graph/plugins/plugin-pool.ts rename to packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts index 05a2682b1753dd..2371f0ba36403b 100644 --- a/packages/nx/src/project-graph/plugins/plugin-pool.ts +++ b/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts @@ -1,12 +1,12 @@ import { ChildProcess, fork } from 'child_process'; import path = require('path'); -import { PluginConfiguration } from '../../config/nx-json'; +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 { LoadedNxPlugin, nxPluginCache } from './internal-api'; +import { LoadedNxPlugin, nxPluginCache } from '../internal-api'; import { PluginWorkerResult, consumeMessage, createMessage } from './messaging'; const cleanupFunctions = new Set<() => void>(); @@ -61,17 +61,13 @@ export function loadRemoteNxPlugin( cleanupFunctions.add(cleanupFunction); return [ - new Promise((res, rej) => { + new Promise((res, rej) => { worker.on( 'message', createWorkerHandler(worker, pendingPromises, res, rej) ); worker.on('exit', exitHandler); - }).then((loadedPlugin) => - typeof plugin === 'string' - ? { plugin: loadedPlugin } - : { ...plugin, plugin: loadedPlugin } - ), + }), () => { cleanupFunction(); cleanupFunctions.delete(cleanupFunction); @@ -108,7 +104,7 @@ async function shutdownPluginWorker( function createWorkerHandler( worker: ChildProcess, pending: Map, - onload: (plugin: LoadedNxPlugin['plugin']) => void, + onload: (plugin: LoadedNxPlugin) => void, onloadError: (err?: unknown) => void ) { let pluginName: string; @@ -145,7 +141,7 @@ function createWorkerHandler( ] : undefined, createDependencies: result.hasCreateDependencies - ? (opts, ctx) => { + ? (ctx) => { const tx = pluginName + ':createDependencies:' + performance.now(); return registerPendingPromise(tx, pending, () => { diff --git a/packages/nx/src/project-graph/plugins/plugin-worker.ts b/packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts similarity index 55% rename from packages/nx/src/project-graph/plugins/plugin-worker.ts rename to packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts index 268a35a071eee6..1b759e9649335b 100644 --- a/packages/nx/src/project-graph/plugins/plugin-worker.ts +++ b/packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts @@ -1,23 +1,19 @@ 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'; +import { LoadedNxPlugin } from '../internal-api'; +import { loadNxPlugin } from '../loader'; +import { runCreateNodesInParallel } from '../utils'; global.NX_GRAPH_CREATION = true; -let plugin: NormalizedPlugin; -let pluginOptions: unknown; +let plugin: LoadedNxPlugin; process.on('message', async (message: string) => { consumeMessage(message, { load: async ({ plugin: pluginConfiguration, root }) => { process.chdir(root); try { - ({ plugin, options: pluginOptions } = await loadPlugin( - pluginConfiguration, - root - )); + const [promise] = loadNxPlugin(pluginConfiguration, root); + plugin = await promise; return { type: 'load-result', payload: { @@ -44,7 +40,7 @@ process.on('message', async (message: string) => { }, createNodes: async ({ configFiles, context, tx }) => { try { - const result = await runCreateNodesInParallel(configFiles, context); + const result = await plugin.createNodes[1](configFiles, context); return { type: 'createNodesResult', payload: { result, success: true, tx }, @@ -58,7 +54,7 @@ process.on('message', async (message: string) => { }, createDependencies: async ({ context, tx }) => { try { - const result = await plugin.createDependencies(pluginOptions, context); + const result = await plugin.createDependencies(context); return { type: 'createDependenciesResult', payload: { dependencies: result, success: true, tx }, @@ -86,37 +82,3 @@ process.on('message', async (message: string) => { }, }); }); - -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/worker-api.ts b/packages/nx/src/project-graph/plugins/loader.ts similarity index 89% rename from packages/nx/src/project-graph/plugins/worker-api.ts rename to packages/nx/src/project-graph/plugins/loader.ts index db9ee31b12af69..b367b4d91a98f5 100644 --- a/packages/nx/src/project-graph/plugins/worker-api.ts +++ b/packages/nx/src/project-graph/plugins/loader.ts @@ -75,6 +75,8 @@ export function resolveLocalNxPlugin( return lookupLocalPlugin(importPath, projects, root); } +export let unregisterPluginTSTranspiler: (() => void) | null = null; + /** * Register swc-node or ts-node if they are not currently registered * with some default settings which work well for Nx plugins. @@ -86,16 +88,24 @@ export function registerPluginTSTranspiler() { join(workspaceRoot, 'tsconfig.json'), ].find((x) => existsSync(x)); + if (!tsConfigName) { + return; + } + const tsConfig: Partial = tsConfigName ? readTsConfig(tsConfigName) : {}; - - registerTsConfigPaths(tsConfigName); - registerTranspiler({ - experimentalDecorators: true, - emitDecoratorMetadata: true, - ...tsConfig.options, - }); + const cleanupFns = [ + registerTsConfigPaths(tsConfigName), + registerTranspiler({ + experimentalDecorators: true, + emitDecoratorMetadata: true, + ...tsConfig.options, + }), + ]; + unregisterPluginTSTranspiler = () => { + cleanupFns.forEach((fn) => fn?.()); + }; } function lookupLocalPlugin( @@ -225,43 +235,40 @@ export function getPluginPathAndName( let projectsWithoutInference: Record; -export async function loadPlugins( - plugins: PluginConfiguration[], - root: string -): Promise { - return await Promise.all(plugins.map((p) => loadPlugin(p, root))); +export function loadNxPlugin(plugin: PluginConfiguration, root: string) { + return [ + loadNxPluginAsync( + plugin, + getNxRequirePaths(root), + projectsWithoutInference, + root + ), + () => {}, + ] as const; } -export async function loadPlugin(plugin: PluginConfiguration, root: string) { +export async function loadNxPluginAsync( + pluginConfiguration: PluginConfiguration, + paths: string[], + projects: Record, + root: string +): Promise { try { - require.resolve(typeof plugin === 'string' ? plugin : plugin.plugin); + require.resolve( + typeof pluginConfiguration === 'string' + ? pluginConfiguration + : pluginConfiguration.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 async function loadNxPluginAsync( - pluginConfiguration: PluginConfiguration, - paths: string[], - projects: Record, - root: string -): Promise { - const { - plugin: moduleName, - options, - include, - exclude, - } = typeof pluginConfiguration === 'object' - ? pluginConfiguration - : ({ plugin: pluginConfiguration } as ExpandedPluginConfiguration); + const moduleName = + typeof pluginConfiguration === 'string' + ? pluginConfiguration + : pluginConfiguration.plugin; performance.mark(`Load Nx Plugin: ${moduleName} - start`); let { pluginPath, name } = await getPluginPathAndName( @@ -278,7 +285,7 @@ export async function loadNxPluginAsync( `Load Nx Plugin: ${moduleName} - start`, `Load Nx Plugin: ${moduleName} - end` ); - return { plugin, options, include, exclude }; + return new LoadedNxPlugin(plugin, pluginConfiguration); } async function importPluginModule(pluginPath: string): Promise { diff --git a/packages/nx/src/project-graph/plugins/utils.ts b/packages/nx/src/project-graph/plugins/utils.ts index 4d5b839d658ccb..51eb9d1a9f7ac8 100644 --- a/packages/nx/src/project-graph/plugins/utils.ts +++ b/packages/nx/src/project-graph/plugins/utils.ts @@ -4,15 +4,20 @@ 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'; +import type { + CreateNodesResultWithContext, + LoadedNxPlugin, + NormalizedPlugin, +} from './internal-api'; +import type { CreateNodesContext, NxPlugin, NxPluginV2 } from './public-api'; +import { AggregateCreateNodesError, CreateNodesError } from '../error-types'; export function isNxPluginV2(plugin: NxPlugin): plugin is NxPluginV2 { return 'createNodes' in plugin || 'createDependencies' in plugin; } export function isNxPluginV1( - plugin: NxPlugin | RemotePlugin + plugin: NxPlugin | LoadedNxPlugin ): plugin is NxPluginV1 { return 'processProjectGraph' in plugin || 'projectFilePatterns' in plugin; } @@ -43,19 +48,81 @@ export function normalizeNxPlugin(plugin: NxPlugin): NormalizedPlugin { 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 }); +export async function runCreateNodesInParallel( + configFiles: string[], + plugin: NormalizedPlugin, + options: unknown, + context: CreateNodesContext +): Promise { + performance.mark(`${plugin.name}:createNodes - start`); + + console.log('FINDING NODES', { + plugin: plugin.name, + configFiles, + }) + + 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, options, context) + ); + return value + .catch((e) => { + performance.mark(`${plugin.name}:createNodes:${file} - end`); + return new CreateNodesError({ + error: e, + pluginName: plugin.name, + file, + }); + }) + .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 }; + }); + }); + const results = await Promise.all(promises).then((results) => { + performance.mark(`${plugin.name}:createNodes - end`); + performance.measure( + `${plugin.name}:createNodes`, + `${plugin.name}:createNodes - start`, + `${plugin.name}:createNodes - end` + ); + return results; + }); + + const [errors, successful] = partition< + CreateNodesError, + CreateNodesResultWithContext + >(results, (r): r is CreateNodesError => r instanceof CreateNodesError); + + if (errors.length > 0) { + throw new AggregateCreateNodesError(plugin.name, errors, successful); + } + return results; +} + +function partition( + arr: Array, + test: (item: T | T2) => item is T +): [T[], T2[]] { + const pass: T[] = []; + const fail: T2[] = []; + for (const item of arr) { + if (test(item)) { + pass.push(item); + } else { + fail.push(item as any as T2); + } } + return [pass, fail]; } diff --git a/packages/nx/src/project-graph/project-graph.ts b/packages/nx/src/project-graph/project-graph.ts index 4f879e41d49a5a..05b8967a3cb4eb 100644 --- a/packages/nx/src/project-graph/project-graph.ts +++ b/packages/nx/src/project-graph/project-graph.ts @@ -26,16 +26,17 @@ import { retrieveWorkspaceFiles, } from './utils/retrieve-workspace-files'; import { readNxJson } from '../config/nx-json'; -import { unregisterPluginTSTranspiler } from '../utils/nx-plugin'; import { ConfigurationResult, ConfigurationSourceMaps, +} from './utils/project-configuration-utils'; +import { CreateNodesError, MergeNodesError, ProjectConfigurationsError, -} from './utils/project-configuration-utils'; +} from './error-types'; import { DaemonProjectGraphError } from '../daemon/daemon-project-graph-error'; -import { loadNxPluginsInIsolation, LoadedNxPlugin } from './plugins/internal-api'; +import { loadNxPlugins, LoadedNxPlugin } from './plugins/internal-api'; /** * Synchronously reads the latest cached copy of the workspace's ProjectGraph. @@ -102,9 +103,8 @@ export async function buildProjectGraphAndSourceMapsWithoutDaemon() { performance.mark('retrieve-project-configurations:start'); let configurationResult: ConfigurationResult; let projectConfigurationsError: ProjectConfigurationsError; - const [plugins, cleanup] = await loadNxPluginsInIsolation(nxJson.plugins); + const [plugins, cleanup] = await loadNxPlugins(nxJson.plugins); try { - configurationResult = await retrieveProjectConfigurations( plugins, workspaceRoot, @@ -122,10 +122,10 @@ export async function buildProjectGraphAndSourceMapsWithoutDaemon() { configurationResult; 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'); + 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'); @@ -160,7 +160,6 @@ export async function buildProjectGraphAndSourceMapsWithoutDaemon() { const { projectGraph, projectFileMapCache } = projectGraphResult; performance.mark('build-project-graph-using-project-file-map:end'); - unregisterPluginTSTranspiler(); delete global.NX_GRAPH_CREATION; const errors = [ diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts index c47040d1e72271..3c108290261b85 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts @@ -1,4 +1,4 @@ -import { ONLY_MODIFIES_EXISTING_TARGET } from '../../plugins/target-defaults/target-defaults-plugin'; +import { ONLY_MODIFIES_EXISTING_TARGET } from '../../plugins/target-defaults/symbols'; import { ProjectConfiguration, TargetConfiguration, 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 e1b13d2db02b70..eedd51d054d26e 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -10,13 +10,21 @@ import { workspaceRoot } from '../../utils/workspace-root'; import { ONLY_MODIFIES_EXISTING_TARGET, OVERRIDE_SOURCE_FILE, -} from '../../plugins/target-defaults/target-defaults-plugin'; +} from '../../plugins/target-defaults/symbols'; import { minimatch } from 'minimatch'; import { join } from 'path'; import { performance } from 'perf_hooks'; -import { CreateNodesResult } from '../plugins/public-api'; -import { CreateNodesResultWithContext, LoadedNxPlugin } from '../plugins/internal-api'; +import { + CreateNodesResultWithContext, + LoadedNxPlugin, +} from '../plugins/internal-api'; +import { + CreateNodesError, + MergeNodesError, + ProjectConfigurationsError, + isAggregateCreateNodesError, +} from '../error-types'; export type SourceInformation = [file: string, plugin: string]; export type ConfigurationSourceMaps = Record< @@ -292,11 +300,11 @@ export type ConfigurationResult = { * @param workspaceFiles A list of non-ignored workspace files * @param plugins The plugins that should be used to infer project configuration */ -export function createProjectConfigurations( +export async function createProjectConfigurations( root: string = workspaceRoot, nxJson: NxJsonConfiguration, projectFiles: string[], // making this parameter allows devkit to pick up newly created projects - plugins: LoadedNxPlugin[], + plugins: LoadedNxPlugin[] ): Promise { performance.mark('build-project-configs:start'); @@ -304,11 +312,13 @@ export function createProjectConfigurations( const errors: Array = []; // We iterate over plugins first - this ensures that plugins specified first take precedence. - for (const { plugin, options, include, exclude } of plugins) { - const [pattern, createNodes] = plugin.createNodes ?? []; - const pluginResults: Array< - CreateNodesResultWithContext | Promise - > = []; + for (const { + name: pluginName, + createNodes: createNodesTuple, + include, + exclude, + } of plugins) { + const [pattern, createNodes] = createNodesTuple ?? []; if (!pattern) { continue; @@ -339,53 +349,21 @@ export function createProjectConfigurations( matchingConfigFiles.push(file); } } - for (const file of matchingConfigFiles) { - performance.mark(`${plugin.name}:createNodes:${file} - start`); - let r = createNodes(matchingConfigFiles, { - nxJsonConfiguration: nxJson, - workspaceRoot: root, - configFiles: matchingConfigFiles, - }); - - pluginResults.push( - r - .catch((error) => { - performance.mark(`${plugin.name}:createNodes:${file} - end`); - errors.push( - new CreateNodesError({ - file, - pluginName: plugin.name, - error, - }) - ); - return { - projects: {}, - }; - }) - .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 }; - }) - ); - - } + let r = createNodes(matchingConfigFiles, { + nxJsonConfiguration: nxJson, + workspaceRoot: root, + configFiles: matchingConfigFiles, + }) + .catch((e) => { + if (isAggregateCreateNodesError(e)) { + errors.push(...e.errors); + return e.partialResults; + } else { + throw e; + } + }); - 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; - }) - ); + results.push(r); } return Promise.all(results).then((results) => { @@ -518,62 +496,6 @@ export function readProjectConfigurationsFromRootMap( return projects; } -export class ProjectConfigurationsError extends Error { - constructor( - public readonly errors: Array, - public readonly partialProjectConfigurationsResult: ConfigurationResult - ) { - super('Failed to create project configurations'); - this.name = this.constructor.name; - } -} - -export class CreateNodesError extends Error { - file: string; - pluginName: string; - - constructor({ - file, - pluginName, - error, - }: { - file: string; - pluginName: string; - error: Error; - }) { - const msg = `The "${pluginName}" plugin threw an error while creating nodes from ${file}:`; - - super(msg, { cause: error }); - this.name = this.constructor.name; - this.file = file; - this.pluginName = pluginName; - this.stack = `${this.message}\n ${error.stack.split('\n').join('\n ')}`; - } -} - -export class MergeNodesError extends Error { - file: string; - pluginName: string; - - constructor({ - file, - pluginName, - error, - }: { - file: string; - pluginName: string; - error: Error; - }) { - const msg = `The nodes created from ${file} by the "${pluginName}" could not be merged into the project graph:`; - - super(msg, { cause: error }); - this.name = this.constructor.name; - this.file = file; - this.pluginName = pluginName; - this.stack = `${this.message}\n ${error.stack.split('\n').join('\n ')}`; - } -} - /** * 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 fc49dacc6b305f..3d23230b11e692 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 @@ -27,7 +27,6 @@ describe('retrieveProjectConfigurationPaths', () => { const configPaths = retrieveProjectConfigurationPaths(fs.tempDir, [ { - name: 'test', createNodes: [ '{project.json,**/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 82b56f401a7d7d..a5c91cce1a96a8 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts @@ -9,10 +9,7 @@ import { createProjectConfigurations, ConfigurationResult, } from './project-configuration-utils'; -import { - LoadedNxPlugin, - loadNxPluginsInIsolation, -} from '../plugins/internal-api'; +import { LoadedNxPlugin, loadNxPlugins } from '../plugins/internal-api'; import { getNxWorkspaceFilesFromContext, globWithWorkspaceContext, @@ -93,7 +90,7 @@ export async function retrieveProjectConfigurationsWithAngularProjects( pluginsToLoad.push(join(__dirname, '../../adapter/angular-json')); } - const [plugins, cleanup] = await loadNxPluginsInIsolation( + const [plugins, cleanup] = await loadNxPlugins( nxJson?.plugins ?? [], workspaceRoot ); @@ -125,7 +122,7 @@ function _retrieveProjectConfigurations( export function retrieveProjectConfigurationPaths( root: string, - plugins: LoadedNxPlugin[] + plugins: Array<{ createNodes?: readonly [string, ...unknown[]] } & unknown> ): string[] { const projectGlobPatterns = configurationGlobs(plugins); return globWithWorkspaceContext(root, projectGlobPatterns); @@ -141,7 +138,7 @@ export async function retrieveProjectConfigurationsWithoutPluginInference( root: string ): Promise> { const nxJson = readNxJson(root); - const [plugins, cleanup] = await loadNxPluginsInIsolation([]); // only load default plugins + const [plugins, cleanup] = await loadNxPlugins([]); // only load default plugins const projectGlobPatterns = retrieveProjectConfigurationPaths(root, plugins); const cacheKey = root + ',' + projectGlobPatterns.join(','); @@ -165,7 +162,9 @@ export async function retrieveProjectConfigurationsWithoutPluginInference( return projects; } -export function configurationGlobs(plugins: LoadedNxPlugin[]): string[] { +export function configurationGlobs( + plugins: Array<{ createNodes?: readonly [string, ...unknown[]] }> +): string[] { const globPatterns = []; for (const plugin of plugins) { if ('createNodes' in plugin && plugin.createNodes) { diff --git a/packages/nx/src/utils/nx-plugin.deprecated.ts b/packages/nx/src/utils/nx-plugin.deprecated.ts index f3f370fcc5a24a..6bd67d63ab9234 100644 --- a/packages/nx/src/utils/nx-plugin.deprecated.ts +++ b/packages/nx/src/utils/nx-plugin.deprecated.ts @@ -5,6 +5,7 @@ import ProjectJsonProjectsPlugin from '../plugins/project-json/build-nodes/proje import TargetDefaultsPlugin from '../plugins/target-defaults/target-defaults-plugin'; import * as PackageJsonWorkspacesPlugin from '../plugins/package-json-workspaces'; import { NxPluginV2 } from '../project-graph/plugins'; +import { LoadedNxPlugin } from '../project-graph/plugins/internal-api'; /** * @deprecated Add targets to the projects in a {@link CreateNodes} function instead. This will be removed in Nx 19 @@ -39,7 +40,7 @@ export type NxPluginV1 = { /** * @todo(@agentender) v19: Remove this fn when we remove readWorkspaceConfig */ -export function getDefaultPluginsSync(root: string) { +export function getDefaultPluginsSync(root: string): NxPluginV2[] { const plugins: NxPluginV2[] = [ require('../plugins/js'), ...(shouldMergeAngularProjects(root, false) @@ -50,7 +51,5 @@ export function getDefaultPluginsSync(root: string) { ProjectJsonProjectsPlugin, ]; - return plugins.map((p) => ({ - plugin: p, - })); + return plugins; } diff --git a/packages/nx/src/utils/plugins/plugin-capabilities.ts b/packages/nx/src/utils/plugins/plugin-capabilities.ts index 3d9fdb22eae033..595d552bd03a28 100644 --- a/packages/nx/src/utils/plugins/plugin-capabilities.ts +++ b/packages/nx/src/utils/plugins/plugin-capabilities.ts @@ -3,7 +3,6 @@ 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 { readJsonFile } from '../fileutils'; import { getNxRequirePaths } from '../installation-directory'; import { output } from '../output'; @@ -13,6 +12,7 @@ import { workspaceRoot } from '../workspace-root'; import { hasElements } from './shared'; import type { PluginCapabilities } from './models'; +import { loadNxPlugin } from 'nx/src/project-graph/plugins/loader'; function tryGetCollection( packageJsonPath: string, @@ -101,15 +101,21 @@ async function tryGetModule( workspaceRoot: string ): Promise { try { - return packageJson.generators ?? + if ( + packageJson.generators ?? packageJson.executors ?? packageJson['nx-migrations'] ?? packageJson['schematics'] ?? packageJson['builders'] - ? (await loadPlugin(packageJson.name, workspaceRoot)).plugin - : ({ - name: packageJson.name, - } as NxPlugin); + ) { + const [pluginPromise] = loadNxPlugin(packageJson.name, workspaceRoot); + const plugin = await pluginPromise; + return plugin; + } else { + return { + name: packageJson.name, + }; + } } catch { return null; }