fix(core): avoid launching default plugins twice (#29539)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior Default plugins are launched twice when loading plugins in a workspace that has local plugins: - Once to resolve the local plugin - Once to be used as an actual plugin ## Expected Behavior Default plugins are launched once and reused ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes # --------- Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
This commit is contained in:
parent
fb318005f2
commit
0edd1102f7
@ -7,7 +7,7 @@ import {
|
|||||||
parseTargetString,
|
parseTargetString,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { addE2eCiTargetDefaults as _addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
import { addE2eCiTargetDefaults as _addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||||
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api';
|
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/loaded-nx-plugin';
|
||||||
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||||
import {
|
import {
|
||||||
ProjectConfigurationsError,
|
ProjectConfigurationsError,
|
||||||
|
|||||||
@ -3,7 +3,10 @@ import { TempFs } from '../internal-testing-utils/temp-fs';
|
|||||||
import { withEnvironmentVariables } from '../internal-testing-utils/with-environment';
|
import { withEnvironmentVariables } from '../internal-testing-utils/with-environment';
|
||||||
import { retrieveProjectConfigurations } from '../project-graph/utils/retrieve-workspace-files';
|
import { retrieveProjectConfigurations } from '../project-graph/utils/retrieve-workspace-files';
|
||||||
import { readNxJson } from './configuration';
|
import { readNxJson } from './configuration';
|
||||||
import { loadNxPlugins } from '../project-graph/plugins/internal-api';
|
import {
|
||||||
|
cleanupPlugins,
|
||||||
|
getPlugins,
|
||||||
|
} from '../project-graph/plugins/get-plugins';
|
||||||
|
|
||||||
describe('Workspaces', () => {
|
describe('Workspaces', () => {
|
||||||
let fs: TempFs;
|
let fs: TempFs;
|
||||||
@ -40,16 +43,13 @@ describe('Workspaces', () => {
|
|||||||
NX_WORKSPACE_ROOT_PATH: fs.tempDir,
|
NX_WORKSPACE_ROOT_PATH: fs.tempDir,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const [plugins, cleanup] = await loadNxPlugins(
|
const plugins = await getPlugins(fs.tempDir);
|
||||||
readNxJson(fs.tempDir).plugins,
|
|
||||||
fs.tempDir
|
|
||||||
);
|
|
||||||
const res = await retrieveProjectConfigurations(
|
const res = await retrieveProjectConfigurations(
|
||||||
plugins,
|
plugins,
|
||||||
fs.tempDir,
|
fs.tempDir,
|
||||||
readNxJson(fs.tempDir)
|
readNxJson(fs.tempDir)
|
||||||
);
|
);
|
||||||
cleanup();
|
cleanupPlugins();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import { notifyFileWatcherSockets } from './file-watching/file-watcher-sockets';
|
|||||||
import { serverLogger } from './logger';
|
import { serverLogger } from './logger';
|
||||||
import { NxWorkspaceFilesExternals } from '../../native';
|
import { NxWorkspaceFilesExternals } from '../../native';
|
||||||
import { ConfigurationResult } from '../../project-graph/utils/project-configuration-utils';
|
import { ConfigurationResult } from '../../project-graph/utils/project-configuration-utils';
|
||||||
import { LoadedNxPlugin } from '../../project-graph/plugins/internal-api';
|
import type { LoadedNxPlugin } from '../../project-graph/plugins/loaded-nx-plugin';
|
||||||
import {
|
import {
|
||||||
DaemonProjectGraphError,
|
DaemonProjectGraphError,
|
||||||
ProjectConfigurationsError,
|
ProjectConfigurationsError,
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export {
|
|||||||
findProjectForPath,
|
findProjectForPath,
|
||||||
} from './project-graph/utils/find-project-for-path';
|
} from './project-graph/utils/find-project-for-path';
|
||||||
export { retrieveProjectConfigurations } from './project-graph/utils/retrieve-workspace-files';
|
export { retrieveProjectConfigurations } from './project-graph/utils/retrieve-workspace-files';
|
||||||
export { LoadedNxPlugin } from './project-graph/plugins/internal-api';
|
export { LoadedNxPlugin } from './project-graph/plugins/loaded-nx-plugin';
|
||||||
export * from './project-graph/error-types';
|
export * from './project-graph/error-types';
|
||||||
export { registerTsProject } from './plugins/js/utils/register';
|
export { registerTsProject } from './plugins/js/utils/register';
|
||||||
export { interpolate } from './tasks-runner/utils';
|
export { interpolate } from './tasks-runner/utils';
|
||||||
|
|||||||
@ -5,7 +5,6 @@ const tempFs = new TempFs('explicit-project-deps');
|
|||||||
import { ProjectGraphProjectNode } from '../../../../config/project-graph';
|
import { ProjectGraphProjectNode } from '../../../../config/project-graph';
|
||||||
import { ProjectConfiguration } from '../../../../config/workspace-json-project-json';
|
import { ProjectConfiguration } from '../../../../config/workspace-json-project-json';
|
||||||
import { CreateDependenciesContext } from '../../../../project-graph/plugins';
|
import { CreateDependenciesContext } from '../../../../project-graph/plugins';
|
||||||
import { loadNxPlugins } from '../../../../project-graph/plugins/internal-api';
|
|
||||||
import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder';
|
import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder';
|
||||||
import {
|
import {
|
||||||
retrieveProjectConfigurations,
|
retrieveProjectConfigurations,
|
||||||
@ -14,6 +13,10 @@ import {
|
|||||||
import { setupWorkspaceContext } from '../../../../utils/workspace-context';
|
import { setupWorkspaceContext } from '../../../../utils/workspace-context';
|
||||||
import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies';
|
import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies';
|
||||||
import { TargetProjectLocator } from './target-project-locator';
|
import { TargetProjectLocator } from './target-project-locator';
|
||||||
|
import {
|
||||||
|
cleanupPlugins,
|
||||||
|
getOnlyDefaultPlugins,
|
||||||
|
} from '../../../../project-graph/plugins/get-plugins';
|
||||||
|
|
||||||
// projectName => tsconfig import path
|
// projectName => tsconfig import path
|
||||||
const dependencyProjectNamesToImportPaths = {
|
const dependencyProjectNamesToImportPaths = {
|
||||||
@ -698,13 +701,13 @@ async function createContext(
|
|||||||
|
|
||||||
setupWorkspaceContext(tempFs.tempDir);
|
setupWorkspaceContext(tempFs.tempDir);
|
||||||
|
|
||||||
const [plugins, cleanup] = await loadNxPlugins([], tempFs.tempDir);
|
const plugins = await getOnlyDefaultPlugins(tempFs.tempDir);
|
||||||
const { projects, projectRootMap } = await retrieveProjectConfigurations(
|
const { projects, projectRootMap } = await retrieveProjectConfigurations(
|
||||||
plugins,
|
plugins,
|
||||||
tempFs.tempDir,
|
tempFs.tempDir,
|
||||||
nxJson
|
nxJson
|
||||||
);
|
);
|
||||||
cleanup();
|
cleanupPlugins();
|
||||||
|
|
||||||
const { fileMap } = await retrieveWorkspaceFiles(
|
const { fileMap } = await retrieveWorkspaceFiles(
|
||||||
tempFs.tempDir,
|
tempFs.tempDir,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
} from './nx-deps-cache';
|
} from './nx-deps-cache';
|
||||||
import { applyImplicitDependencies } from './utils/implicit-project-dependencies';
|
import { applyImplicitDependencies } from './utils/implicit-project-dependencies';
|
||||||
import { normalizeProjectNodes } from './utils/normalize-project-nodes';
|
import { normalizeProjectNodes } from './utils/normalize-project-nodes';
|
||||||
import { LoadedNxPlugin } from './plugins/internal-api';
|
import type { LoadedNxPlugin } from './plugins/loaded-nx-plugin';
|
||||||
import {
|
import {
|
||||||
CreateDependenciesContext,
|
CreateDependenciesContext,
|
||||||
CreateMetadataContext,
|
CreateMetadataContext,
|
||||||
|
|||||||
@ -1,17 +1,33 @@
|
|||||||
import { hashObject } from '../../hasher/file-hasher';
|
import { join } from 'node:path';
|
||||||
import { readNxJson } from '../../config/nx-json';
|
|
||||||
import { LoadedNxPlugin, loadNxPlugins } from './internal-api';
|
|
||||||
import { workspaceRoot } from '../../utils/workspace-root';
|
|
||||||
|
|
||||||
|
import { shouldMergeAngularProjects } from '../../adapter/angular-json';
|
||||||
|
import { PluginConfiguration, readNxJson } from '../../config/nx-json';
|
||||||
|
import { hashObject } from '../../hasher/file-hasher';
|
||||||
|
import { IS_WASM } from '../../native';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import { loadNxPluginInIsolation } from './isolation';
|
||||||
|
import { loadNxPlugin } from './in-process-loader';
|
||||||
|
|
||||||
|
import type { LoadedNxPlugin } from './loaded-nx-plugin';
|
||||||
|
import {
|
||||||
|
cleanupPluginTSTranspiler,
|
||||||
|
pluginTranspilerIsRegistered,
|
||||||
|
} from './transpiler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stuff for specified NX Plugins.
|
||||||
|
*/
|
||||||
let currentPluginsConfigurationHash: string;
|
let currentPluginsConfigurationHash: string;
|
||||||
let loadedPlugins: LoadedNxPlugin[];
|
let loadedPlugins: LoadedNxPlugin[];
|
||||||
let pendingPluginsPromise:
|
let pendingPluginsPromise:
|
||||||
| Promise<readonly [LoadedNxPlugin[], () => void]>
|
| Promise<readonly [LoadedNxPlugin[], () => void]>
|
||||||
| undefined;
|
| undefined;
|
||||||
let cleanup: () => void;
|
let cleanup: () => void | undefined;
|
||||||
|
|
||||||
export async function getPlugins() {
|
export async function getPlugins(
|
||||||
const pluginsConfiguration = readNxJson().plugins ?? [];
|
root = workspaceRoot
|
||||||
|
): Promise<LoadedNxPlugin[]> {
|
||||||
|
const pluginsConfiguration = readNxJson(root).plugins ?? [];
|
||||||
const pluginsConfigurationHash = hashObject(pluginsConfiguration);
|
const pluginsConfigurationHash = hashObject(pluginsConfiguration);
|
||||||
|
|
||||||
// If the plugins configuration has not changed, reuse the current plugins
|
// If the plugins configuration has not changed, reuse the current plugins
|
||||||
@ -28,22 +44,29 @@ export async function getPlugins() {
|
|||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingPluginsPromise ??= loadNxPlugins(pluginsConfiguration, workspaceRoot);
|
pendingPluginsPromise ??= loadSpecifiedNxPlugins(pluginsConfiguration, root);
|
||||||
|
|
||||||
currentPluginsConfigurationHash = pluginsConfigurationHash;
|
currentPluginsConfigurationHash = pluginsConfigurationHash;
|
||||||
const [result, cleanupFn] = await pendingPluginsPromise;
|
const [[result, cleanupFn], defaultPlugins] = await Promise.all([
|
||||||
|
pendingPluginsPromise,
|
||||||
|
getOnlyDefaultPlugins(root),
|
||||||
|
]);
|
||||||
cleanup = cleanupFn;
|
cleanup = cleanupFn;
|
||||||
loadedPlugins = result;
|
loadedPlugins = result.concat(defaultPlugins);
|
||||||
return result;
|
return loadedPlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stuff for default NX Plugins.
|
||||||
|
*/
|
||||||
|
|
||||||
let loadedDefaultPlugins: LoadedNxPlugin[];
|
let loadedDefaultPlugins: LoadedNxPlugin[];
|
||||||
let cleanupDefaultPlugins: () => void;
|
let cleanupDefaultPlugins: () => void;
|
||||||
let pendingDefaultPluginPromise:
|
let pendingDefaultPluginPromise:
|
||||||
| Promise<readonly [LoadedNxPlugin[], () => void]>
|
| Promise<readonly [LoadedNxPlugin[], () => void]>
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
export async function getOnlyDefaultPlugins() {
|
export async function getOnlyDefaultPlugins(root = workspaceRoot) {
|
||||||
// If the plugins configuration has not changed, reuse the current plugins
|
// If the plugins configuration has not changed, reuse the current plugins
|
||||||
if (loadedDefaultPlugins) {
|
if (loadedDefaultPlugins) {
|
||||||
return loadedPlugins;
|
return loadedPlugins;
|
||||||
@ -55,7 +78,7 @@ export async function getOnlyDefaultPlugins() {
|
|||||||
cleanupDefaultPlugins();
|
cleanupDefaultPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingDefaultPluginPromise ??= loadNxPlugins([], workspaceRoot);
|
pendingDefaultPluginPromise ??= loadDefaultNxPlugins(workspaceRoot);
|
||||||
|
|
||||||
const [result, cleanupFn] = await pendingDefaultPluginPromise;
|
const [result, cleanupFn] = await pendingDefaultPluginPromise;
|
||||||
cleanupDefaultPlugins = cleanupFn;
|
cleanupDefaultPlugins = cleanupFn;
|
||||||
@ -66,6 +89,138 @@ export async function getOnlyDefaultPlugins() {
|
|||||||
export function cleanupPlugins() {
|
export function cleanupPlugins() {
|
||||||
pendingPluginsPromise = undefined;
|
pendingPluginsPromise = undefined;
|
||||||
pendingDefaultPluginPromise = undefined;
|
pendingDefaultPluginPromise = undefined;
|
||||||
cleanup();
|
cleanup?.();
|
||||||
cleanupDefaultPlugins();
|
cleanupDefaultPlugins?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stuff for generic loading
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isIsolationEnabled() {
|
||||||
|
// Explicitly enabled, regardless of further conditions
|
||||||
|
if (process.env.NX_ISOLATE_PLUGINS === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
// Explicitly disabled
|
||||||
|
process.env.NX_ISOLATE_PLUGINS === 'false' ||
|
||||||
|
// Isolation is disabled on WASM builds currently.
|
||||||
|
IS_WASM
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Default value
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingMethod = isIsolationEnabled()
|
||||||
|
? loadNxPluginInIsolation
|
||||||
|
: loadNxPlugin;
|
||||||
|
|
||||||
|
async function loadDefaultNxPlugins(root = workspaceRoot) {
|
||||||
|
performance.mark('loadDefaultNxPlugins:start');
|
||||||
|
|
||||||
|
const plugins = getDefaultPlugins(root);
|
||||||
|
|
||||||
|
const cleanupFunctions: Array<() => void> = [];
|
||||||
|
const ret = [
|
||||||
|
await Promise.all(
|
||||||
|
plugins.map(async (plugin) => {
|
||||||
|
performance.mark(`Load Nx Plugin: ${plugin} - start`);
|
||||||
|
|
||||||
|
const [loadedPluginPromise, cleanup] = await loadingMethod(
|
||||||
|
plugin,
|
||||||
|
root
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanupFunctions.push(cleanup);
|
||||||
|
const res = await loadedPluginPromise;
|
||||||
|
performance.mark(`Load Nx Plugin: ${plugin} - end`);
|
||||||
|
performance.measure(
|
||||||
|
`Load Nx Plugin: ${plugin}`,
|
||||||
|
`Load Nx Plugin: ${plugin} - start`,
|
||||||
|
`Load Nx Plugin: ${plugin} - end`
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
() => {
|
||||||
|
for (const fn of cleanupFunctions) {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
if (pluginTranspilerIsRegistered()) {
|
||||||
|
cleanupPluginTSTranspiler();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
performance.mark('loadDefaultNxPlugins:end');
|
||||||
|
performance.measure(
|
||||||
|
'loadDefaultNxPlugins',
|
||||||
|
'loadDefaultNxPlugins:start',
|
||||||
|
'loadDefaultNxPlugins:end'
|
||||||
|
);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSpecifiedNxPlugins(
|
||||||
|
plugins: PluginConfiguration[],
|
||||||
|
root = workspaceRoot
|
||||||
|
): Promise<readonly [LoadedNxPlugin[], () => void]> {
|
||||||
|
performance.mark('loadSpecifiedNxPlugins:start');
|
||||||
|
|
||||||
|
plugins ??= [];
|
||||||
|
|
||||||
|
const cleanupFunctions: Array<() => void> = [];
|
||||||
|
const ret = [
|
||||||
|
await Promise.all(
|
||||||
|
plugins.map(async (plugin) => {
|
||||||
|
const pluginPath = typeof plugin === 'string' ? plugin : plugin.plugin;
|
||||||
|
performance.mark(`Load Nx Plugin: ${pluginPath} - start`);
|
||||||
|
|
||||||
|
const [loadedPluginPromise, cleanup] = await loadingMethod(
|
||||||
|
plugin,
|
||||||
|
root
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanupFunctions.push(cleanup);
|
||||||
|
const res = await loadedPluginPromise;
|
||||||
|
performance.mark(`Load Nx Plugin: ${pluginPath} - end`);
|
||||||
|
performance.measure(
|
||||||
|
`Load Nx Plugin: ${pluginPath}`,
|
||||||
|
`Load Nx Plugin: ${pluginPath} - start`,
|
||||||
|
`Load Nx Plugin: ${pluginPath} - end`
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
() => {
|
||||||
|
for (const fn of cleanupFunctions) {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
if (pluginTranspilerIsRegistered()) {
|
||||||
|
cleanupPluginTSTranspiler();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
performance.mark('loadSpecifiedNxPlugins:end');
|
||||||
|
performance.measure(
|
||||||
|
'loadSpecifiedNxPlugins',
|
||||||
|
'loadSpecifiedNxPlugins:start',
|
||||||
|
'loadSpecifiedNxPlugins:end'
|
||||||
|
);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultPlugins(root: string) {
|
||||||
|
return [
|
||||||
|
join(__dirname, '../../plugins/js'),
|
||||||
|
...(shouldMergeAngularProjects(root, false)
|
||||||
|
? [join(__dirname, '../../adapter/angular-json')]
|
||||||
|
: []),
|
||||||
|
join(__dirname, '../../plugins/package-json'),
|
||||||
|
join(__dirname, '../../plugins/project-json/build-nodes/project-json'),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
85
packages/nx/src/project-graph/plugins/in-process-loader.ts
Normal file
85
packages/nx/src/project-graph/plugins/in-process-loader.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// This file contains methods and utilities that should **only** be used by the plugin worker.
|
||||||
|
|
||||||
|
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||||
|
|
||||||
|
import { getNxRequirePaths } from '../../utils/installation-directory';
|
||||||
|
import {
|
||||||
|
PackageJson,
|
||||||
|
readModulePackageJsonWithoutFallbacks,
|
||||||
|
} from '../../utils/package-json';
|
||||||
|
import { readJsonFile } from '../../utils/fileutils';
|
||||||
|
|
||||||
|
import type { PluginConfiguration } from '../../config/nx-json';
|
||||||
|
import type { LoadedNxPlugin } from './loaded-nx-plugin';
|
||||||
|
import { LoadPluginError } from '../error-types';
|
||||||
|
import path = require('node:path/posix');
|
||||||
|
import { loadResolvedNxPluginAsync } from './load-resolved-plugin';
|
||||||
|
import { resolveLocalNxPlugin, resolveNxPlugin } from './resolve-plugin';
|
||||||
|
import {
|
||||||
|
pluginTranspilerIsRegistered,
|
||||||
|
registerPluginTSTranspiler,
|
||||||
|
} from './transpiler';
|
||||||
|
|
||||||
|
export function readPluginPackageJson(
|
||||||
|
pluginName: string,
|
||||||
|
projects: Record<string, ProjectConfiguration>,
|
||||||
|
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'
|
||||||
|
);
|
||||||
|
if (pluginTranspilerIsRegistered()) {
|
||||||
|
registerPluginTSTranspiler();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path: localPluginPackageJson,
|
||||||
|
json: readJsonFile(localPluginPackageJson),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadNxPlugin(plugin: PluginConfiguration, root: string) {
|
||||||
|
return [
|
||||||
|
loadNxPluginAsync(plugin, getNxRequirePaths(root), root),
|
||||||
|
() => {},
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadNxPluginAsync(
|
||||||
|
pluginConfiguration: PluginConfiguration,
|
||||||
|
paths: string[],
|
||||||
|
root: string
|
||||||
|
): Promise<LoadedNxPlugin> {
|
||||||
|
const moduleName =
|
||||||
|
typeof pluginConfiguration === 'string'
|
||||||
|
? pluginConfiguration
|
||||||
|
: pluginConfiguration.plugin;
|
||||||
|
try {
|
||||||
|
const { pluginPath, name, shouldRegisterTSTranspiler } =
|
||||||
|
await resolveNxPlugin(moduleName, root, paths);
|
||||||
|
|
||||||
|
if (shouldRegisterTSTranspiler) {
|
||||||
|
registerPluginTSTranspiler();
|
||||||
|
}
|
||||||
|
return loadResolvedNxPluginAsync(pluginConfiguration, pluginPath, name);
|
||||||
|
} catch (e) {
|
||||||
|
throw new LoadPluginError(moduleName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,5 +2,6 @@ export * from './public-api';
|
|||||||
|
|
||||||
// export * from './get-plugins';
|
// export * from './get-plugins';
|
||||||
|
|
||||||
export { readPluginPackageJson, registerPluginTSTranspiler } from './loader';
|
export { readPluginPackageJson } from './in-process-loader';
|
||||||
|
export { registerPluginTSTranspiler } from './transpiler';
|
||||||
export { createNodesFromFiles } from './utils';
|
export { createNodesFromFiles } from './utils';
|
||||||
|
|||||||
@ -1,224 +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 { shouldMergeAngularProjects } from '../../adapter/angular-json';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CreateDependencies,
|
|
||||||
CreateDependenciesContext,
|
|
||||||
CreateMetadata,
|
|
||||||
CreateMetadataContext,
|
|
||||||
CreateNodesContextV2,
|
|
||||||
CreateNodesResult,
|
|
||||||
NxPluginV2,
|
|
||||||
ProjectsMetadata,
|
|
||||||
} from './public-api';
|
|
||||||
import { ProjectGraph } from '../../config/project-graph';
|
|
||||||
import { loadNxPluginInIsolation } from './isolation';
|
|
||||||
import { loadNxPlugin, unregisterPluginTSTranspiler } from './loader';
|
|
||||||
import { createNodesFromFiles } from './utils';
|
|
||||||
import {
|
|
||||||
AggregateCreateNodesError,
|
|
||||||
isAggregateCreateNodesError,
|
|
||||||
} from '../error-types';
|
|
||||||
import { IS_WASM } from '../../native';
|
|
||||||
import { RawProjectGraphDependency } from '../project-graph-builder';
|
|
||||||
|
|
||||||
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: CreateNodesContextV2
|
|
||||||
) => Promise<
|
|
||||||
Array<readonly [plugin: string, file: string, result: CreateNodesResult]>
|
|
||||||
>
|
|
||||||
];
|
|
||||||
readonly createDependencies?: (
|
|
||||||
context: CreateDependenciesContext
|
|
||||||
) => Promise<RawProjectGraphDependency[]>;
|
|
||||||
readonly createMetadata?: (
|
|
||||||
graph: ProjectGraph,
|
|
||||||
context: CreateMetadataContext
|
|
||||||
) => Promise<ProjectsMetadata>;
|
|
||||||
|
|
||||||
readonly options?: unknown;
|
|
||||||
readonly include?: string[];
|
|
||||||
readonly exclude?: string[];
|
|
||||||
|
|
||||||
constructor(plugin: NxPluginV2, 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 && !plugin.createNodesV2) {
|
|
||||||
this.createNodes = [
|
|
||||||
plugin.createNodes[0],
|
|
||||||
(configFiles, context) =>
|
|
||||||
createNodesFromFiles(
|
|
||||||
plugin.createNodes[1],
|
|
||||||
configFiles,
|
|
||||||
this.options,
|
|
||||||
context
|
|
||||||
).then((results) => results.map((r) => [this.name, r[0], r[1]])),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.createNodesV2) {
|
|
||||||
this.createNodes = [
|
|
||||||
plugin.createNodesV2[0],
|
|
||||||
async (configFiles, context) => {
|
|
||||||
const result = await plugin.createNodesV2[1](
|
|
||||||
configFiles,
|
|
||||||
this.options,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
return result.map((r) => [this.name, r[0], r[1]]);
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.createNodes) {
|
|
||||||
const inner = this.createNodes[1];
|
|
||||||
this.createNodes[1] = async (...args) => {
|
|
||||||
performance.mark(`${plugin.name}:createNodes - start`);
|
|
||||||
try {
|
|
||||||
return await inner(...args);
|
|
||||||
} catch (e) {
|
|
||||||
if (isAggregateCreateNodesError(e)) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
// The underlying plugin errored out. We can't know any partial results.
|
|
||||||
throw new AggregateCreateNodesError([[null, e]], []);
|
|
||||||
} finally {
|
|
||||||
performance.mark(`${plugin.name}:createNodes - end`);
|
|
||||||
performance.measure(
|
|
||||||
`${plugin.name}:createNodes`,
|
|
||||||
`${plugin.name}:createNodes - start`,
|
|
||||||
`${plugin.name}:createNodes - end`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.createDependencies) {
|
|
||||||
this.createDependencies = async (context) =>
|
|
||||||
plugin.createDependencies(this.options, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.createMetadata) {
|
|
||||||
this.createMetadata = async (graph, context) =>
|
|
||||||
plugin.createMetadata(graph, this.options, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CreateNodesResultWithContext = CreateNodesResult & {
|
|
||||||
file: string;
|
|
||||||
pluginName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function isIsolationEnabled() {
|
|
||||||
// Explicitly enabled, regardless of further conditions
|
|
||||||
if (process.env.NX_ISOLATE_PLUGINS === 'true') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
// Explicitly disabled
|
|
||||||
process.env.NX_ISOLATE_PLUGINS === 'false' ||
|
|
||||||
// Isolation is disabled on WASM builds currently.
|
|
||||||
IS_WASM
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Default value
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use `getPlugins` instead.
|
|
||||||
* @deprecated Do not use this. Use `getPlugins` instead.
|
|
||||||
*/
|
|
||||||
export async function loadNxPlugins(
|
|
||||||
plugins: PluginConfiguration[],
|
|
||||||
root = workspaceRoot
|
|
||||||
): Promise<readonly [LoadedNxPlugin[], () => void]> {
|
|
||||||
performance.mark('loadNxPlugins:start');
|
|
||||||
const loadingMethod = isIsolationEnabled()
|
|
||||||
? loadNxPluginInIsolation
|
|
||||||
: loadNxPlugin;
|
|
||||||
|
|
||||||
plugins = await normalizePlugins(plugins, root);
|
|
||||||
|
|
||||||
const cleanupFunctions: Array<() => void> = [];
|
|
||||||
const ret = [
|
|
||||||
await Promise.all(
|
|
||||||
plugins.map(async (plugin) => {
|
|
||||||
const pluginPath = typeof plugin === 'string' ? plugin : plugin.plugin;
|
|
||||||
performance.mark(`Load Nx Plugin: ${pluginPath} - start`);
|
|
||||||
|
|
||||||
const [loadedPluginPromise, cleanup] = await loadingMethod(
|
|
||||||
plugin,
|
|
||||||
root
|
|
||||||
);
|
|
||||||
|
|
||||||
cleanupFunctions.push(cleanup);
|
|
||||||
const res = await loadedPluginPromise;
|
|
||||||
performance.mark(`Load Nx Plugin: ${pluginPath} - end`);
|
|
||||||
performance.measure(
|
|
||||||
`Load Nx Plugin: ${pluginPath}`,
|
|
||||||
`Load Nx Plugin: ${pluginPath} - start`,
|
|
||||||
`Load Nx Plugin: ${pluginPath} - end`
|
|
||||||
);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
),
|
|
||||||
() => {
|
|
||||||
for (const fn of cleanupFunctions) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
if (unregisterPluginTSTranspiler) {
|
|
||||||
unregisterPluginTSTranspiler();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
performance.mark('loadNxPlugins:end');
|
|
||||||
performance.measure(
|
|
||||||
'loadNxPlugins',
|
|
||||||
'loadNxPlugins:start',
|
|
||||||
'loadNxPlugins:end'
|
|
||||||
);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function normalizePlugins(plugins: PluginConfiguration[], root: string) {
|
|
||||||
plugins ??= [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
...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) {
|
|
||||||
return [
|
|
||||||
join(__dirname, '../../plugins/js'),
|
|
||||||
...(shouldMergeAngularProjects(root, false)
|
|
||||||
? [join(__dirname, '../../adapter/angular-json')]
|
|
||||||
: []),
|
|
||||||
join(__dirname, '../../plugins/package-json'),
|
|
||||||
join(__dirname, '../../plugins/project-json/build-nodes/project-json'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { workspaceRoot } from '../../../utils/workspace-root';
|
import { workspaceRoot } from '../../../utils/workspace-root';
|
||||||
import { PluginConfiguration } from '../../../config/nx-json';
|
import type { PluginConfiguration } from '../../../config/nx-json';
|
||||||
import { LoadedNxPlugin } from '../internal-api';
|
import type { LoadedNxPlugin } from '../loaded-nx-plugin';
|
||||||
import { loadRemoteNxPlugin } from './plugin-pool';
|
import { loadRemoteNxPlugin } from './plugin-pool';
|
||||||
|
|
||||||
export async function loadNxPluginInIsolation(
|
export async function loadNxPluginInIsolation(
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
CreateMetadataContext,
|
CreateMetadataContext,
|
||||||
CreateNodesContextV2,
|
CreateNodesContextV2,
|
||||||
} from '../public-api';
|
} from '../public-api';
|
||||||
import { LoadedNxPlugin } from '../internal-api';
|
import type { LoadedNxPlugin } from '../loaded-nx-plugin';
|
||||||
import { Serializable } from 'child_process';
|
import { Serializable } from 'child_process';
|
||||||
import { Socket } from 'net';
|
import { Socket } from 'net';
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { PluginConfiguration } from '../../../config/nx-json';
|
|||||||
// TODO (@AgentEnder): After scoped verbose logging is implemented, re-add verbose logs here.
|
// TODO (@AgentEnder): After scoped verbose logging is implemented, re-add verbose logs here.
|
||||||
// import { logger } from '../../utils/logger';
|
// import { logger } from '../../utils/logger';
|
||||||
|
|
||||||
import { LoadedNxPlugin } from '../internal-api';
|
import type { LoadedNxPlugin } from '../loaded-nx-plugin';
|
||||||
import { getPluginOsSocketPath } from '../../../daemon/socket-utils';
|
import { getPluginOsSocketPath } from '../../../daemon/socket-utils';
|
||||||
import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-socket';
|
import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-socket';
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
sendMessageOverSocket,
|
sendMessageOverSocket,
|
||||||
} from './messaging';
|
} from './messaging';
|
||||||
import { getNxRequirePaths } from '../../../utils/installation-directory';
|
import { getNxRequirePaths } from '../../../utils/installation-directory';
|
||||||
import { resolveNxPlugin } from '../loader';
|
import { resolveNxPlugin } from '../resolve-plugin';
|
||||||
|
|
||||||
const cleanupFunctions = new Set<() => void>();
|
const cleanupFunctions = new Set<() => void>();
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-
|
|||||||
|
|
||||||
import { createServer } from 'net';
|
import { createServer } from 'net';
|
||||||
import { unlinkSync } from 'fs';
|
import { unlinkSync } from 'fs';
|
||||||
import { registerPluginTSTranspiler } from '../loader';
|
|
||||||
|
|
||||||
if (process.env.NX_PERF_LOGGING === 'true') {
|
if (process.env.NX_PERF_LOGGING === 'true') {
|
||||||
require('../../../utils/perf-logging');
|
require('../../../utils/perf-logging');
|
||||||
@ -53,7 +52,9 @@ const server = createServer((socket) => {
|
|||||||
// Register the ts-transpiler if we are pointing to a
|
// Register the ts-transpiler if we are pointing to a
|
||||||
// plain ts file that's not part of a plugin project
|
// plain ts file that's not part of a plugin project
|
||||||
if (shouldRegisterTSTranspiler) {
|
if (shouldRegisterTSTranspiler) {
|
||||||
registerPluginTSTranspiler();
|
(
|
||||||
|
require('../transpiler') as typeof import('../transpiler')
|
||||||
|
).registerPluginTSTranspiler();
|
||||||
}
|
}
|
||||||
plugin = await loadResolvedNxPluginAsync(
|
plugin = await loadResolvedNxPluginAsync(
|
||||||
pluginConfiguration,
|
pluginConfiguration,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { PluginConfiguration } from '../../config/nx-json';
|
import type { PluginConfiguration } from '../../config/nx-json';
|
||||||
import { LoadedNxPlugin } from './internal-api';
|
import { LoadedNxPlugin } from './loaded-nx-plugin';
|
||||||
import { NxPlugin } from './public-api';
|
import { NxPlugin } from './public-api';
|
||||||
|
|
||||||
export async function loadResolvedNxPluginAsync(
|
export async function loadResolvedNxPluginAsync(
|
||||||
|
|||||||
116
packages/nx/src/project-graph/plugins/loaded-nx-plugin.ts
Normal file
116
packages/nx/src/project-graph/plugins/loaded-nx-plugin.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import type { ProjectGraph } from '../../config/project-graph';
|
||||||
|
import type { PluginConfiguration } from '../../config/nx-json';
|
||||||
|
import {
|
||||||
|
AggregateCreateNodesError,
|
||||||
|
isAggregateCreateNodesError,
|
||||||
|
} from '../error-types';
|
||||||
|
import type { RawProjectGraphDependency } from '../project-graph-builder';
|
||||||
|
import type {
|
||||||
|
CreateDependenciesContext,
|
||||||
|
CreateMetadataContext,
|
||||||
|
CreateNodesContextV2,
|
||||||
|
CreateNodesResult,
|
||||||
|
NxPluginV2,
|
||||||
|
ProjectsMetadata,
|
||||||
|
} from './public-api';
|
||||||
|
import { createNodesFromFiles } from './utils';
|
||||||
|
|
||||||
|
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: CreateNodesContextV2
|
||||||
|
) => Promise<
|
||||||
|
Array<readonly [plugin: string, file: string, result: CreateNodesResult]>
|
||||||
|
>
|
||||||
|
];
|
||||||
|
readonly createDependencies?: (
|
||||||
|
context: CreateDependenciesContext
|
||||||
|
) => Promise<RawProjectGraphDependency[]>;
|
||||||
|
readonly createMetadata?: (
|
||||||
|
graph: ProjectGraph,
|
||||||
|
context: CreateMetadataContext
|
||||||
|
) => Promise<ProjectsMetadata>;
|
||||||
|
|
||||||
|
readonly options?: unknown;
|
||||||
|
readonly include?: string[];
|
||||||
|
readonly exclude?: string[];
|
||||||
|
|
||||||
|
constructor(plugin: NxPluginV2, 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 && !plugin.createNodesV2) {
|
||||||
|
this.createNodes = [
|
||||||
|
plugin.createNodes[0],
|
||||||
|
(configFiles, context) =>
|
||||||
|
createNodesFromFiles(
|
||||||
|
plugin.createNodes[1],
|
||||||
|
configFiles,
|
||||||
|
this.options,
|
||||||
|
context
|
||||||
|
).then((results) => results.map((r) => [this.name, r[0], r[1]])),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.createNodesV2) {
|
||||||
|
this.createNodes = [
|
||||||
|
plugin.createNodesV2[0],
|
||||||
|
async (configFiles, context) => {
|
||||||
|
const result = await plugin.createNodesV2[1](
|
||||||
|
configFiles,
|
||||||
|
this.options,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
return result.map((r) => [this.name, r[0], r[1]]);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.createNodes) {
|
||||||
|
const inner = this.createNodes[1];
|
||||||
|
this.createNodes[1] = async (...args) => {
|
||||||
|
performance.mark(`${plugin.name}:createNodes - start`);
|
||||||
|
try {
|
||||||
|
return await inner(...args);
|
||||||
|
} catch (e) {
|
||||||
|
if (isAggregateCreateNodesError(e)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// The underlying plugin errored out. We can't know any partial results.
|
||||||
|
throw new AggregateCreateNodesError([[null, e]], []);
|
||||||
|
} finally {
|
||||||
|
performance.mark(`${plugin.name}:createNodes - end`);
|
||||||
|
performance.measure(
|
||||||
|
`${plugin.name}:createNodes`,
|
||||||
|
`${plugin.name}:createNodes - start`,
|
||||||
|
`${plugin.name}:createNodes - end`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.createDependencies) {
|
||||||
|
this.createDependencies = async (context) =>
|
||||||
|
plugin.createDependencies(this.options, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.createMetadata) {
|
||||||
|
this.createMetadata = async (graph, context) =>
|
||||||
|
plugin.createMetadata(graph, this.options, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateNodesResultWithContext = CreateNodesResult & {
|
||||||
|
file: string;
|
||||||
|
pluginName: string;
|
||||||
|
};
|
||||||
@ -1,71 +1,59 @@
|
|||||||
// This file contains methods and utilities that should **only** be used by the plugin worker.
|
import * as path from 'node:path';
|
||||||
|
|
||||||
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 { existsSync } from 'node:fs';
|
||||||
import {
|
|
||||||
registerTranspiler,
|
|
||||||
registerTsConfigPaths,
|
|
||||||
} from '../../plugins/js/utils/register';
|
|
||||||
import {
|
|
||||||
ProjectRootMappings,
|
|
||||||
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 type { PluginConfiguration } from '../../config/nx-json';
|
|
||||||
import { retrieveProjectConfigurationsWithoutPluginInference } from '../utils/retrieve-workspace-files';
|
|
||||||
import { LoadedNxPlugin } from './internal-api';
|
|
||||||
import { LoadPluginError } from '../error-types';
|
|
||||||
import path = require('node:path/posix');
|
|
||||||
import { readTsConfig } from '../../plugins/js/utils/typescript';
|
|
||||||
import { loadResolvedNxPluginAsync } from './load-resolved-plugin';
|
|
||||||
import { getPackageEntryPointsToProjectMap } from '../../plugins/js/utils/packages';
|
import { getPackageEntryPointsToProjectMap } from '../../plugins/js/utils/packages';
|
||||||
|
import { readJsonFile } from '../../utils/fileutils';
|
||||||
|
import { logger } from '../../utils/logger';
|
||||||
|
import { normalizePath } from '../../utils/path';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import {
|
||||||
|
findProjectForPath,
|
||||||
|
ProjectRootMappings,
|
||||||
|
} from '../utils/find-project-for-path';
|
||||||
|
import { retrieveProjectConfigurationsWithoutPluginInference } from '../utils/retrieve-workspace-files';
|
||||||
|
|
||||||
export function readPluginPackageJson(
|
import type { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||||
pluginName: string,
|
|
||||||
projects: Record<string, ProjectConfiguration>,
|
let projectsWithoutInference: Record<string, ProjectConfiguration>;
|
||||||
paths = getNxRequirePaths()
|
|
||||||
): {
|
export async function resolveNxPlugin(
|
||||||
path: string;
|
moduleName: string,
|
||||||
json: PackageJson;
|
root: string,
|
||||||
} {
|
paths: string[]
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const result = readModulePackageJsonWithoutFallbacks(pluginName, paths);
|
require.resolve(moduleName);
|
||||||
return {
|
} catch {
|
||||||
json: result.packageJson,
|
// If a plugin cannot be resolved, we will need projects to resolve it
|
||||||
path: result.path,
|
projectsWithoutInference ??=
|
||||||
};
|
await retrieveProjectConfigurationsWithoutPluginInference(root);
|
||||||
} catch (e) {
|
|
||||||
if (e.code === 'MODULE_NOT_FOUND') {
|
|
||||||
const localPluginPath = resolveLocalNxPlugin(pluginName, projects);
|
|
||||||
if (localPluginPath) {
|
|
||||||
const localPluginPackageJson = path.join(
|
|
||||||
localPluginPath.path,
|
|
||||||
'package.json'
|
|
||||||
);
|
|
||||||
if (!unregisterPluginTSTranspiler) {
|
|
||||||
registerPluginTSTranspiler();
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path: localPluginPackageJson,
|
|
||||||
json: readJsonFile(localPluginPackageJson),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
const { pluginPath, name, shouldRegisterTSTranspiler } = getPluginPathAndName(
|
||||||
|
moduleName,
|
||||||
|
paths,
|
||||||
|
projectsWithoutInference,
|
||||||
|
root
|
||||||
|
);
|
||||||
|
return { pluginPath, name, shouldRegisterTSTranspiler };
|
||||||
|
}
|
||||||
|
|
||||||
|
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 resolveLocalNxPlugin(
|
export function resolveLocalNxPlugin(
|
||||||
@ -76,40 +64,45 @@ export function resolveLocalNxPlugin(
|
|||||||
return lookupLocalPlugin(importPath, projects, root);
|
return lookupLocalPlugin(importPath, projects, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
export let unregisterPluginTSTranspiler: (() => void) | null = null;
|
export function getPluginPathAndName(
|
||||||
|
moduleName: string,
|
||||||
/**
|
paths: string[],
|
||||||
* Register swc-node or ts-node if they are not currently registered
|
projects: Record<string, ProjectConfiguration>,
|
||||||
* with some default settings which work well for Nx plugins.
|
root: string
|
||||||
*/
|
) {
|
||||||
export function registerPluginTSTranspiler() {
|
let pluginPath: string;
|
||||||
// Get the first tsconfig that matches the allowed set
|
let shouldRegisterTSTranspiler = false;
|
||||||
const tsConfigName = [
|
try {
|
||||||
join(workspaceRoot, 'tsconfig.base.json'),
|
pluginPath = require.resolve(moduleName, {
|
||||||
join(workspaceRoot, 'tsconfig.json'),
|
paths,
|
||||||
].find((x) => existsSync(x));
|
});
|
||||||
|
const extension = path.extname(pluginPath);
|
||||||
if (!tsConfigName) {
|
shouldRegisterTSTranspiler = extension === '.ts';
|
||||||
return;
|
} catch (e) {
|
||||||
|
if (e.code === 'MODULE_NOT_FOUND') {
|
||||||
|
const plugin = resolveLocalNxPlugin(moduleName, projects, root);
|
||||||
|
if (plugin) {
|
||||||
|
shouldRegisterTSTranspiler = 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');
|
||||||
|
|
||||||
const tsConfig: Partial<ts.ParsedCommandLine> = tsConfigName
|
const { name } =
|
||||||
? readTsConfig(tsConfigName)
|
!['.ts', '.js'].some((x) => path.extname(moduleName) === x) && // Not trying to point to a ts or js file
|
||||||
: {};
|
existsSync(packageJsonPath) // plugin has a package.json
|
||||||
const cleanupFns = [
|
? readJsonFile(packageJsonPath) // read name from package.json
|
||||||
registerTsConfigPaths(tsConfigName),
|
: { name: moduleName };
|
||||||
registerTranspiler(
|
return { pluginPath, name, shouldRegisterTSTranspiler };
|
||||||
{
|
|
||||||
experimentalDecorators: true,
|
|
||||||
emitDecoratorMetadata: true,
|
|
||||||
...tsConfig.options,
|
|
||||||
},
|
|
||||||
tsConfig.raw
|
|
||||||
),
|
|
||||||
];
|
|
||||||
unregisterPluginTSTranspiler = () => {
|
|
||||||
cleanupFns.forEach((fn) => fn?.());
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function lookupLocalPlugin(
|
function lookupLocalPlugin(
|
||||||
@ -184,115 +177,3 @@ function readTsConfigPaths(root: string = workspaceRoot) {
|
|||||||
}
|
}
|
||||||
return tsconfigPaths ?? {};
|
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<string, ProjectConfiguration>,
|
|
||||||
root: string
|
|
||||||
) {
|
|
||||||
let pluginPath: string;
|
|
||||||
let shouldRegisterTSTranspiler = false;
|
|
||||||
try {
|
|
||||||
pluginPath = require.resolve(moduleName, {
|
|
||||||
paths,
|
|
||||||
});
|
|
||||||
const extension = path.extname(pluginPath);
|
|
||||||
shouldRegisterTSTranspiler = extension === '.ts';
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === 'MODULE_NOT_FOUND') {
|
|
||||||
const plugin = resolveLocalNxPlugin(moduleName, projects, root);
|
|
||||||
if (plugin) {
|
|
||||||
shouldRegisterTSTranspiler = 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');
|
|
||||||
|
|
||||||
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, shouldRegisterTSTranspiler };
|
|
||||||
}
|
|
||||||
|
|
||||||
let projectsWithoutInference: Record<string, ProjectConfiguration>;
|
|
||||||
|
|
||||||
export function loadNxPlugin(plugin: PluginConfiguration, root: string) {
|
|
||||||
return [
|
|
||||||
loadNxPluginAsync(plugin, getNxRequirePaths(root), root),
|
|
||||||
() => {},
|
|
||||||
] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function resolveNxPlugin(
|
|
||||||
moduleName: string,
|
|
||||||
root: string,
|
|
||||||
paths: string[]
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
require.resolve(moduleName);
|
|
||||||
} catch {
|
|
||||||
// If a plugin cannot be resolved, we will need projects to resolve it
|
|
||||||
projectsWithoutInference ??=
|
|
||||||
await retrieveProjectConfigurationsWithoutPluginInference(root);
|
|
||||||
}
|
|
||||||
const { pluginPath, name, shouldRegisterTSTranspiler } = getPluginPathAndName(
|
|
||||||
moduleName,
|
|
||||||
paths,
|
|
||||||
projectsWithoutInference,
|
|
||||||
root
|
|
||||||
);
|
|
||||||
return { pluginPath, name, shouldRegisterTSTranspiler };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadNxPluginAsync(
|
|
||||||
pluginConfiguration: PluginConfiguration,
|
|
||||||
paths: string[],
|
|
||||||
root: string
|
|
||||||
): Promise<LoadedNxPlugin> {
|
|
||||||
const moduleName =
|
|
||||||
typeof pluginConfiguration === 'string'
|
|
||||||
? pluginConfiguration
|
|
||||||
: pluginConfiguration.plugin;
|
|
||||||
try {
|
|
||||||
const { pluginPath, name, shouldRegisterTSTranspiler } =
|
|
||||||
await resolveNxPlugin(moduleName, root, paths);
|
|
||||||
|
|
||||||
if (shouldRegisterTSTranspiler) {
|
|
||||||
registerPluginTSTranspiler();
|
|
||||||
}
|
|
||||||
return loadResolvedNxPluginAsync(pluginConfiguration, pluginPath, name);
|
|
||||||
} catch (e) {
|
|
||||||
throw new LoadPluginError(moduleName, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
packages/nx/src/project-graph/plugins/transpiler.ts
Normal file
54
packages/nx/src/project-graph/plugins/transpiler.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { existsSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path/posix';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import {
|
||||||
|
registerTranspiler,
|
||||||
|
registerTsConfigPaths,
|
||||||
|
} from '../../plugins/js/utils/register';
|
||||||
|
import { readTsConfig } from '../../plugins/js/utils/typescript';
|
||||||
|
import type * as ts from 'typescript';
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
|
||||||
|
if (!tsConfigName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tsConfig: Partial<ts.ParsedCommandLine> = tsConfigName
|
||||||
|
? readTsConfig(tsConfigName)
|
||||||
|
: {};
|
||||||
|
const cleanupFns = [
|
||||||
|
registerTsConfigPaths(tsConfigName),
|
||||||
|
registerTranspiler(
|
||||||
|
{
|
||||||
|
experimentalDecorators: true,
|
||||||
|
emitDecoratorMetadata: true,
|
||||||
|
...tsConfig.options,
|
||||||
|
},
|
||||||
|
tsConfig.raw
|
||||||
|
),
|
||||||
|
];
|
||||||
|
unregisterPluginTSTranspiler = () => {
|
||||||
|
cleanupFns.forEach((fn) => fn?.());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pluginTranspilerIsRegistered() {
|
||||||
|
return unregisterPluginTSTranspiler !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanupPluginTSTranspiler() {
|
||||||
|
unregisterPluginTSTranspiler?.();
|
||||||
|
unregisterPluginTSTranspiler = null;
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
readTargetDefaultsForTarget,
|
readTargetDefaultsForTarget,
|
||||||
} from './project-configuration-utils';
|
} from './project-configuration-utils';
|
||||||
import { NxPluginV2 } from '../plugins';
|
import { NxPluginV2 } from '../plugins';
|
||||||
import { LoadedNxPlugin } from '../plugins/internal-api';
|
import { LoadedNxPlugin } from '../plugins/loaded-nx-plugin';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { isProjectConfigurationsError } from '../error-types';
|
import { isProjectConfigurationsError } from '../error-types';
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { minimatch } from 'minimatch';
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
|
|
||||||
import { LoadedNxPlugin } from '../plugins/internal-api';
|
import { LoadedNxPlugin } from '../plugins/loaded-nx-plugin';
|
||||||
import {
|
import {
|
||||||
MergeNodesError,
|
MergeNodesError,
|
||||||
ProjectConfigurationsError,
|
ProjectConfigurationsError,
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
ConfigurationResult,
|
ConfigurationResult,
|
||||||
createProjectConfigurations,
|
createProjectConfigurations,
|
||||||
} from './project-configuration-utils';
|
} from './project-configuration-utils';
|
||||||
import { LoadedNxPlugin } from '../plugins/internal-api';
|
import { LoadedNxPlugin } from '../plugins/loaded-nx-plugin';
|
||||||
import {
|
import {
|
||||||
getNxWorkspaceFilesFromContext,
|
getNxWorkspaceFilesFromContext,
|
||||||
globWithWorkspaceContext,
|
globWithWorkspaceContext,
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
|||||||
import { readJsonFile } from '../fileutils';
|
import { readJsonFile } from '../fileutils';
|
||||||
import { getNxRequirePaths } from '../installation-directory';
|
import { getNxRequirePaths } from '../installation-directory';
|
||||||
import { readPluginPackageJson } from '../../project-graph/plugins';
|
import { readPluginPackageJson } from '../../project-graph/plugins';
|
||||||
import { loadNxPlugin } from '../../project-graph/plugins/loader';
|
import { loadNxPlugin } from '../../project-graph/plugins/in-process-loader';
|
||||||
import { PackageJson } from '../package-json';
|
import { PackageJson } from '../package-json';
|
||||||
import { LoadedNxPlugin } from '../../project-graph/plugins/internal-api';
|
import { LoadedNxPlugin } from '../../project-graph/plugins/loaded-nx-plugin';
|
||||||
|
|
||||||
export interface PluginCapabilities {
|
export interface PluginCapabilities {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
parseTargetString,
|
parseTargetString,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { addE2eCiTargetDefaults as _addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
import { addE2eCiTargetDefaults as _addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||||
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api';
|
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/loaded-nx-plugin';
|
||||||
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||||
import {
|
import {
|
||||||
ProjectConfigurationsError,
|
ProjectConfigurationsError,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
import addE2eCiTargetDefaults from './add-e2e-ci-target-defaults';
|
import addE2eCiTargetDefaults from './add-e2e-ci-target-defaults';
|
||||||
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||||
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api';
|
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/loaded-nx-plugin';
|
||||||
import { retrieveProjectConfigurations } from 'nx/src/project-graph/utils/retrieve-workspace-files';
|
import { retrieveProjectConfigurations } from 'nx/src/project-graph/utils/retrieve-workspace-files';
|
||||||
import { ProjectConfigurationsError } from 'nx/src/project-graph/error-types';
|
import { ProjectConfigurationsError } from 'nx/src/project-graph/error-types';
|
||||||
import { createNodesV2 as webpackCreateNodesV2 } from '@nx/webpack/src/plugins/plugin';
|
import { createNodesV2 as webpackCreateNodesV2 } from '@nx/webpack/src/plugins/plugin';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user