feat(core): re-enable running plugins in isolation (#22527)
This commit is contained in:
parent
caf663fd32
commit
7a7cbeca44
@ -1,6 +1,6 @@
|
|||||||
# Type alias: CreateNodes\<T\>
|
# Type alias: CreateNodes\<T\>
|
||||||
|
|
||||||
Ƭ **CreateNodes**\<`T`\>: readonly [configFilePattern: string, createNodesFunction: CreateNodesFunction\<T\>]
|
Ƭ **CreateNodes**\<`T`\>: readonly [projectFilePattern: string, createNodesFunction: CreateNodesFunction\<T\>]
|
||||||
|
|
||||||
A pair of file patterns and [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
|
A pair of file patterns and [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
|
||||||
|
|
||||||
|
|||||||
12
docs/generated/devkit/ExpandedPluginConfiguration.md
Normal file
12
docs/generated/devkit/ExpandedPluginConfiguration.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Type alias: ExpandedPluginConfiguration
|
||||||
|
|
||||||
|
Ƭ **ExpandedPluginConfiguration**: `Object`
|
||||||
|
|
||||||
|
#### Type declaration
|
||||||
|
|
||||||
|
| Name | Type |
|
||||||
|
| :--------- | :--------- |
|
||||||
|
| `exclude?` | `string`[] |
|
||||||
|
| `include?` | `string`[] |
|
||||||
|
| `options?` | `unknown` |
|
||||||
|
| `plugin` | `string` |
|
||||||
@ -15,5 +15,5 @@ A plugin for Nx which creates nodes and dependencies for the [ProjectGraph](../.
|
|||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| :-------------------- | :------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- |
|
| :-------------------- | :------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies)\<`TOptions`\> | Provides a function to analyze files to create dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) |
|
| `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies)\<`TOptions`\> | Provides a function to analyze files to create dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) |
|
||||||
| `createNodes?` | [`CreateNodes`](../../devkit/documents/CreateNodes) | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } |
|
| `createNodes?` | [`CreateNodes`](../../devkit/documents/CreateNodes)\<`TOptions`\> | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } |
|
||||||
| `name` | `string` | - |
|
| `name` | `string` | - |
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
# Type alias: PluginConfiguration
|
# Type alias: PluginConfiguration
|
||||||
|
|
||||||
Ƭ **PluginConfiguration**: `string` \| \{ `exclude?`: `string`[] ; `include?`: `string`[] ; `options?`: `unknown` ; `plugin`: `string` }
|
Ƭ **PluginConfiguration**: `string` \| [`ExpandedPluginConfiguration`](../../devkit/documents/ExpandedPluginConfiguration)
|
||||||
|
|||||||
@ -69,6 +69,7 @@ It only uses language primitives and immutable objects
|
|||||||
- [CustomHasher](../../devkit/documents/CustomHasher)
|
- [CustomHasher](../../devkit/documents/CustomHasher)
|
||||||
- [DynamicDependency](../../devkit/documents/DynamicDependency)
|
- [DynamicDependency](../../devkit/documents/DynamicDependency)
|
||||||
- [Executor](../../devkit/documents/Executor)
|
- [Executor](../../devkit/documents/Executor)
|
||||||
|
- [ExpandedPluginConfiguration](../../devkit/documents/ExpandedPluginConfiguration)
|
||||||
- [Generator](../../devkit/documents/Generator)
|
- [Generator](../../devkit/documents/Generator)
|
||||||
- [GeneratorCallback](../../devkit/documents/GeneratorCallback)
|
- [GeneratorCallback](../../devkit/documents/GeneratorCallback)
|
||||||
- [Hasher](../../devkit/documents/Hasher)
|
- [Hasher](../../devkit/documents/Hasher)
|
||||||
|
|||||||
@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
#### Type declaration
|
#### Type declaration
|
||||||
|
|
||||||
| Name | Type |
|
| Name | Type |
|
||||||
| :------ | :-------------------------- |
|
| :-------- | :-------------------------- |
|
||||||
| `debug` | (...`s`: `any`[]) => `void` |
|
| `debug` | (...`s`: `any`[]) => `void` |
|
||||||
| `error` | (`s`: `any`) => `void` |
|
| `error` | (`s`: `any`) => `void` |
|
||||||
| `fatal` | (...`s`: `any`[]) => `void` |
|
| `fatal` | (...`s`: `any`[]) => `void` |
|
||||||
| `info` | (`s`: `any`) => `void` |
|
| `info` | (`s`: `any`) => `void` |
|
||||||
| `log` | (...`s`: `any`[]) => `void` |
|
| `log` | (...`s`: `any`[]) => `void` |
|
||||||
| `warn` | (`s`: `any`) => `void` |
|
| `verbose` | (...`s`: `any`[]) => `void` |
|
||||||
|
| `warn` | (`s`: `any`) => `void` |
|
||||||
|
|||||||
@ -69,6 +69,7 @@ It only uses language primitives and immutable objects
|
|||||||
- [CustomHasher](../../devkit/documents/CustomHasher)
|
- [CustomHasher](../../devkit/documents/CustomHasher)
|
||||||
- [DynamicDependency](../../devkit/documents/DynamicDependency)
|
- [DynamicDependency](../../devkit/documents/DynamicDependency)
|
||||||
- [Executor](../../devkit/documents/Executor)
|
- [Executor](../../devkit/documents/Executor)
|
||||||
|
- [ExpandedPluginConfiguration](../../devkit/documents/ExpandedPluginConfiguration)
|
||||||
- [Generator](../../devkit/documents/Generator)
|
- [Generator](../../devkit/documents/Generator)
|
||||||
- [GeneratorCallback](../../devkit/documents/GeneratorCallback)
|
- [GeneratorCallback](../../devkit/documents/GeneratorCallback)
|
||||||
- [Hasher](../../devkit/documents/Hasher)
|
- [Hasher](../../devkit/documents/Hasher)
|
||||||
|
|||||||
@ -1,23 +1,35 @@
|
|||||||
// When plugins from root nx.json load through ts-jest, they can cause transpile errors such as `@nx/playwright/plugin.d.ts` containing an unexpected "export" keyword.
|
import { TempFs } from '../../internal-testing-utils';
|
||||||
// Mock `loadNxPlugins` function to prevent them from loading.
|
|
||||||
jest.mock('nx/src/utils/nx-plugin', () => ({
|
|
||||||
loadNxPlugins: () => Promise.resolve([]),
|
|
||||||
}));
|
|
||||||
|
|
||||||
import { convertNxExecutor } from './convert-nx-executor';
|
import { convertNxExecutor } from './convert-nx-executor';
|
||||||
|
|
||||||
describe('Convert Nx Executor', () => {
|
describe('Convert Nx Executor', () => {
|
||||||
|
let fs: TempFs;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
fs = new TempFs('convert-nx-executor');
|
||||||
|
// The tests in this file don't actually care about the files in the temp dir.
|
||||||
|
// The converted executor reads project configuration from the workspace root,
|
||||||
|
// which is set to the temp dir in the tests. If there are no files in the temp
|
||||||
|
// dir, the glob search currently hangs. So we create a dummy file to prevent that.
|
||||||
|
await fs.createFile('blah.json', JSON.stringify({}));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
fs.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
it('should convertNxExecutor to builder correctly and produce the same output', async () => {
|
it('should convertNxExecutor to builder correctly and produce the same output', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const { schema } = require('@angular-devkit/core');
|
const { schema } = require('@angular-devkit/core');
|
||||||
const {
|
const {
|
||||||
TestingArchitectHost,
|
TestingArchitectHost,
|
||||||
} = require('@angular-devkit/architect/testing');
|
// nx-ignore-next-line
|
||||||
|
} = require('@angular-devkit/architect/testing') as typeof import('@angular-devkit/architect/testing');
|
||||||
const { Architect } = require('@angular-devkit/architect');
|
const { Architect } = require('@angular-devkit/architect');
|
||||||
|
|
||||||
const registry = new schema.CoreSchemaRegistry();
|
const registry = new schema.CoreSchemaRegistry();
|
||||||
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
|
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
|
||||||
const testArchitectHost = new TestingArchitectHost();
|
const testArchitectHost = new TestingArchitectHost();
|
||||||
|
testArchitectHost.workspaceRoot = fs.tempDir;
|
||||||
const architect = new Architect(testArchitectHost, registry);
|
const architect = new Architect(testArchitectHost, registry);
|
||||||
|
|
||||||
const convertedExecutor = convertNxExecutor(echoExecutor);
|
const convertedExecutor = convertNxExecutor(echoExecutor);
|
||||||
|
|||||||
@ -11,10 +11,10 @@ import { readNxJson } from '../src/config/nx-json';
|
|||||||
import { setupWorkspaceContext } from '../src/utils/workspace-context';
|
import { setupWorkspaceContext } from '../src/utils/workspace-context';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
const start = new Date();
|
||||||
try {
|
try {
|
||||||
setupWorkspaceContext(workspaceRoot);
|
setupWorkspaceContext(workspaceRoot);
|
||||||
if (isMainNxPackage() && fileExists(join(workspaceRoot, 'nx.json'))) {
|
if (isMainNxPackage() && fileExists(join(workspaceRoot, 'nx.json'))) {
|
||||||
const b = new Date();
|
|
||||||
assertSupportedPlatform();
|
assertSupportedPlatform();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -35,15 +35,18 @@ import { setupWorkspaceContext } from '../src/utils/workspace-context';
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
|
||||||
const a = new Date();
|
|
||||||
console.log(`Nx postinstall steps took ${a.getTime() - b.getTime()}ms`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||||
|
const end = new Date();
|
||||||
|
console.log(
|
||||||
|
`Nx postinstall steps took ${end.getTime() - start.getTime()}ms`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { NxPluginV2 } from '../src/utils/nx-plugin';
|
import type { NxPluginV2 } from '../src/project-graph/plugins';
|
||||||
import { workspaceRoot } from '../src/utils/workspace-root';
|
import { workspaceRoot } from '../src/utils/workspace-root';
|
||||||
import { createNodeFromPackageJson } from '../src/plugins/package-json-workspaces';
|
import { createNodeFromPackageJson } from '../src/plugins/package-json-workspaces';
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { existsSync } from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { readJsonFile } from '../utils/fileutils';
|
import { readJsonFile } from '../utils/fileutils';
|
||||||
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
|
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
|
||||||
import { NxPluginV2 } from '../utils/nx-plugin';
|
import { NxPluginV2 } from '../project-graph/plugins';
|
||||||
|
|
||||||
export const NX_ANGULAR_JSON_PLUGIN_NAME = 'nx-angular-json-plugin';
|
export const NX_ANGULAR_JSON_PLUGIN_NAME = 'nx-angular-json-plugin';
|
||||||
|
|
||||||
@ -16,6 +16,8 @@ export const NxAngularJsonPlugin: NxPluginV2 = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default NxAngularJsonPlugin;
|
||||||
|
|
||||||
export function shouldMergeAngularProjects(
|
export function shouldMergeAngularProjects(
|
||||||
root: string,
|
root: string,
|
||||||
includeProjectsFromAngularJson: boolean
|
includeProjectsFromAngularJson: boolean
|
||||||
|
|||||||
@ -59,7 +59,7 @@ import {
|
|||||||
ExecutorsJson,
|
ExecutorsJson,
|
||||||
TaskGraphExecutor,
|
TaskGraphExecutor,
|
||||||
} from '../config/misc-interfaces';
|
} from '../config/misc-interfaces';
|
||||||
import { readPluginPackageJson } from '../utils/nx-plugin';
|
import { readPluginPackageJson } from '../project-graph/plugins';
|
||||||
import {
|
import {
|
||||||
getImplementationFactory,
|
getImplementationFactory,
|
||||||
resolveImplementation,
|
resolveImplementation,
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
resolveSchema,
|
resolveSchema,
|
||||||
} from '../../config/schema-utils';
|
} from '../../config/schema-utils';
|
||||||
import { readJsonFile } from '../../utils/fileutils';
|
import { readJsonFile } from '../../utils/fileutils';
|
||||||
import { readPluginPackageJson } from '../../utils/nx-plugin';
|
import { readPluginPackageJson } from '../../project-graph/plugins';
|
||||||
|
|
||||||
export function getGeneratorInformation(
|
export function getGeneratorInformation(
|
||||||
collectionName: string,
|
collectionName: string,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
|
|
||||||
import { readPluginPackageJson } from '../../utils/nx-plugin';
|
import { readPluginPackageJson } from '../../project-graph/plugins';
|
||||||
import {
|
import {
|
||||||
CustomHasher,
|
CustomHasher,
|
||||||
Executor,
|
Executor,
|
||||||
|
|||||||
@ -436,14 +436,14 @@ export interface NxJsonConfiguration<T = '*' | string[]> {
|
|||||||
useInferencePlugins?: boolean;
|
useInferencePlugins?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginConfiguration =
|
export type PluginConfiguration = string | ExpandedPluginConfiguration;
|
||||||
| string
|
|
||||||
| {
|
export type ExpandedPluginConfiguration = {
|
||||||
plugin: string;
|
plugin: string;
|
||||||
options?: unknown;
|
options?: unknown;
|
||||||
include?: string[];
|
include?: string[];
|
||||||
exclude?: string[];
|
exclude?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function readNxJson(root: string = workspaceRoot): NxJsonConfiguration {
|
export function readNxJson(root: string = workspaceRoot): NxJsonConfiguration {
|
||||||
const nxJson = join(root, 'nx.json');
|
const nxJson = join(root, 'nx.json');
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { extname, join } from 'path';
|
import { extname, join } from 'path';
|
||||||
import { registerPluginTSTranspiler } from '../utils/nx-plugin';
|
import { registerPluginTSTranspiler } from '../project-graph/plugins';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used to get the implementation factory of an executor or generator.
|
* This function is used to get the implementation factory of an executor or generator.
|
||||||
|
|||||||
@ -1,22 +1,9 @@
|
|||||||
import { toProjectName, Workspaces } from './workspaces';
|
import { toProjectName } from './workspaces';
|
||||||
import { TempFs } from '../internal-testing-utils/temp-fs';
|
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';
|
||||||
const libConfig = (root, name?: string) => ({
|
|
||||||
name: name ?? toProjectName(`${root}/some-file`),
|
|
||||||
projectType: 'library',
|
|
||||||
root: `libs/${root}`,
|
|
||||||
sourceRoot: `libs/${root}/src`,
|
|
||||||
targets: {
|
|
||||||
'nx-release-publish': {
|
|
||||||
dependsOn: ['^nx-release-publish'],
|
|
||||||
executor: '@nx/js:release-publish',
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Workspaces', () => {
|
describe('Workspaces', () => {
|
||||||
let fs: TempFs;
|
let fs: TempFs;
|
||||||
@ -48,10 +35,23 @@ describe('Workspaces', () => {
|
|||||||
|
|
||||||
const { projects } = await withEnvironmentVariables(
|
const { projects } = await withEnvironmentVariables(
|
||||||
{
|
{
|
||||||
NX_WORKSPACE_ROOT: fs.tempDir,
|
NX_WORKSPACE_ROOT_PATH: fs.tempDir,
|
||||||
},
|
},
|
||||||
() => retrieveProjectConfigurations(fs.tempDir, readNxJson(fs.tempDir))
|
async () => {
|
||||||
|
const [plugins, cleanup] = await loadNxPlugins(
|
||||||
|
readNxJson(fs.tempDir).plugins,
|
||||||
|
fs.tempDir
|
||||||
|
);
|
||||||
|
const res = retrieveProjectConfigurations(
|
||||||
|
plugins,
|
||||||
|
fs.tempDir,
|
||||||
|
readNxJson(fs.tempDir)
|
||||||
|
);
|
||||||
|
cleanup();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
console.log(projects);
|
||||||
expect(projects['my-package']).toEqual({
|
expect(projects['my-package']).toEqual({
|
||||||
name: 'my-package',
|
name: 'my-package',
|
||||||
root: 'packages/my-package',
|
root: 'packages/my-package',
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { serializeResult } from '../socket-utils';
|
|||||||
import { serverLogger } from './logger';
|
import { serverLogger } from './logger';
|
||||||
import { getCachedSerializedProjectGraphPromise } from './project-graph-incremental-recomputation';
|
import { getCachedSerializedProjectGraphPromise } from './project-graph-incremental-recomputation';
|
||||||
import { HandlerResult } from './server';
|
import { HandlerResult } from './server';
|
||||||
|
import { getPlugins } from './plugins';
|
||||||
|
import { readNxJson } from '../../config/nx-json';
|
||||||
|
|
||||||
export async function handleRequestProjectGraph(): Promise<HandlerResult> {
|
export async function handleRequestProjectGraph(): Promise<HandlerResult> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
26
packages/nx/src/daemon/server/plugins.ts
Normal file
26
packages/nx/src/daemon/server/plugins.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { readNxJson } from '../../config/nx-json';
|
||||||
|
import {
|
||||||
|
LoadedNxPlugin,
|
||||||
|
loadNxPlugins,
|
||||||
|
} from '../../project-graph/plugins/internal-api';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
|
||||||
|
let loadedPlugins: Promise<LoadedNxPlugin[]>;
|
||||||
|
let cleanup: () => void;
|
||||||
|
|
||||||
|
export async function getPlugins() {
|
||||||
|
if (loadedPlugins) {
|
||||||
|
return loadedPlugins;
|
||||||
|
}
|
||||||
|
const pluginsConfiguration = readNxJson().plugins ?? [];
|
||||||
|
const [result, cleanupFn] = await loadNxPlugins(
|
||||||
|
pluginsConfiguration,
|
||||||
|
workspaceRoot
|
||||||
|
);
|
||||||
|
cleanup = cleanupFn;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanupPlugins() {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
@ -32,11 +32,11 @@ import { workspaceRoot } from '../../utils/workspace-root';
|
|||||||
import { notifyFileWatcherSockets } from './file-watching/file-watcher-sockets';
|
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 {
|
import { ConfigurationResult } from '../../project-graph/utils/project-configuration-utils';
|
||||||
ConfigurationResult,
|
|
||||||
ProjectConfigurationsError,
|
|
||||||
} from '../../project-graph/utils/project-configuration-utils';
|
|
||||||
import { DaemonProjectGraphError } from '../daemon-project-graph-error';
|
import { DaemonProjectGraphError } from '../daemon-project-graph-error';
|
||||||
|
import { LoadedNxPlugin } from '../../project-graph/plugins/internal-api';
|
||||||
|
import { getPlugins } from './plugins';
|
||||||
|
import { ProjectConfigurationsError } from '../../project-graph/error-types';
|
||||||
|
|
||||||
interface SerializedProjectGraph {
|
interface SerializedProjectGraph {
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
@ -78,14 +78,15 @@ export async function getCachedSerializedProjectGraphPromise(): Promise<Serializ
|
|||||||
// reset the wait time
|
// reset the wait time
|
||||||
waitPeriod = 100;
|
waitPeriod = 100;
|
||||||
await resetInternalStateIfNxDepsMissing();
|
await resetInternalStateIfNxDepsMissing();
|
||||||
|
const plugins = await getPlugins();
|
||||||
if (collectedUpdatedFiles.size == 0 && collectedDeletedFiles.size == 0) {
|
if (collectedUpdatedFiles.size == 0 && collectedDeletedFiles.size == 0) {
|
||||||
if (!cachedSerializedProjectGraphPromise) {
|
if (!cachedSerializedProjectGraphPromise) {
|
||||||
cachedSerializedProjectGraphPromise =
|
cachedSerializedProjectGraphPromise =
|
||||||
processFilesAndCreateAndSerializeProjectGraph();
|
processFilesAndCreateAndSerializeProjectGraph(plugins);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cachedSerializedProjectGraphPromise =
|
cachedSerializedProjectGraphPromise =
|
||||||
processFilesAndCreateAndSerializeProjectGraph();
|
processFilesAndCreateAndSerializeProjectGraph(plugins);
|
||||||
}
|
}
|
||||||
return await cachedSerializedProjectGraphPromise;
|
return await cachedSerializedProjectGraphPromise;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -133,7 +134,7 @@ export function addUpdatedAndDeletedFiles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
cachedSerializedProjectGraphPromise =
|
cachedSerializedProjectGraphPromise =
|
||||||
processFilesAndCreateAndSerializeProjectGraph();
|
processFilesAndCreateAndSerializeProjectGraph(await getPlugins());
|
||||||
await cachedSerializedProjectGraphPromise;
|
await cachedSerializedProjectGraphPromise;
|
||||||
|
|
||||||
if (createdFiles.length > 0) {
|
if (createdFiles.length > 0) {
|
||||||
@ -209,7 +210,9 @@ async function processCollectedUpdatedAndDeletedFiles(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processFilesAndCreateAndSerializeProjectGraph(): Promise<SerializedProjectGraph> {
|
async function processFilesAndCreateAndSerializeProjectGraph(
|
||||||
|
plugins: LoadedNxPlugin[]
|
||||||
|
): Promise<SerializedProjectGraph> {
|
||||||
try {
|
try {
|
||||||
performance.mark('hash-watched-changes-start');
|
performance.mark('hash-watched-changes-start');
|
||||||
const updatedFiles = [...collectedUpdatedFiles.values()];
|
const updatedFiles = [...collectedUpdatedFiles.values()];
|
||||||
@ -227,14 +230,17 @@ async function processFilesAndCreateAndSerializeProjectGraph(): Promise<Serializ
|
|||||||
serverLogger.requestLog([...updatedFiles.values()]);
|
serverLogger.requestLog([...updatedFiles.values()]);
|
||||||
serverLogger.requestLog([...deletedFiles]);
|
serverLogger.requestLog([...deletedFiles]);
|
||||||
const nxJson = readNxJson(workspaceRoot);
|
const nxJson = readNxJson(workspaceRoot);
|
||||||
// Set this globally to allow plugins to know if they are being called from the project graph creation
|
|
||||||
global.NX_GRAPH_CREATION = true;
|
global.NX_GRAPH_CREATION = true;
|
||||||
|
|
||||||
let graphNodes: ConfigurationResult;
|
let graphNodes: ConfigurationResult;
|
||||||
let projectConfigurationsError;
|
let projectConfigurationsError;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
graphNodes = await retrieveProjectConfigurations(workspaceRoot, nxJson);
|
graphNodes = await retrieveProjectConfigurations(
|
||||||
|
plugins,
|
||||||
|
workspaceRoot,
|
||||||
|
nxJson
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ProjectConfigurationsError) {
|
if (e instanceof ProjectConfigurationsError) {
|
||||||
graphNodes = e.partialProjectConfigurationsResult;
|
graphNodes = e.partialProjectConfigurationsResult;
|
||||||
@ -335,7 +341,8 @@ async function createAndSerializeProjectGraph({
|
|||||||
fileMap,
|
fileMap,
|
||||||
allWorkspaceFiles,
|
allWorkspaceFiles,
|
||||||
rustReferences,
|
rustReferences,
|
||||||
currentProjectFileMapCache || readFileMapCache()
|
currentProjectFileMapCache || readFileMapCache(),
|
||||||
|
await getPlugins()
|
||||||
);
|
);
|
||||||
|
|
||||||
currentProjectFileMapCache = projectFileMapCache;
|
currentProjectFileMapCache = projectFileMapCache;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { serverLogger } from './logger';
|
|||||||
import { serializeResult } from '../socket-utils';
|
import { serializeResult } from '../socket-utils';
|
||||||
import { deleteDaemonJsonProcessCache } from '../cache';
|
import { deleteDaemonJsonProcessCache } from '../cache';
|
||||||
import type { Watcher } from '../../native';
|
import type { Watcher } from '../../native';
|
||||||
|
import { cleanupPlugins } from './plugins';
|
||||||
|
|
||||||
export const SERVER_INACTIVITY_TIMEOUT_MS = 10800000 as const; // 10800000 ms = 3 hours
|
export const SERVER_INACTIVITY_TIMEOUT_MS = 10800000 as const; // 10800000 ms = 3 hours
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ export async function handleServerProcessTermination({
|
|||||||
try {
|
try {
|
||||||
server.close();
|
server.close();
|
||||||
deleteDaemonJsonProcessCache();
|
deleteDaemonJsonProcessCache();
|
||||||
|
cleanupPlugins();
|
||||||
|
|
||||||
if (watcherInstance) {
|
if (watcherInstance) {
|
||||||
await watcherInstance.stop();
|
await watcherInstance.stop();
|
||||||
|
|||||||
@ -47,16 +47,19 @@ export { workspaceLayout } from './config/configuration';
|
|||||||
|
|
||||||
export type {
|
export type {
|
||||||
NxPlugin,
|
NxPlugin,
|
||||||
NxPluginV1,
|
|
||||||
NxPluginV2,
|
NxPluginV2,
|
||||||
ProjectTargetConfigurator,
|
|
||||||
CreateNodes,
|
CreateNodes,
|
||||||
CreateNodesFunction,
|
CreateNodesFunction,
|
||||||
CreateNodesResult,
|
CreateNodesResult,
|
||||||
CreateNodesContext,
|
CreateNodesContext,
|
||||||
CreateDependencies,
|
CreateDependencies,
|
||||||
CreateDependenciesContext,
|
CreateDependenciesContext,
|
||||||
} from './utils/nx-plugin';
|
} from './project-graph/plugins';
|
||||||
|
|
||||||
|
export type {
|
||||||
|
NxPluginV1,
|
||||||
|
ProjectTargetConfigurator,
|
||||||
|
} from './utils/nx-plugin.deprecated';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @category Workspace
|
* @category Workspace
|
||||||
@ -71,6 +74,7 @@ export type {
|
|||||||
ImplicitJsonSubsetDependency,
|
ImplicitJsonSubsetDependency,
|
||||||
NxJsonConfiguration,
|
NxJsonConfiguration,
|
||||||
PluginConfiguration,
|
PluginConfiguration,
|
||||||
|
ExpandedPluginConfiguration,
|
||||||
TargetDefaults,
|
TargetDefaults,
|
||||||
NxAffectedConfig,
|
NxAffectedConfig,
|
||||||
} from './config/nx-json';
|
} from './config/nx-json';
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { readNxJson } from '../../config/nx-json';
|
|||||||
import { Executor, ExecutorContext } from '../../config/misc-interfaces';
|
import { Executor, ExecutorContext } from '../../config/misc-interfaces';
|
||||||
import { retrieveProjectConfigurations } from '../../project-graph/utils/retrieve-workspace-files';
|
import { retrieveProjectConfigurations } from '../../project-graph/utils/retrieve-workspace-files';
|
||||||
import { ProjectsConfigurations } from '../../config/workspace-json-project-json';
|
import { ProjectsConfigurations } from '../../config/workspace-json-project-json';
|
||||||
|
import { loadNxPlugins } from '../../project-graph/plugins/internal-api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an Nx Executor into an Angular Devkit Builder
|
* Convert an Nx Executor into an Angular Devkit Builder
|
||||||
@ -17,15 +18,22 @@ export function convertNxExecutor(executor: Executor) {
|
|||||||
const builderFunction = (options, builderContext) => {
|
const builderFunction = (options, builderContext) => {
|
||||||
const promise = async () => {
|
const promise = async () => {
|
||||||
const nxJsonConfiguration = readNxJson(builderContext.workspaceRoot);
|
const nxJsonConfiguration = readNxJson(builderContext.workspaceRoot);
|
||||||
|
|
||||||
|
const [plugins, cleanup] = await loadNxPlugins(
|
||||||
|
nxJsonConfiguration.plugins,
|
||||||
|
builderContext.workspaceRoot
|
||||||
|
);
|
||||||
const projectsConfigurations: ProjectsConfigurations = {
|
const projectsConfigurations: ProjectsConfigurations = {
|
||||||
version: 2,
|
version: 2,
|
||||||
projects: (
|
projects: (
|
||||||
await retrieveProjectConfigurations(
|
await retrieveProjectConfigurations(
|
||||||
|
plugins,
|
||||||
builderContext.workspaceRoot,
|
builderContext.workspaceRoot,
|
||||||
nxJsonConfiguration
|
nxJsonConfiguration
|
||||||
)
|
)
|
||||||
).projects,
|
).projects,
|
||||||
};
|
};
|
||||||
|
cleanup();
|
||||||
const context: ExecutorContext = {
|
const context: ExecutorContext = {
|
||||||
root: builderContext.workspaceRoot,
|
root: builderContext.workspaceRoot,
|
||||||
projectName: builderContext.target.project,
|
projectName: builderContext.target.project,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { basename, join, relative } from 'path';
|
|||||||
import {
|
import {
|
||||||
buildProjectConfigurationFromPackageJson,
|
buildProjectConfigurationFromPackageJson,
|
||||||
getGlobPatternsFromPackageManagerWorkspaces,
|
getGlobPatternsFromPackageManagerWorkspaces,
|
||||||
getNxPackageJsonWorkspacesPlugin,
|
createNodes as packageJsonWorkspacesCreateNodes,
|
||||||
} from '../../plugins/package-json-workspaces';
|
} from '../../plugins/package-json-workspaces';
|
||||||
import {
|
import {
|
||||||
buildProjectFromProjectJson,
|
buildProjectFromProjectJson,
|
||||||
@ -28,6 +28,7 @@ import { readJson, writeJson } from './json';
|
|||||||
import { readNxJson } from './nx-json';
|
import { readNxJson } from './nx-json';
|
||||||
|
|
||||||
import type { Tree } from '../tree';
|
import type { Tree } from '../tree';
|
||||||
|
import { NxPlugin } from '../../project-graph/plugins';
|
||||||
|
|
||||||
export { readNxJson, updateNxJson } from './nx-json';
|
export { readNxJson, updateNxJson } from './nx-json';
|
||||||
|
|
||||||
@ -200,8 +201,8 @@ function readAndCombineAllProjectConfigurations(tree: Tree): {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
const projectGlobPatterns = configurationGlobs([
|
const projectGlobPatterns = configurationGlobs([
|
||||||
{ plugin: ProjectJsonProjectsPlugin },
|
ProjectJsonProjectsPlugin,
|
||||||
{ plugin: getNxPackageJsonWorkspacesPlugin(tree.root) },
|
{ createNodes: packageJsonWorkspacesCreateNodes },
|
||||||
]);
|
]);
|
||||||
const globbedFiles = globWithWorkspaceContext(tree.root, projectGlobPatterns);
|
const globbedFiles = globWithWorkspaceContext(tree.root, projectGlobPatterns);
|
||||||
const createdFiles = findCreatedProjectFiles(tree, patterns);
|
const createdFiles = findCreatedProjectFiles(tree, patterns);
|
||||||
|
|||||||
@ -4,14 +4,15 @@ import { dirname } from 'path';
|
|||||||
import { readJson, writeJson } from '../../generators/utils/json';
|
import { readJson, writeJson } from '../../generators/utils/json';
|
||||||
import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available';
|
import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available';
|
||||||
import { retrieveProjectConfigurationPaths } from '../../project-graph/utils/retrieve-workspace-files';
|
import { retrieveProjectConfigurationPaths } from '../../project-graph/utils/retrieve-workspace-files';
|
||||||
import { loadNxPlugins } from '../../utils/nx-plugin';
|
import { loadNxPlugins } from '../../project-graph/plugins/internal-api';
|
||||||
|
|
||||||
export default async function (tree: Tree) {
|
export default async function (tree: Tree) {
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
const projectFiles = await retrieveProjectConfigurationPaths(
|
const [plugins, cleanup] = await loadNxPlugins(
|
||||||
tree.root,
|
nxJson?.plugins ?? [],
|
||||||
await loadNxPlugins(nxJson?.plugins)
|
tree.root
|
||||||
);
|
);
|
||||||
|
const projectFiles = retrieveProjectConfigurationPaths(tree.root, plugins);
|
||||||
const projectJsons = projectFiles.filter((f) => f.endsWith('project.json'));
|
const projectJsons = projectFiles.filter((f) => f.endsWith('project.json'));
|
||||||
|
|
||||||
for (let f of projectJsons) {
|
for (let f of projectJsons) {
|
||||||
@ -22,6 +23,7 @@ export default async function (tree: Tree) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await formatChangedFilesWithPrettierIfAvailable(tree);
|
await formatChangedFilesWithPrettierIfAvailable(tree);
|
||||||
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toProjectName(directory: string, nxJson: any): string {
|
function toProjectName(directory: string, nxJson: any): string {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
CreateDependencies,
|
CreateDependencies,
|
||||||
CreateDependenciesContext,
|
CreateDependenciesContext,
|
||||||
CreateNodes,
|
CreateNodes,
|
||||||
} from '../../utils/nx-plugin';
|
} from '../../project-graph/plugins';
|
||||||
import {
|
import {
|
||||||
getLockFileDependencies,
|
getLockFileDependencies,
|
||||||
getLockFileName,
|
getLockFileName,
|
||||||
|
|||||||
@ -40,7 +40,7 @@ import { readJsonFile } from '../../../utils/fileutils';
|
|||||||
import {
|
import {
|
||||||
CreateDependenciesContext,
|
CreateDependenciesContext,
|
||||||
CreateNodesContext,
|
CreateNodesContext,
|
||||||
} from '../../../utils/nx-plugin';
|
} from '../../../project-graph/plugins';
|
||||||
|
|
||||||
const YARN_LOCK_FILE = 'yarn.lock';
|
const YARN_LOCK_FILE = 'yarn.lock';
|
||||||
const NPM_LOCK_FILE = 'package-lock.json';
|
const NPM_LOCK_FILE = 'package-lock.json';
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { pruneProjectGraph } from './project-graph-pruning';
|
|||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
import { ProjectGraph } from '../../../config/project-graph';
|
import { ProjectGraph } from '../../../config/project-graph';
|
||||||
import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder';
|
import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder';
|
||||||
import { CreateDependenciesContext } from '../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
||||||
|
|
||||||
jest.mock('fs', () => {
|
jest.mock('fs', () => {
|
||||||
const memFs = require('memfs').fs;
|
const memFs = require('memfs').fs;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
ProjectGraphExternalNode,
|
ProjectGraphExternalNode,
|
||||||
} from '../../../config/project-graph';
|
} from '../../../config/project-graph';
|
||||||
import { hashArray } from '../../../hasher/file-hasher';
|
import { hashArray } from '../../../hasher/file-hasher';
|
||||||
import { CreateDependenciesContext } from '../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NPM
|
* NPM
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
ProjectGraphBuilder,
|
ProjectGraphBuilder,
|
||||||
RawProjectGraphDependency,
|
RawProjectGraphDependency,
|
||||||
} from '../../../project-graph/project-graph-builder';
|
} from '../../../project-graph/project-graph-builder';
|
||||||
import { CreateDependenciesContext } from '../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
||||||
|
|
||||||
jest.mock('fs', () => {
|
jest.mock('fs', () => {
|
||||||
const memFs = require('memfs').fs;
|
const memFs = require('memfs').fs;
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import {
|
|||||||
ProjectGraphExternalNode,
|
ProjectGraphExternalNode,
|
||||||
} from '../../../config/project-graph';
|
} from '../../../config/project-graph';
|
||||||
import { hashArray } from '../../../hasher/file-hasher';
|
import { hashArray } from '../../../hasher/file-hasher';
|
||||||
import { CreateDependenciesContext } from '../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
||||||
|
|
||||||
// we use key => node map to avoid duplicate work when parsing keys
|
// we use key => node map to avoid duplicate work when parsing keys
|
||||||
let keyMap = new Map<string, ProjectGraphExternalNode>();
|
let keyMap = new Map<string, ProjectGraphExternalNode>();
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { vol } from 'memfs';
|
|||||||
import { ProjectGraph } from '../../../config/project-graph';
|
import { ProjectGraph } from '../../../config/project-graph';
|
||||||
import { PackageJson } from '../../../utils/package-json';
|
import { PackageJson } from '../../../utils/package-json';
|
||||||
import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder';
|
import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder';
|
||||||
import { CreateDependenciesContext } from '../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
||||||
|
|
||||||
jest.mock('fs', () => {
|
jest.mock('fs', () => {
|
||||||
const memFs = require('memfs').fs;
|
const memFs = require('memfs').fs;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
} from '../../../config/project-graph';
|
} from '../../../config/project-graph';
|
||||||
import { hashArray } from '../../../hasher/file-hasher';
|
import { hashArray } from '../../../hasher/file-hasher';
|
||||||
import { sortObjectByKeys } from '../../../utils/object-sort';
|
import { sortObjectByKeys } from '../../../utils/object-sort';
|
||||||
import { CreateDependenciesContext } from '../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Yarn
|
* Yarn
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies';
|
import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies';
|
||||||
import { buildExplicitPackageJsonDependencies } from './explicit-package-json-dependencies';
|
import { buildExplicitPackageJsonDependencies } from './explicit-package-json-dependencies';
|
||||||
import { CreateDependenciesContext } from '../../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../../project-graph/plugins';
|
||||||
import { RawProjectGraphDependency } from '../../../../project-graph/project-graph-builder';
|
import { RawProjectGraphDependency } from '../../../../project-graph/project-graph-builder';
|
||||||
|
|
||||||
export function buildExplicitDependencies(
|
export function buildExplicitDependencies(
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { buildExplicitPackageJsonDependencies } from './explicit-package-json-de
|
|||||||
import { ProjectGraphProjectNode } from '../../../../config/project-graph';
|
import { ProjectGraphProjectNode } from '../../../../config/project-graph';
|
||||||
import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder';
|
import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder';
|
||||||
import { createFileMap } from '../../../../project-graph/file-map-utils';
|
import { createFileMap } from '../../../../project-graph/file-map-utils';
|
||||||
import { CreateDependenciesContext } from '../../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../../project-graph/plugins';
|
||||||
import { getAllFileDataInContext } from '../../../../utils/workspace-context';
|
import { getAllFileDataInContext } from '../../../../utils/workspace-context';
|
||||||
|
|
||||||
describe('explicit package json dependencies', () => {
|
describe('explicit package json dependencies', () => {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
} from '../../../../config/workspace-json-project-json';
|
} from '../../../../config/workspace-json-project-json';
|
||||||
import { NxJsonConfiguration } from '../../../../config/nx-json';
|
import { NxJsonConfiguration } from '../../../../config/nx-json';
|
||||||
import { PackageJson } from '../../../../utils/package-json';
|
import { PackageJson } from '../../../../utils/package-json';
|
||||||
import { CreateDependenciesContext } from '../../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../../project-graph/plugins';
|
||||||
import {
|
import {
|
||||||
RawProjectGraphDependency,
|
RawProjectGraphDependency,
|
||||||
validateDependency,
|
validateDependency,
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import { TempFs } from '../../../../internal-testing-utils/temp-fs';
|
import { TempFs } from '../../../../internal-testing-utils/temp-fs';
|
||||||
|
|
||||||
const tempFs = new TempFs('explicit-project-deps');
|
const tempFs = new TempFs('explicit-project-deps');
|
||||||
|
|
||||||
import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder';
|
import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder';
|
||||||
import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies';
|
import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies';
|
||||||
import {
|
import {
|
||||||
retrieveProjectConfigurationPaths,
|
|
||||||
retrieveProjectConfigurations,
|
retrieveProjectConfigurations,
|
||||||
retrieveWorkspaceFiles,
|
retrieveWorkspaceFiles,
|
||||||
} from '../../../../project-graph/utils/retrieve-workspace-files';
|
} from '../../../../project-graph/utils/retrieve-workspace-files';
|
||||||
import { CreateDependenciesContext } from '../../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../../project-graph/plugins';
|
||||||
import { setupWorkspaceContext } from '../../../../utils/workspace-context';
|
import { setupWorkspaceContext } from '../../../../utils/workspace-context';
|
||||||
|
import { loadNxPlugins } from '../../../../project-graph/plugins/internal-api';
|
||||||
|
|
||||||
// projectName => tsconfig import path
|
// projectName => tsconfig import path
|
||||||
const dependencyProjectNamesToImportPaths = {
|
const dependencyProjectNamesToImportPaths = {
|
||||||
@ -564,10 +565,13 @@ async function createContext(
|
|||||||
|
|
||||||
setupWorkspaceContext(tempFs.tempDir);
|
setupWorkspaceContext(tempFs.tempDir);
|
||||||
|
|
||||||
|
const [plugins, cleanup] = await loadNxPlugins([], tempFs.tempDir);
|
||||||
const { projects, projectRootMap } = await retrieveProjectConfigurations(
|
const { projects, projectRootMap } = await retrieveProjectConfigurations(
|
||||||
|
plugins,
|
||||||
tempFs.tempDir,
|
tempFs.tempDir,
|
||||||
nxJson
|
nxJson
|
||||||
);
|
);
|
||||||
|
cleanup();
|
||||||
|
|
||||||
const { fileMap } = await retrieveWorkspaceFiles(
|
const { fileMap } = await retrieveWorkspaceFiles(
|
||||||
tempFs.tempDir,
|
tempFs.tempDir,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
import { join, relative } from 'path';
|
import { join, relative } from 'path';
|
||||||
import { workspaceRoot } from '../../../../utils/workspace-root';
|
import { workspaceRoot } from '../../../../utils/workspace-root';
|
||||||
import { normalizePath } from '../../../../utils/path';
|
import { normalizePath } from '../../../../utils/path';
|
||||||
import { CreateDependenciesContext } from '../../../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../../../../project-graph/plugins';
|
||||||
import {
|
import {
|
||||||
RawProjectGraphDependency,
|
RawProjectGraphDependency,
|
||||||
validateDependency,
|
validateDependency,
|
||||||
|
|||||||
@ -8,48 +8,43 @@ import { toProjectName } from '../../config/workspaces';
|
|||||||
import { readJsonFile, readYamlFile } from '../../utils/fileutils';
|
import { readJsonFile, readYamlFile } from '../../utils/fileutils';
|
||||||
import { combineGlobPatterns } from '../../utils/globs';
|
import { combineGlobPatterns } from '../../utils/globs';
|
||||||
import { NX_PREFIX } from '../../utils/logger';
|
import { NX_PREFIX } from '../../utils/logger';
|
||||||
import { NxPluginV2 } from '../../utils/nx-plugin';
|
|
||||||
import { output } from '../../utils/output';
|
import { output } from '../../utils/output';
|
||||||
import {
|
import {
|
||||||
PackageJson,
|
PackageJson,
|
||||||
readTargetsFromPackageJson,
|
readTargetsFromPackageJson,
|
||||||
} from '../../utils/package-json';
|
} from '../../utils/package-json';
|
||||||
import { joinPathFragments } from '../../utils/path';
|
import { joinPathFragments } from '../../utils/path';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import { CreateNodes } from '../../project-graph/plugins';
|
||||||
|
|
||||||
export function getNxPackageJsonWorkspacesPlugin(root: string): NxPluginV2 {
|
const readJson = (f) => readJsonFile(join(workspaceRoot, f));
|
||||||
const readJson = (f) => readJsonFile(join(root, f));
|
const patterns = getGlobPatternsFromPackageManagerWorkspaces(
|
||||||
const patterns = getGlobPatternsFromPackageManagerWorkspaces(root, readJson);
|
workspaceRoot,
|
||||||
|
readJson
|
||||||
|
);
|
||||||
|
|
||||||
// If the user only specified a negative pattern, we should find all package.json
|
const negativePatterns = patterns.filter((p) => p.startsWith('!'));
|
||||||
// files and only return those that don't match a negative pattern.
|
const positivePatterns = patterns.filter((p) => !p.startsWith('!'));
|
||||||
const negativePatterns = patterns.filter((p) => p.startsWith('!'));
|
if (
|
||||||
let positivePatterns = patterns.filter((p) => !p.startsWith('!'));
|
// There are some negative patterns
|
||||||
|
negativePatterns.length > 0 &&
|
||||||
if (
|
// No positive patterns
|
||||||
// There are some negative patterns
|
(positivePatterns.length === 0 ||
|
||||||
negativePatterns.length > 0 &&
|
// Or only a single positive pattern that is the default coming from root package
|
||||||
// No positive patterns
|
(positivePatterns.length === 1 && positivePatterns[0] === 'package.json'))
|
||||||
(positivePatterns.length === 0 ||
|
) {
|
||||||
// Or only a single positive pattern that is the default coming from root package
|
positivePatterns.push('**/package.json');
|
||||||
(positivePatterns.length === 1 && positivePatterns[0] === 'package.json'))
|
|
||||||
) {
|
|
||||||
positivePatterns.push('**/package.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'nx/core/package-json-workspaces',
|
|
||||||
createNodes: [
|
|
||||||
combineGlobPatterns(positivePatterns),
|
|
||||||
(p) => {
|
|
||||||
if (!negativePatterns.some((negative) => minimatch(p, negative))) {
|
|
||||||
return createNodeFromPackageJson(p, root);
|
|
||||||
}
|
|
||||||
// A negative pattern matched, so we should not create a node for this package.json
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
export const createNodes: CreateNodes = [
|
||||||
|
combineGlobPatterns(positivePatterns),
|
||||||
|
(p, _, { workspaceRoot }) => {
|
||||||
|
if (!negativePatterns.some((negative) => minimatch(p, negative))) {
|
||||||
|
return createNodeFromPackageJson(p, workspaceRoot);
|
||||||
|
}
|
||||||
|
// A negative pattern matched, so we should not create a node for this package.json
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function createNodeFromPackageJson(pkgJsonPath: string, root: string) {
|
export function createNodeFromPackageJson(pkgJsonPath: string, root: string) {
|
||||||
const json: PackageJson = readJsonFile(join(root, pkgJsonPath));
|
const json: PackageJson = readJsonFile(join(root, pkgJsonPath));
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './create-nodes';
|
export * from './create-nodes';
|
||||||
|
export const name = 'nx/core/package-json-workspaces';
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import * as memfs from 'memfs';
|
|||||||
import '../../../internal-testing-utils/mock-fs';
|
import '../../../internal-testing-utils/mock-fs';
|
||||||
|
|
||||||
import { PackageJsonProjectsNextToProjectJsonPlugin } from './package-json-next-to-project-json';
|
import { PackageJsonProjectsNextToProjectJsonPlugin } from './package-json-next-to-project-json';
|
||||||
import { CreateNodesContext } from '../../../utils/nx-plugin';
|
import { CreateNodesContext } from '../../../project-graph/plugins';
|
||||||
const { createNodes } = PackageJsonProjectsNextToProjectJsonPlugin;
|
const { createNodes } = PackageJsonProjectsNextToProjectJsonPlugin;
|
||||||
|
|
||||||
describe('nx project.json plugin', () => {
|
describe('nx project.json plugin', () => {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { NxPluginV2 } from '../../../utils/nx-plugin';
|
import { NxPluginV2 } from '../../../project-graph/plugins';
|
||||||
import { readJsonFile } from '../../../utils/fileutils';
|
import { readJsonFile } from '../../../utils/fileutils';
|
||||||
import { ProjectConfiguration } from '../../../config/workspace-json-project-json';
|
import { ProjectConfiguration } from '../../../config/workspace-json-project-json';
|
||||||
import {
|
import {
|
||||||
@ -33,6 +33,8 @@ export const PackageJsonProjectsNextToProjectJsonPlugin: NxPluginV2 = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default PackageJsonProjectsNextToProjectJsonPlugin;
|
||||||
|
|
||||||
function createProjectFromPackageJsonNextToProjectJson(
|
function createProjectFromPackageJsonNextToProjectJson(
|
||||||
projectJsonPath: string,
|
projectJsonPath: string,
|
||||||
workspaceRoot: string
|
workspaceRoot: string
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import * as memfs from 'memfs';
|
|||||||
import '../../../internal-testing-utils/mock-fs';
|
import '../../../internal-testing-utils/mock-fs';
|
||||||
|
|
||||||
import { ProjectJsonProjectsPlugin } from './project-json';
|
import { ProjectJsonProjectsPlugin } from './project-json';
|
||||||
import { CreateNodesContext } from '../../../utils/nx-plugin';
|
import { CreateNodesContext } from '../../../project-graph/plugins';
|
||||||
const { createNodes } = ProjectJsonProjectsPlugin;
|
const { createNodes } = ProjectJsonProjectsPlugin;
|
||||||
|
|
||||||
describe('nx project.json plugin', () => {
|
describe('nx project.json plugin', () => {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { dirname, join } from 'node:path';
|
|||||||
import { ProjectConfiguration } from '../../../config/workspace-json-project-json';
|
import { ProjectConfiguration } from '../../../config/workspace-json-project-json';
|
||||||
import { toProjectName } from '../../../config/workspaces';
|
import { toProjectName } from '../../../config/workspaces';
|
||||||
import { readJsonFile } from '../../../utils/fileutils';
|
import { readJsonFile } from '../../../utils/fileutils';
|
||||||
import { NxPluginV2 } from '../../../utils/nx-plugin';
|
import { NxPluginV2 } from '../../../project-graph/plugins';
|
||||||
|
|
||||||
export const ProjectJsonProjectsPlugin: NxPluginV2 = {
|
export const ProjectJsonProjectsPlugin: NxPluginV2 = {
|
||||||
name: 'nx/core/project-json',
|
name: 'nx/core/project-json',
|
||||||
@ -23,6 +23,8 @@ export const ProjectJsonProjectsPlugin: NxPluginV2 = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default ProjectJsonProjectsPlugin;
|
||||||
|
|
||||||
export function buildProjectFromProjectJson(
|
export function buildProjectFromProjectJson(
|
||||||
json: Partial<ProjectConfiguration>,
|
json: Partial<ProjectConfiguration>,
|
||||||
path: string
|
path: string
|
||||||
|
|||||||
18
packages/nx/src/plugins/target-defaults/symbols.ts
Normal file
18
packages/nx/src/plugins/target-defaults/symbols.ts
Normal file
@ -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';
|
||||||
@ -3,7 +3,7 @@ import * as memfs from 'memfs';
|
|||||||
import '../../../src/internal-testing-utils/mock-fs';
|
import '../../../src/internal-testing-utils/mock-fs';
|
||||||
|
|
||||||
import { getTargetInfo, TargetDefaultsPlugin } from './target-defaults-plugin';
|
import { getTargetInfo, TargetDefaultsPlugin } from './target-defaults-plugin';
|
||||||
import { CreateNodesContext } from '../../utils/nx-plugin';
|
import { CreateNodesContext } from '../../project-graph/plugins';
|
||||||
const {
|
const {
|
||||||
createNodes: [, createNodesFn],
|
createNodes: [, createNodesFn],
|
||||||
} = TargetDefaultsPlugin;
|
} = TargetDefaultsPlugin;
|
||||||
|
|||||||
@ -8,31 +8,13 @@ import {
|
|||||||
} from '../../config/workspace-json-project-json';
|
} from '../../config/workspace-json-project-json';
|
||||||
import { readJsonFile } from '../../utils/fileutils';
|
import { readJsonFile } from '../../utils/fileutils';
|
||||||
import { combineGlobPatterns } from '../../utils/globs';
|
import { combineGlobPatterns } from '../../utils/globs';
|
||||||
import { NxPluginV2 } from '../../utils/nx-plugin';
|
import { NxPluginV2 } from '../../project-graph/plugins';
|
||||||
import {
|
import {
|
||||||
PackageJson,
|
PackageJson,
|
||||||
readTargetsFromPackageJson,
|
readTargetsFromPackageJson,
|
||||||
} from '../../utils/package-json';
|
} from '../../utils/package-json';
|
||||||
import { getGlobPatternsFromPackageManagerWorkspaces } from '../package-json-workspaces';
|
import { getGlobPatternsFromPackageManagerWorkspaces } from '../package-json-workspaces';
|
||||||
|
import { ONLY_MODIFIES_EXISTING_TARGET, OVERRIDE_SOURCE_FILE } from './symbols';
|
||||||
/**
|
|
||||||
* 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';
|
|
||||||
|
|
||||||
export const TargetDefaultsPlugin: NxPluginV2 = {
|
export const TargetDefaultsPlugin: NxPluginV2 = {
|
||||||
name: 'nx/core/target-defaults',
|
name: 'nx/core/target-defaults',
|
||||||
@ -127,6 +109,8 @@ export const TargetDefaultsPlugin: NxPluginV2 = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default TargetDefaultsPlugin;
|
||||||
|
|
||||||
function getExecutorToTargetMap(
|
function getExecutorToTargetMap(
|
||||||
packageJsonTargets: Record<string, TargetConfiguration>,
|
packageJsonTargets: Record<string, TargetConfiguration>,
|
||||||
projectJsonTargets: Record<string, TargetConfiguration>
|
projectJsonTargets: Record<string, TargetConfiguration>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
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 * as nxPlugin from '../../../utils/nx-plugin';
|
import * as nxPlugin from '../../../project-graph/plugins';
|
||||||
import { DeletedFileChange } from '../../file-utils';
|
import { DeletedFileChange } from '../../file-utils';
|
||||||
import { getTouchedProjectsFromProjectGlobChanges } from './project-glob-changes';
|
import { getTouchedProjectsFromProjectGlobChanges } from './project-glob-changes';
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,16 @@
|
|||||||
import { TouchedProjectLocator } from '../affected-project-graph-models';
|
import { TouchedProjectLocator } from '../affected-project-graph-models';
|
||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
import { workspaceRoot } from '../../../utils/workspace-root';
|
import { workspaceRoot } from '../../../utils/workspace-root';
|
||||||
import { getNxRequirePaths } from '../../../utils/installation-directory';
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { configurationGlobs } from '../../utils/retrieve-workspace-files';
|
import { configurationGlobs } from '../../utils/retrieve-workspace-files';
|
||||||
import { loadNxPlugins } from '../../../utils/nx-plugin';
|
import { loadNxPlugins } from '../../plugins/internal-api';
|
||||||
import { combineGlobPatterns } from '../../../utils/globs';
|
import { combineGlobPatterns } from '../../../utils/globs';
|
||||||
|
|
||||||
export const getTouchedProjectsFromProjectGlobChanges: TouchedProjectLocator =
|
export const getTouchedProjectsFromProjectGlobChanges: TouchedProjectLocator =
|
||||||
async (touchedFiles, projectGraphNodes, nxJson): Promise<string[]> => {
|
async (touchedFiles, projectGraphNodes, nxJson): Promise<string[]> => {
|
||||||
const globPattern = combineGlobPatterns(
|
const [plugins] = await loadNxPlugins(nxJson?.plugins ?? [], workspaceRoot);
|
||||||
configurationGlobs(
|
const globPattern = combineGlobPatterns(configurationGlobs(plugins));
|
||||||
await loadNxPlugins(
|
|
||||||
nxJson?.plugins,
|
|
||||||
getNxRequirePaths(workspaceRoot),
|
|
||||||
workspaceRoot
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const touchedProjects = new Set<string>();
|
const touchedProjects = new Set<string>();
|
||||||
for (const touchedFile of touchedFiles) {
|
for (const touchedFile of touchedFiles) {
|
||||||
|
|||||||
@ -13,12 +13,9 @@ 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 {
|
import { LoadedNxPlugin } from './plugins/internal-api';
|
||||||
CreateDependenciesContext,
|
import { isNxPluginV1, isNxPluginV2 } from './plugins/utils';
|
||||||
isNxPluginV1,
|
import { CreateDependenciesContext } from './plugins';
|
||||||
isNxPluginV2,
|
|
||||||
loadNxPlugins,
|
|
||||||
} from '../utils/nx-plugin';
|
|
||||||
import { getRootTsConfigPath } from '../plugins/js/utils/typescript';
|
import { getRootTsConfigPath } from '../plugins/js/utils/typescript';
|
||||||
import {
|
import {
|
||||||
FileMap,
|
FileMap,
|
||||||
@ -32,9 +29,8 @@ import { ProjectConfiguration } from '../config/workspace-json-project-json';
|
|||||||
import { readNxJson } from '../config/configuration';
|
import { readNxJson } from '../config/configuration';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { PackageJson } from '../utils/package-json';
|
import { PackageJson } from '../utils/package-json';
|
||||||
import { getNxRequirePaths } from '../utils/installation-directory';
|
|
||||||
import { output } from '../utils/output';
|
import { output } from '../utils/output';
|
||||||
import { ExternalObject, NxWorkspaceFilesExternals } from '../native';
|
import { NxWorkspaceFilesExternals } from '../native';
|
||||||
|
|
||||||
let storedFileMap: FileMap | null = null;
|
let storedFileMap: FileMap | null = null;
|
||||||
let storedAllWorkspaceFiles: FileData[] | null = null;
|
let storedAllWorkspaceFiles: FileData[] | null = null;
|
||||||
@ -69,7 +65,8 @@ export async function buildProjectGraphUsingProjectFileMap(
|
|||||||
fileMap: FileMap,
|
fileMap: FileMap,
|
||||||
allWorkspaceFiles: FileData[],
|
allWorkspaceFiles: FileData[],
|
||||||
rustReferences: NxWorkspaceFilesExternals,
|
rustReferences: NxWorkspaceFilesExternals,
|
||||||
fileMapCache: FileMapCache | null
|
fileMapCache: FileMapCache | null,
|
||||||
|
plugins: LoadedNxPlugin[]
|
||||||
): Promise<{
|
): Promise<{
|
||||||
projectGraph: ProjectGraph;
|
projectGraph: ProjectGraph;
|
||||||
projectFileMapCache: FileMapCache;
|
projectFileMapCache: FileMapCache;
|
||||||
@ -118,7 +115,8 @@ export async function buildProjectGraphUsingProjectFileMap(
|
|||||||
externalNodes,
|
externalNodes,
|
||||||
context,
|
context,
|
||||||
cachedFileData,
|
cachedFileData,
|
||||||
projectGraphVersion
|
projectGraphVersion,
|
||||||
|
plugins
|
||||||
);
|
);
|
||||||
const projectFileMapCache = createProjectFileMapCache(
|
const projectFileMapCache = createProjectFileMapCache(
|
||||||
nxJson,
|
nxJson,
|
||||||
@ -160,7 +158,8 @@ async function buildProjectGraphUsingContext(
|
|||||||
knownExternalNodes: Record<string, ProjectGraphExternalNode>,
|
knownExternalNodes: Record<string, ProjectGraphExternalNode>,
|
||||||
ctx: CreateDependenciesContext,
|
ctx: CreateDependenciesContext,
|
||||||
cachedFileData: CachedFileData,
|
cachedFileData: CachedFileData,
|
||||||
projectGraphVersion: string
|
projectGraphVersion: string,
|
||||||
|
plugins: LoadedNxPlugin[]
|
||||||
) {
|
) {
|
||||||
performance.mark('build project graph:start');
|
performance.mark('build project graph:start');
|
||||||
|
|
||||||
@ -176,7 +175,11 @@ async function buildProjectGraphUsingContext(
|
|||||||
let updatedGraph;
|
let updatedGraph;
|
||||||
let error;
|
let error;
|
||||||
try {
|
try {
|
||||||
updatedGraph = await updateProjectGraphWithPlugins(ctx, initProjectGraph);
|
updatedGraph = await updateProjectGraphWithPlugins(
|
||||||
|
ctx,
|
||||||
|
initProjectGraph,
|
||||||
|
plugins
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CreateDependenciesError) {
|
if (e instanceof CreateDependenciesError) {
|
||||||
updatedGraph = e.partialProjectGraph;
|
updatedGraph = e.partialProjectGraph;
|
||||||
@ -248,17 +251,12 @@ function createContext(
|
|||||||
|
|
||||||
async function updateProjectGraphWithPlugins(
|
async function updateProjectGraphWithPlugins(
|
||||||
context: CreateDependenciesContext,
|
context: CreateDependenciesContext,
|
||||||
initProjectGraph: ProjectGraph
|
initProjectGraph: ProjectGraph,
|
||||||
|
plugins: LoadedNxPlugin[]
|
||||||
) {
|
) {
|
||||||
const plugins = await loadNxPlugins(
|
|
||||||
context.nxJsonConfiguration?.plugins,
|
|
||||||
getNxRequirePaths(),
|
|
||||||
context.workspaceRoot,
|
|
||||||
context.projects
|
|
||||||
);
|
|
||||||
let graph = initProjectGraph;
|
let graph = initProjectGraph;
|
||||||
const errors: Array<ProcessDependenciesError | ProcessProjectGraphError> = [];
|
const errors: Array<ProcessDependenciesError | ProcessProjectGraphError> = [];
|
||||||
for (const { plugin } of plugins) {
|
for (const plugin of plugins) {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
isNxPluginV1(plugin) &&
|
isNxPluginV1(plugin) &&
|
||||||
@ -309,14 +307,14 @@ async function updateProjectGraphWithPlugins(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const createDependencyPlugins = plugins.filter(
|
const createDependencyPlugins = plugins.filter(
|
||||||
({ plugin }) => isNxPluginV2(plugin) && plugin.createDependencies
|
(plugin) => isNxPluginV2(plugin) && plugin.createDependencies
|
||||||
);
|
);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
createDependencyPlugins.map(async ({ plugin, options }) => {
|
createDependencyPlugins.map(async (plugin) => {
|
||||||
performance.mark(`${plugin.name}:createDependencies - start`);
|
performance.mark(`${plugin.name}:createDependencies - start`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dependencies = await plugin.createDependencies(options, {
|
const dependencies = await plugin.createDependencies({
|
||||||
...context,
|
...context,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
98
packages/nx/src/project-graph/error-types.ts
Normal file
98
packages/nx/src/project-graph/error-types.ts
Normal file
@ -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<MergeNodesError | CreateNodesError>,
|
||||||
|
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<CreateNodesError>,
|
||||||
|
public readonly partialResults: Array<CreateNodesResultWithContext>
|
||||||
|
) {
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -27,7 +27,7 @@ import { getDefaultPluginsSync } from '../utils/nx-plugin.deprecated';
|
|||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
import { CreateNodesResult } from '../devkit-exports';
|
import { CreateNodesResult } from '../devkit-exports';
|
||||||
import { PackageJsonProjectsNextToProjectJsonPlugin } from '../plugins/project-json/build-nodes/package-json-next-to-project-json';
|
import { PackageJsonProjectsNextToProjectJsonPlugin } from '../plugins/project-json/build-nodes/package-json-next-to-project-json';
|
||||||
import { LoadedNxPlugin } from '../utils/nx-plugin';
|
import { LoadedNxPlugin } from './plugins/internal-api';
|
||||||
|
|
||||||
export interface Change {
|
export interface Change {
|
||||||
type: string;
|
type: string;
|
||||||
@ -186,15 +186,15 @@ function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) {
|
|||||||
root,
|
root,
|
||||||
getDefaultPluginsSync(root)
|
getDefaultPluginsSync(root)
|
||||||
);
|
);
|
||||||
const plugins: LoadedNxPlugin[] = [
|
const plugins = [
|
||||||
{ plugin: PackageJsonProjectsNextToProjectJsonPlugin },
|
PackageJsonProjectsNextToProjectJsonPlugin,
|
||||||
...getDefaultPluginsSync(root),
|
...getDefaultPluginsSync(root),
|
||||||
];
|
];
|
||||||
|
|
||||||
const projectRootMap: Map<string, ProjectConfiguration> = new Map();
|
const projectRootMap: Map<string, ProjectConfiguration> = new Map();
|
||||||
|
|
||||||
// We iterate over plugins first - this ensures that plugins specified first take precedence.
|
// We iterate over plugins first - this ensures that plugins specified first take precedence.
|
||||||
for (const { plugin, options } of plugins) {
|
for (const plugin of plugins) {
|
||||||
const [pattern, createNodes] = plugin.createNodes ?? [];
|
const [pattern, createNodes] = plugin.createNodes ?? [];
|
||||||
if (!pattern) {
|
if (!pattern) {
|
||||||
continue;
|
continue;
|
||||||
@ -204,11 +204,15 @@ function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) {
|
|||||||
);
|
);
|
||||||
for (const file of matchingConfigFiles) {
|
for (const file of matchingConfigFiles) {
|
||||||
if (minimatch(file, pattern, { dot: true })) {
|
if (minimatch(file, pattern, { dot: true })) {
|
||||||
let r = createNodes(file, options, {
|
let r = createNodes(
|
||||||
nxJsonConfiguration: nxJson,
|
file,
|
||||||
workspaceRoot: root,
|
{},
|
||||||
configFiles: matchingConfigFiles,
|
{
|
||||||
}) as CreateNodesResult;
|
nxJsonConfiguration: nxJson,
|
||||||
|
workspaceRoot: root,
|
||||||
|
configFiles: matchingConfigFiles,
|
||||||
|
}
|
||||||
|
) as CreateNodesResult;
|
||||||
for (const node in r.projects) {
|
for (const node in r.projects) {
|
||||||
const project = {
|
const project = {
|
||||||
root: node,
|
root: node,
|
||||||
|
|||||||
3
packages/nx/src/project-graph/plugins/index.ts
Normal file
3
packages/nx/src/project-graph/plugins/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './public-api';
|
||||||
|
|
||||||
|
export { readPluginPackageJson, registerPluginTSTranspiler } from './loader';
|
||||||
144
packages/nx/src/project-graph/plugins/internal-api.ts
Normal file
144
packages/nx/src/project-graph/plugins/internal-api.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// This file contains the bits and bobs of the internal API for loading and interacting with Nx plugins.
|
||||||
|
// For the public API, used by plugin authors, see `./public-api.ts`.
|
||||||
|
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import { PluginConfiguration } from '../../config/nx-json';
|
||||||
|
import { NxPluginV1 } from '../../utils/nx-plugin.deprecated';
|
||||||
|
import { shouldMergeAngularProjects } from '../../adapter/angular-json';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateDependencies,
|
||||||
|
CreateDependenciesContext,
|
||||||
|
CreateNodesContext,
|
||||||
|
CreateNodesResult,
|
||||||
|
NxPluginV2,
|
||||||
|
} from './public-api';
|
||||||
|
import { ProjectGraphProcessor } from '../../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<CreateNodesResultWithContext[]>
|
||||||
|
];
|
||||||
|
readonly createDependencies?: (
|
||||||
|
context: CreateDependenciesContext
|
||||||
|
) => ReturnType<CreateDependencies>;
|
||||||
|
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;
|
||||||
|
pluginName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NormalizedPlugin = NxPluginV2 &
|
||||||
|
Pick<NxPluginV1, 'processProjectGraph'>;
|
||||||
|
|
||||||
|
// Short lived cache (cleared between cmd runs)
|
||||||
|
// holding resolved nx plugin objects.
|
||||||
|
// Allows loaded plugins to not be reloaded when
|
||||||
|
// referenced multiple times.
|
||||||
|
export const nxPluginCache: Map<
|
||||||
|
unknown,
|
||||||
|
[Promise<LoadedNxPlugin>, () => void]
|
||||||
|
> = new Map();
|
||||||
|
|
||||||
|
export async function loadNxPlugins(
|
||||||
|
plugins: PluginConfiguration[],
|
||||||
|
root = workspaceRoot
|
||||||
|
): Promise<[LoadedNxPlugin[], () => void]> {
|
||||||
|
const result: Promise<LoadedNxPlugin>[] = [];
|
||||||
|
|
||||||
|
const loadingMethod =
|
||||||
|
process.env.NX_ISOLATE_PLUGINS === 'true'
|
||||||
|
? loadNxPluginInIsolation
|
||||||
|
: loadNxPlugin;
|
||||||
|
|
||||||
|
plugins = await normalizePlugins(plugins, root);
|
||||||
|
|
||||||
|
const cleanupFunctions: Array<() => void> = [];
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
const [loadedPluginPromise, cleanup] = loadingMethod(plugin, root);
|
||||||
|
result.push(loadedPluginPromise);
|
||||||
|
cleanupFunctions.push(cleanup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
await Promise.all(result),
|
||||||
|
() => {
|
||||||
|
for (const fn of cleanupFunctions) {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
if (unregisterPluginTSTranspiler) {
|
||||||
|
unregisterPluginTSTranspiler();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function normalizePlugins(plugins: PluginConfiguration[], root: string) {
|
||||||
|
plugins ??= [];
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return [
|
||||||
|
join(__dirname, '../../plugins/js'),
|
||||||
|
join(__dirname, '../../plugins/target-defaults/target-defaults-plugin'),
|
||||||
|
...(shouldMergeAngularProjects(root, false)
|
||||||
|
? [join(__dirname, '../../adapter/angular-json')]
|
||||||
|
: []),
|
||||||
|
join(__dirname, '../../plugins/package-json-workspaces'),
|
||||||
|
join(__dirname, '../../plugins/project-json/build-nodes/project-json'),
|
||||||
|
];
|
||||||
|
}
|
||||||
24
packages/nx/src/project-graph/plugins/isolation/index.ts
Normal file
24
packages/nx/src/project-graph/plugins/isolation/index.ts
Normal file
@ -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<LoadedNxPlugin>, () => void]
|
||||||
|
>();
|
||||||
|
|
||||||
|
export function loadNxPluginInIsolation(
|
||||||
|
plugin: PluginConfiguration,
|
||||||
|
root = workspaceRoot
|
||||||
|
): [Promise<LoadedNxPlugin>, () => 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];
|
||||||
|
}
|
||||||
153
packages/nx/src/project-graph/plugins/isolation/messaging.ts
Normal file
153
packages/nx/src/project-graph/plugins/isolation/messaging.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import {
|
||||||
|
ProjectGraph,
|
||||||
|
ProjectGraphProcessorContext,
|
||||||
|
} 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';
|
||||||
|
payload: {
|
||||||
|
plugin: PluginConfiguration;
|
||||||
|
root: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginWorkerLoadResult {
|
||||||
|
type: 'load-result';
|
||||||
|
payload:
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
createNodesPattern: string;
|
||||||
|
hasCreateDependencies: boolean;
|
||||||
|
hasProcessProjectGraph: boolean;
|
||||||
|
success: true;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginWorkerCreateNodesMessage {
|
||||||
|
type: 'createNodes';
|
||||||
|
payload: {
|
||||||
|
configFiles: string[];
|
||||||
|
context: CreateNodesContext;
|
||||||
|
tx: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginWorkerCreateNodesResult {
|
||||||
|
type: 'createNodesResult';
|
||||||
|
payload:
|
||||||
|
| {
|
||||||
|
success: true;
|
||||||
|
result: Awaited<ReturnType<LoadedNxPlugin['createNodes'][1]>>;
|
||||||
|
tx: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
error: string;
|
||||||
|
tx: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginCreateDependenciesMessage {
|
||||||
|
type: 'createDependencies';
|
||||||
|
payload: {
|
||||||
|
context: CreateDependenciesContext;
|
||||||
|
tx: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginCreateDependenciesResult {
|
||||||
|
type: 'createDependenciesResult';
|
||||||
|
payload:
|
||||||
|
| {
|
||||||
|
dependencies: ReturnType<LoadedNxPlugin['createDependencies']>;
|
||||||
|
success: true;
|
||||||
|
tx: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
error: string;
|
||||||
|
tx: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginWorkerProcessProjectGraphMessage {
|
||||||
|
type: 'processProjectGraph';
|
||||||
|
payload: {
|
||||||
|
graph: ProjectGraph;
|
||||||
|
ctx: ProjectGraphProcessorContext;
|
||||||
|
tx: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginWorkerProcessProjectGraphResult {
|
||||||
|
type: 'processProjectGraphResult';
|
||||||
|
payload:
|
||||||
|
| {
|
||||||
|
graph: ProjectGraph;
|
||||||
|
success: true;
|
||||||
|
tx: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
error: string;
|
||||||
|
tx: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PluginWorkerMessage =
|
||||||
|
| PluginWorkerLoadMessage
|
||||||
|
| PluginWorkerCreateNodesMessage
|
||||||
|
| PluginCreateDependenciesMessage
|
||||||
|
| PluginWorkerProcessProjectGraphMessage;
|
||||||
|
|
||||||
|
export type PluginWorkerResult =
|
||||||
|
| PluginWorkerLoadResult
|
||||||
|
| PluginWorkerCreateNodesResult
|
||||||
|
| PluginCreateDependenciesResult
|
||||||
|
| PluginWorkerProcessProjectGraphResult;
|
||||||
|
|
||||||
|
type MaybePromise<T> = T | Promise<T>;
|
||||||
|
|
||||||
|
// The handler can return a message to be sent back to the process from which the message originated
|
||||||
|
type MessageHandlerReturn<T extends PluginWorkerMessage | PluginWorkerResult> =
|
||||||
|
T extends PluginWorkerResult
|
||||||
|
? MaybePromise<PluginWorkerMessage | void>
|
||||||
|
: MaybePromise<PluginWorkerResult | void>;
|
||||||
|
|
||||||
|
// Takes a message and a map of handlers and calls the appropriate handler
|
||||||
|
// type safe and requires all handlers to be handled
|
||||||
|
export async function consumeMessage<
|
||||||
|
T extends PluginWorkerMessage | PluginWorkerResult
|
||||||
|
>(
|
||||||
|
raw: string | T,
|
||||||
|
handlers: {
|
||||||
|
[K in T['type']]: (
|
||||||
|
// Extract restricts the type of payload to the payload of the message with the type K
|
||||||
|
payload: Extract<T, { type: K }>['payload']
|
||||||
|
) => MessageHandlerReturn<T>;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const message: T = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
|
const handler = handlers[message.type];
|
||||||
|
if (handler) {
|
||||||
|
const response = await handler(message.payload);
|
||||||
|
if (response) {
|
||||||
|
process.send!(createMessage(response));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unhandled message type: ${message.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMessage(
|
||||||
|
message: PluginWorkerMessage | PluginWorkerResult
|
||||||
|
): string {
|
||||||
|
return JSON.stringify(message);
|
||||||
|
}
|
||||||
250
packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts
Normal file
250
packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import { ChildProcess, fork } from 'child_process';
|
||||||
|
import path = require('path');
|
||||||
|
|
||||||
|
import { PluginConfiguration } from '../../../config/nx-json';
|
||||||
|
|
||||||
|
// TODO (@AgentEnder): After scoped verbose logging is implemented, re-add verbose logs here.
|
||||||
|
// import { logger } from '../../utils/logger';
|
||||||
|
|
||||||
|
import { LoadedNxPlugin, nxPluginCache } from '../internal-api';
|
||||||
|
import { PluginWorkerResult, consumeMessage, createMessage } from './messaging';
|
||||||
|
|
||||||
|
const cleanupFunctions = new Set<() => void>();
|
||||||
|
|
||||||
|
const pluginNames = new Map<ChildProcess, string>();
|
||||||
|
|
||||||
|
interface PendingPromise {
|
||||||
|
promise: Promise<unknown>;
|
||||||
|
resolver: (result: any) => void;
|
||||||
|
rejector: (err: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadRemoteNxPlugin(
|
||||||
|
plugin: PluginConfiguration,
|
||||||
|
root: string
|
||||||
|
): [Promise<LoadedNxPlugin>, () => void] {
|
||||||
|
// this should only really be true when running unit tests within
|
||||||
|
// the Nx repo. We still need to start the worker in this case,
|
||||||
|
// but its typescript.
|
||||||
|
const isWorkerTypescript = path.extname(__filename) === '.ts';
|
||||||
|
const workerPath = path.join(__dirname, 'plugin-worker');
|
||||||
|
const worker = fork(workerPath, [], {
|
||||||
|
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
...(isWorkerTypescript
|
||||||
|
? {
|
||||||
|
// Ensures that the worker uses the same tsconfig as the main process
|
||||||
|
TS_NODE_PROJECT: path.join(__dirname, '../../../tsconfig.lib.json'),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
execArgv: [
|
||||||
|
...process.execArgv,
|
||||||
|
// If the worker is typescript, we need to register ts-node
|
||||||
|
...(isWorkerTypescript ? ['-r', 'ts-node/register'] : []),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
worker.send(createMessage({ type: 'load', payload: { plugin, root } }));
|
||||||
|
|
||||||
|
// logger.verbose(`[plugin-worker] started worker: ${worker.pid}`);
|
||||||
|
|
||||||
|
const pendingPromises = new Map<string, PendingPromise>();
|
||||||
|
|
||||||
|
const exitHandler = createWorkerExitHandler(worker, pendingPromises);
|
||||||
|
|
||||||
|
const cleanupFunction = () => {
|
||||||
|
worker.off('exit', exitHandler);
|
||||||
|
shutdownPluginWorker(worker, pendingPromises);
|
||||||
|
};
|
||||||
|
|
||||||
|
cleanupFunctions.add(cleanupFunction);
|
||||||
|
|
||||||
|
return [
|
||||||
|
new Promise<LoadedNxPlugin>((res, rej) => {
|
||||||
|
worker.on(
|
||||||
|
'message',
|
||||||
|
createWorkerHandler(worker, pendingPromises, res, rej)
|
||||||
|
);
|
||||||
|
worker.on('exit', exitHandler);
|
||||||
|
}),
|
||||||
|
() => {
|
||||||
|
cleanupFunction();
|
||||||
|
cleanupFunctions.delete(cleanupFunction);
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shutdownPluginWorker(
|
||||||
|
worker: ChildProcess,
|
||||||
|
pendingPromises: Map<string, PendingPromise>
|
||||||
|
) {
|
||||||
|
// Clears the plugin cache so no refs to the workers are held
|
||||||
|
nxPluginCache.clear();
|
||||||
|
|
||||||
|
// logger.verbose(`[plugin-pool] starting worker shutdown`);
|
||||||
|
|
||||||
|
// Other things may be interacting with the worker.
|
||||||
|
// Wait for all pending promises to be done before killing the worker
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(pendingPromises.values()).map(({ promise }) => promise)
|
||||||
|
);
|
||||||
|
|
||||||
|
worker.kill('SIGINT');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a message handler for the given worker.
|
||||||
|
* @param worker Instance of plugin-worker
|
||||||
|
* @param pending Set of pending promises
|
||||||
|
* @param onload Resolver for RemotePlugin promise
|
||||||
|
* @param onloadError Rejecter for RemotePlugin promise
|
||||||
|
* @returns Function to handle messages from the worker
|
||||||
|
*/
|
||||||
|
function createWorkerHandler(
|
||||||
|
worker: ChildProcess,
|
||||||
|
pending: Map<string, PendingPromise>,
|
||||||
|
onload: (plugin: LoadedNxPlugin) => void,
|
||||||
|
onloadError: (err?: unknown) => void
|
||||||
|
) {
|
||||||
|
let pluginName: string;
|
||||||
|
|
||||||
|
return function (message: string) {
|
||||||
|
const parsed = JSON.parse(message);
|
||||||
|
// logger.verbose(
|
||||||
|
// `[plugin-pool] received message: ${parsed.type} from ${
|
||||||
|
// pluginName ?? worker.pid
|
||||||
|
// }`
|
||||||
|
// );
|
||||||
|
consumeMessage<PluginWorkerResult>(parsed, {
|
||||||
|
'load-result': (result) => {
|
||||||
|
if (result.success) {
|
||||||
|
const { name, createNodesPattern } = result;
|
||||||
|
pluginName = name;
|
||||||
|
pluginNames.set(worker, pluginName);
|
||||||
|
onload({
|
||||||
|
name,
|
||||||
|
createNodes: createNodesPattern
|
||||||
|
? [
|
||||||
|
createNodesPattern,
|
||||||
|
(configFiles, ctx) => {
|
||||||
|
const tx = pluginName + ':createNodes:' + performance.now();
|
||||||
|
return registerPendingPromise(tx, pending, () => {
|
||||||
|
worker.send(
|
||||||
|
createMessage({
|
||||||
|
type: 'createNodes',
|
||||||
|
payload: { configFiles, context: ctx, tx },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
|
createDependencies: result.hasCreateDependencies
|
||||||
|
? (ctx) => {
|
||||||
|
const tx =
|
||||||
|
pluginName + ':createDependencies:' + performance.now();
|
||||||
|
return registerPendingPromise(tx, pending, () => {
|
||||||
|
worker.send(
|
||||||
|
createMessage({
|
||||||
|
type: 'createDependencies',
|
||||||
|
payload: { context: ctx, tx },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
processProjectGraph: result.hasProcessProjectGraph
|
||||||
|
? (graph, ctx) => {
|
||||||
|
const tx =
|
||||||
|
pluginName + ':processProjectGraph:' + performance.now();
|
||||||
|
return registerPendingPromise(tx, pending, () => {
|
||||||
|
worker.send(
|
||||||
|
createMessage({
|
||||||
|
type: 'processProjectGraph',
|
||||||
|
payload: { graph, ctx, tx },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
} else if (result.success === false) {
|
||||||
|
onloadError(result.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createDependenciesResult: ({ tx, ...result }) => {
|
||||||
|
const { resolver, rejector } = pending.get(tx);
|
||||||
|
if (result.success) {
|
||||||
|
resolver(result.dependencies);
|
||||||
|
} else if (result.success === false) {
|
||||||
|
rejector(result.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createNodesResult: ({ tx, ...result }) => {
|
||||||
|
const { resolver, rejector } = pending.get(tx);
|
||||||
|
if (result.success) {
|
||||||
|
resolver(result.result);
|
||||||
|
} else if (result.success === false) {
|
||||||
|
rejector(result.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
processProjectGraphResult: ({ tx, ...result }) => {
|
||||||
|
const { resolver, rejector } = pending.get(tx);
|
||||||
|
if (result.success) {
|
||||||
|
resolver(result.graph);
|
||||||
|
} else if (result.success === false) {
|
||||||
|
rejector(result.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWorkerExitHandler(
|
||||||
|
worker: ChildProcess,
|
||||||
|
pendingPromises: Map<string, PendingPromise>
|
||||||
|
) {
|
||||||
|
return () => {
|
||||||
|
for (const [_, pendingPromise] of pendingPromises) {
|
||||||
|
pendingPromise.rejector(
|
||||||
|
new Error(
|
||||||
|
`Plugin worker ${
|
||||||
|
pluginNames.get(worker) ?? worker.pid
|
||||||
|
} exited unexpectedly with code ${worker.exitCode}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('exit', () => {
|
||||||
|
for (const fn of cleanupFunctions) {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function registerPendingPromise(
|
||||||
|
tx: string,
|
||||||
|
pending: Map<string, PendingPromise>,
|
||||||
|
callback: () => void
|
||||||
|
): Promise<any> {
|
||||||
|
let resolver, rejector;
|
||||||
|
|
||||||
|
const promise = new Promise((res, rej) => {
|
||||||
|
resolver = res;
|
||||||
|
rejector = rej;
|
||||||
|
|
||||||
|
callback();
|
||||||
|
}).finally(() => {
|
||||||
|
pending.delete(tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
pending.set(tx, {
|
||||||
|
promise,
|
||||||
|
resolver,
|
||||||
|
rejector,
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import { consumeMessage, PluginWorkerMessage } from './messaging';
|
||||||
|
import { LoadedNxPlugin } from '../internal-api';
|
||||||
|
import { loadNxPlugin } from '../loader';
|
||||||
|
import { runCreateNodesInParallel } from '../utils';
|
||||||
|
|
||||||
|
global.NX_GRAPH_CREATION = true;
|
||||||
|
|
||||||
|
let plugin: LoadedNxPlugin;
|
||||||
|
|
||||||
|
process.on('message', async (message: string) => {
|
||||||
|
consumeMessage<PluginWorkerMessage>(message, {
|
||||||
|
load: async ({ plugin: pluginConfiguration, root }) => {
|
||||||
|
process.chdir(root);
|
||||||
|
try {
|
||||||
|
const [promise] = loadNxPlugin(pluginConfiguration, root);
|
||||||
|
plugin = await promise;
|
||||||
|
return {
|
||||||
|
type: 'load-result',
|
||||||
|
payload: {
|
||||||
|
name: plugin.name,
|
||||||
|
createNodesPattern: plugin.createNodes?.[0],
|
||||||
|
hasCreateDependencies:
|
||||||
|
'createDependencies' in plugin && !!plugin.createDependencies,
|
||||||
|
hasProcessProjectGraph:
|
||||||
|
'processProjectGraph' in plugin && !!plugin.processProjectGraph,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
type: 'load-result',
|
||||||
|
payload: {
|
||||||
|
success: false,
|
||||||
|
error: `Could not load plugin ${plugin} \n ${
|
||||||
|
e instanceof Error ? e.stack : ''
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createNodes: async ({ configFiles, context, tx }) => {
|
||||||
|
try {
|
||||||
|
const result = await plugin.createNodes[1](configFiles, context);
|
||||||
|
return {
|
||||||
|
type: 'createNodesResult',
|
||||||
|
payload: { result, success: true, tx },
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
type: 'createNodesResult',
|
||||||
|
payload: { success: false, error: e.stack, tx },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createDependencies: async ({ context, tx }) => {
|
||||||
|
try {
|
||||||
|
const result = await plugin.createDependencies(context);
|
||||||
|
return {
|
||||||
|
type: 'createDependenciesResult',
|
||||||
|
payload: { dependencies: result, success: true, tx },
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
type: 'createDependenciesResult',
|
||||||
|
payload: { success: false, error: e.stack, tx },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
processProjectGraph: async ({ graph, ctx, tx }) => {
|
||||||
|
try {
|
||||||
|
const result = await plugin.processProjectGraph(graph, ctx);
|
||||||
|
return {
|
||||||
|
type: 'processProjectGraphResult',
|
||||||
|
payload: { graph: result, success: true, tx },
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
type: 'processProjectGraphResult',
|
||||||
|
payload: { success: false, error: e.stack, tx },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
294
packages/nx/src/project-graph/plugins/loader.ts
Normal file
294
packages/nx/src/project-graph/plugins/loader.ts
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
// This file contains methods and utilities that should **only** be used by the plugin worker.
|
||||||
|
|
||||||
|
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||||
|
|
||||||
|
import { join } from 'node:path/posix';
|
||||||
|
import { getNxRequirePaths } from '../../utils/installation-directory';
|
||||||
|
import {
|
||||||
|
PackageJson,
|
||||||
|
readModulePackageJsonWithoutFallbacks,
|
||||||
|
} from '../../utils/package-json';
|
||||||
|
import { readJsonFile } from '../../utils/fileutils';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import { existsSync } from 'node:fs';
|
||||||
|
import { readTsConfig } from '../../utils/typescript';
|
||||||
|
import {
|
||||||
|
registerTranspiler,
|
||||||
|
registerTsConfigPaths,
|
||||||
|
} from '../../plugins/js/utils/register';
|
||||||
|
import {
|
||||||
|
createProjectRootMappingsFromProjectConfigurations,
|
||||||
|
findProjectForPath,
|
||||||
|
} from '../utils/find-project-for-path';
|
||||||
|
import { normalizePath } from '../../utils/path';
|
||||||
|
import { logger } from '../../utils/logger';
|
||||||
|
|
||||||
|
import type * as ts from 'typescript';
|
||||||
|
import { extname } from 'node:path';
|
||||||
|
import { NxPlugin } from './public-api';
|
||||||
|
import path = require('node:path/posix');
|
||||||
|
import {
|
||||||
|
ExpandedPluginConfiguration,
|
||||||
|
PluginConfiguration,
|
||||||
|
} from '../../config/nx-json';
|
||||||
|
import { retrieveProjectConfigurationsWithoutPluginInference } from '../utils/retrieve-workspace-files';
|
||||||
|
import { normalizeNxPlugin } from './utils';
|
||||||
|
import { LoadedNxPlugin } from './internal-api';
|
||||||
|
|
||||||
|
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'
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
path: localPluginPackageJson,
|
||||||
|
json: readJsonFile(localPluginPackageJson),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveLocalNxPlugin(
|
||||||
|
importPath: string,
|
||||||
|
projects: Record<string, ProjectConfiguration>,
|
||||||
|
root = workspaceRoot
|
||||||
|
): { path: string; projectConfig: ProjectConfiguration } | null {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
unregisterPluginTSTranspiler = () => {
|
||||||
|
cleanupFns.forEach((fn) => fn?.());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookupLocalPlugin(
|
||||||
|
importPath: string,
|
||||||
|
projects: Record<string, ProjectConfiguration>,
|
||||||
|
root = workspaceRoot
|
||||||
|
) {
|
||||||
|
const plugin = findNxProjectForImportPath(importPath, projects, root);
|
||||||
|
if (!plugin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectConfig: ProjectConfiguration = projects[plugin];
|
||||||
|
return { path: path.join(root, projectConfig.root), projectConfig };
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNxProjectForImportPath(
|
||||||
|
importPath: string,
|
||||||
|
projects: Record<string, ProjectConfiguration>,
|
||||||
|
root = workspaceRoot
|
||||||
|
): string | null {
|
||||||
|
const tsConfigPaths: Record<string, string[]> = readTsConfigPaths(root);
|
||||||
|
const possiblePaths = tsConfigPaths[importPath]?.map((p) =>
|
||||||
|
normalizePath(path.relative(root, path.join(root, p)))
|
||||||
|
);
|
||||||
|
if (possiblePaths?.length) {
|
||||||
|
const projectRootMappings =
|
||||||
|
createProjectRootMappingsFromProjectConfigurations(projects);
|
||||||
|
for (const tsConfigPath of possiblePaths) {
|
||||||
|
const nxProject = findProjectForPath(tsConfigPath, projectRootMappings);
|
||||||
|
if (nxProject) {
|
||||||
|
return nxProject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.verbose(
|
||||||
|
'Unable to find local plugin',
|
||||||
|
possiblePaths,
|
||||||
|
projectRootMappings
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
'Unable to resolve local plugin with import path ' + importPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tsconfigPaths: Record<string, string[]>;
|
||||||
|
|
||||||
|
function readTsConfigPaths(root: string = workspaceRoot) {
|
||||||
|
if (!tsconfigPaths) {
|
||||||
|
const tsconfigPath: string | null = ['tsconfig.base.json', 'tsconfig.json']
|
||||||
|
.map((x) => path.join(root, x))
|
||||||
|
.filter((x) => existsSync(x))[0];
|
||||||
|
if (!tsconfigPath) {
|
||||||
|
throw new Error('unable to find tsconfig.base.json or tsconfig.json');
|
||||||
|
}
|
||||||
|
const { compilerOptions } = readJsonFile(tsconfigPath);
|
||||||
|
tsconfigPaths = compilerOptions?.paths;
|
||||||
|
}
|
||||||
|
return tsconfigPaths ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPluginMainFromProjectConfiguration(
|
||||||
|
plugin: ProjectConfiguration
|
||||||
|
): string | null {
|
||||||
|
const { main } =
|
||||||
|
Object.values(plugin.targets).find((x) =>
|
||||||
|
[
|
||||||
|
'@nx/js:tsc',
|
||||||
|
'@nrwl/js:tsc',
|
||||||
|
'@nx/js:swc',
|
||||||
|
'@nrwl/js:swc',
|
||||||
|
'@nx/node:package',
|
||||||
|
'@nrwl/node:package',
|
||||||
|
].includes(x.executor)
|
||||||
|
)?.options ||
|
||||||
|
plugin.targets?.build?.options ||
|
||||||
|
{};
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPluginPathAndName(
|
||||||
|
moduleName: string,
|
||||||
|
paths: string[],
|
||||||
|
projects: Record<string, ProjectConfiguration>,
|
||||||
|
root: string
|
||||||
|
) {
|
||||||
|
let pluginPath: string;
|
||||||
|
let registerTSTranspiler = false;
|
||||||
|
try {
|
||||||
|
pluginPath = require.resolve(moduleName, {
|
||||||
|
paths,
|
||||||
|
});
|
||||||
|
const extension = path.extname(pluginPath);
|
||||||
|
registerTSTranspiler = extension === '.ts';
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'MODULE_NOT_FOUND') {
|
||||||
|
const plugin = resolveLocalNxPlugin(moduleName, projects, root);
|
||||||
|
if (plugin) {
|
||||||
|
registerTSTranspiler = true;
|
||||||
|
const main = readPluginMainFromProjectConfiguration(
|
||||||
|
plugin.projectConfig
|
||||||
|
);
|
||||||
|
pluginPath = main ? path.join(root, main) : plugin.path;
|
||||||
|
} else {
|
||||||
|
logger.error(`Plugin listed in \`nx.json\` not found: ${moduleName}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const packageJsonPath = path.join(pluginPath, 'package.json');
|
||||||
|
|
||||||
|
// Register the ts-transpiler if we are pointing to a
|
||||||
|
// plain ts file that's not part of a plugin project
|
||||||
|
if (registerTSTranspiler) {
|
||||||
|
registerPluginTSTranspiler();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name } =
|
||||||
|
!['.ts', '.js'].some((x) => extname(moduleName) === x) && // Not trying to point to a ts or js file
|
||||||
|
existsSync(packageJsonPath) // plugin has a package.json
|
||||||
|
? readJsonFile(packageJsonPath) // read name from package.json
|
||||||
|
: { name: moduleName };
|
||||||
|
return { pluginPath, name };
|
||||||
|
}
|
||||||
|
|
||||||
|
let projectsWithoutInference: Record<string, ProjectConfiguration>;
|
||||||
|
|
||||||
|
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> {
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleName =
|
||||||
|
typeof pluginConfiguration === 'string'
|
||||||
|
? pluginConfiguration
|
||||||
|
: pluginConfiguration.plugin;
|
||||||
|
|
||||||
|
performance.mark(`Load Nx Plugin: ${moduleName} - start`);
|
||||||
|
let { pluginPath, name } = await getPluginPathAndName(
|
||||||
|
moduleName,
|
||||||
|
paths,
|
||||||
|
projectsWithoutInference,
|
||||||
|
root
|
||||||
|
);
|
||||||
|
const plugin = normalizeNxPlugin(await importPluginModule(pluginPath));
|
||||||
|
plugin.name ??= name;
|
||||||
|
performance.mark(`Load Nx Plugin: ${moduleName} - end`);
|
||||||
|
performance.measure(
|
||||||
|
`Load Nx Plugin: ${moduleName}`,
|
||||||
|
`Load Nx Plugin: ${moduleName} - start`,
|
||||||
|
`Load Nx Plugin: ${moduleName} - end`
|
||||||
|
);
|
||||||
|
return new LoadedNxPlugin(plugin, pluginConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importPluginModule(pluginPath: string): Promise<NxPlugin> {
|
||||||
|
const m = await import(pluginPath);
|
||||||
|
if (
|
||||||
|
m.default &&
|
||||||
|
('createNodes' in m.default || 'createDependencies' in m.default)
|
||||||
|
) {
|
||||||
|
return m.default;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
123
packages/nx/src/project-graph/plugins/public-api.ts
Normal file
123
packages/nx/src/project-graph/plugins/public-api.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// This file represents the public API for plugins which live in nx.json's plugins array.
|
||||||
|
// For methods to interact with plugins from within Nx, see `./internal-api.ts`.
|
||||||
|
|
||||||
|
import { NxPluginV1 } from '../../utils/nx-plugin.deprecated';
|
||||||
|
import {
|
||||||
|
FileMap,
|
||||||
|
ProjectGraph,
|
||||||
|
ProjectGraphExternalNode,
|
||||||
|
} from '../../config/project-graph';
|
||||||
|
|
||||||
|
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||||
|
|
||||||
|
import { NxJsonConfiguration } from '../../config/nx-json';
|
||||||
|
import { RawProjectGraphDependency } from '../project-graph-builder';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context for {@link CreateNodesFunction}
|
||||||
|
*/
|
||||||
|
export interface CreateNodesContext {
|
||||||
|
readonly nxJsonConfiguration: NxJsonConfiguration;
|
||||||
|
readonly workspaceRoot: string;
|
||||||
|
/**
|
||||||
|
* The subset of configuration files which match the createNodes pattern
|
||||||
|
*/
|
||||||
|
readonly configFiles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function which parses a configuration file into a set of nodes.
|
||||||
|
* Used for creating nodes for the {@link ProjectGraph}
|
||||||
|
*/
|
||||||
|
export type CreateNodesFunction<T = unknown> = (
|
||||||
|
projectConfigurationFile: string,
|
||||||
|
options: T | undefined,
|
||||||
|
context: CreateNodesContext
|
||||||
|
) => CreateNodesResult | Promise<CreateNodesResult>;
|
||||||
|
|
||||||
|
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||||
|
|
||||||
|
export interface CreateNodesResult {
|
||||||
|
/**
|
||||||
|
* A map of project root -> project configuration
|
||||||
|
*/
|
||||||
|
projects?: Record<string, Optional<ProjectConfiguration, 'root'>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of external node name -> external node. External nodes do not have a root, so the key is their name.
|
||||||
|
*/
|
||||||
|
externalNodes?: Record<string, ProjectGraphExternalNode>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pair of file patterns and {@link CreateNodesFunction}
|
||||||
|
*/
|
||||||
|
export type CreateNodes<T = unknown> = readonly [
|
||||||
|
projectFilePattern: string,
|
||||||
|
createNodesFunction: CreateNodesFunction<T>
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context for {@link CreateDependencies}
|
||||||
|
*/
|
||||||
|
export interface CreateDependenciesContext {
|
||||||
|
/**
|
||||||
|
* The external nodes that have been added to the graph.
|
||||||
|
*/
|
||||||
|
readonly externalNodes: ProjectGraph['externalNodes'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration of each project in the workspace.
|
||||||
|
*/
|
||||||
|
readonly projects: Record<string, ProjectConfiguration>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `nx.json` configuration from the workspace
|
||||||
|
*/
|
||||||
|
readonly nxJsonConfiguration: NxJsonConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All files in the workspace
|
||||||
|
*/
|
||||||
|
readonly fileMap: FileMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Files changes since last invocation
|
||||||
|
*/
|
||||||
|
readonly filesToProcess: FileMap;
|
||||||
|
|
||||||
|
readonly workspaceRoot: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function which parses files in the workspace to create dependencies in the {@link ProjectGraph}
|
||||||
|
* Use {@link validateDependency} to validate dependencies
|
||||||
|
*/
|
||||||
|
export type CreateDependencies<T = unknown> = (
|
||||||
|
options: T | undefined,
|
||||||
|
context: CreateDependenciesContext
|
||||||
|
) => RawProjectGraphDependency[] | Promise<RawProjectGraphDependency[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plugin for Nx which creates nodes and dependencies for the {@link ProjectGraph}
|
||||||
|
*/
|
||||||
|
export type NxPluginV2<TOptions = unknown> = {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a file pattern and function that retrieves configuration info from
|
||||||
|
* those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile }
|
||||||
|
*/
|
||||||
|
createNodes?: CreateNodes<TOptions>;
|
||||||
|
|
||||||
|
// Todo(@AgentEnder): This shouldn't be a full processor, since its only responsible for defining edges between projects. What do we want the API to be?
|
||||||
|
/**
|
||||||
|
* Provides a function to analyze files to create dependencies for the {@link ProjectGraph}
|
||||||
|
*/
|
||||||
|
createDependencies?: CreateDependencies<TOptions>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plugin for Nx
|
||||||
|
*/
|
||||||
|
export type NxPlugin = NxPluginV1 | NxPluginV2;
|
||||||
123
packages/nx/src/project-graph/plugins/utils.ts
Normal file
123
packages/nx/src/project-graph/plugins/utils.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import { dirname } from 'node:path';
|
||||||
|
|
||||||
|
import { toProjectName } from '../../config/workspaces';
|
||||||
|
import { combineGlobPatterns } from '../../utils/globs';
|
||||||
|
|
||||||
|
import type { NxPluginV1 } from '../../utils/nx-plugin.deprecated';
|
||||||
|
import type {
|
||||||
|
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 | LoadedNxPlugin
|
||||||
|
): plugin is NxPluginV1 {
|
||||||
|
return 'processProjectGraph' in plugin || 'projectFilePatterns' in plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeNxPlugin(plugin: NxPlugin): NormalizedPlugin {
|
||||||
|
if (isNxPluginV2(plugin)) {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
if (isNxPluginV1(plugin) && plugin.projectFilePatterns) {
|
||||||
|
return {
|
||||||
|
...plugin,
|
||||||
|
createNodes: [
|
||||||
|
`*/**/${combineGlobPatterns(plugin.projectFilePatterns)}`,
|
||||||
|
(configFilePath) => {
|
||||||
|
const root = dirname(configFilePath);
|
||||||
|
return {
|
||||||
|
projects: {
|
||||||
|
[root]: {
|
||||||
|
name: toProjectName(configFilePath),
|
||||||
|
targets: plugin.registerProjectTargets?.(configFilePath),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runCreateNodesInParallel(
|
||||||
|
configFiles: string[],
|
||||||
|
plugin: NormalizedPlugin,
|
||||||
|
options: unknown,
|
||||||
|
context: CreateNodesContext
|
||||||
|
): Promise<CreateNodesResultWithContext[]> {
|
||||||
|
performance.mark(`${plugin.name}:createNodes - start`);
|
||||||
|
|
||||||
|
const promises: Array<
|
||||||
|
CreateNodesResultWithContext | Promise<CreateNodesResultWithContext>
|
||||||
|
> = 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<T, T2 = T>(
|
||||||
|
arr: Array<T | T2>,
|
||||||
|
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];
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
ProjectGraphProjectNode,
|
ProjectGraphProjectNode,
|
||||||
} from '../config/project-graph';
|
} from '../config/project-graph';
|
||||||
import { ProjectConfiguration } from '../config/workspace-json-project-json';
|
import { ProjectConfiguration } from '../config/workspace-json-project-json';
|
||||||
import { CreateDependenciesContext } from '../utils/nx-plugin';
|
import { CreateDependenciesContext } from './plugins';
|
||||||
import { getFileMap } from './build-project-graph';
|
import { getFileMap } from './build-project-graph';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -26,15 +26,17 @@ import {
|
|||||||
retrieveWorkspaceFiles,
|
retrieveWorkspaceFiles,
|
||||||
} from './utils/retrieve-workspace-files';
|
} from './utils/retrieve-workspace-files';
|
||||||
import { readNxJson } from '../config/nx-json';
|
import { readNxJson } from '../config/nx-json';
|
||||||
import { unregisterPluginTSTranspiler } from '../utils/nx-plugin';
|
|
||||||
import {
|
import {
|
||||||
ConfigurationResult,
|
ConfigurationResult,
|
||||||
ConfigurationSourceMaps,
|
ConfigurationSourceMaps,
|
||||||
|
} from './utils/project-configuration-utils';
|
||||||
|
import {
|
||||||
CreateNodesError,
|
CreateNodesError,
|
||||||
MergeNodesError,
|
MergeNodesError,
|
||||||
ProjectConfigurationsError,
|
ProjectConfigurationsError,
|
||||||
} from './utils/project-configuration-utils';
|
} from './error-types';
|
||||||
import { DaemonProjectGraphError } from '../daemon/daemon-project-graph-error';
|
import { DaemonProjectGraphError } from '../daemon/daemon-project-graph-error';
|
||||||
|
import { loadNxPlugins, LoadedNxPlugin } from './plugins/internal-api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously reads the latest cached copy of the workspace's ProjectGraph.
|
* Synchronously reads the latest cached copy of the workspace's ProjectGraph.
|
||||||
@ -95,15 +97,16 @@ export function readProjectsConfigurationFromProjectGraph(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function buildProjectGraphAndSourceMapsWithoutDaemon() {
|
export async function buildProjectGraphAndSourceMapsWithoutDaemon() {
|
||||||
// Set this globally to allow plugins to know if they are being called from the project graph creation
|
|
||||||
global.NX_GRAPH_CREATION = true;
|
global.NX_GRAPH_CREATION = true;
|
||||||
const nxJson = readNxJson();
|
const nxJson = readNxJson();
|
||||||
|
|
||||||
performance.mark('retrieve-project-configurations:start');
|
performance.mark('retrieve-project-configurations:start');
|
||||||
let configurationResult: ConfigurationResult;
|
let configurationResult: ConfigurationResult;
|
||||||
let projectConfigurationsError: ProjectConfigurationsError;
|
let projectConfigurationsError: ProjectConfigurationsError;
|
||||||
|
const [plugins, cleanup] = await loadNxPlugins(nxJson.plugins);
|
||||||
try {
|
try {
|
||||||
configurationResult = await retrieveProjectConfigurations(
|
configurationResult = await retrieveProjectConfigurations(
|
||||||
|
plugins,
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
nxJson
|
nxJson
|
||||||
);
|
);
|
||||||
@ -137,7 +140,8 @@ export async function buildProjectGraphAndSourceMapsWithoutDaemon() {
|
|||||||
fileMap,
|
fileMap,
|
||||||
allWorkspaceFiles,
|
allWorkspaceFiles,
|
||||||
rustReferences,
|
rustReferences,
|
||||||
cacheEnabled ? readFileMapCache() : null
|
cacheEnabled ? readFileMapCache() : null,
|
||||||
|
plugins
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CreateDependenciesError) {
|
if (e instanceof CreateDependenciesError) {
|
||||||
@ -149,12 +153,13 @@ export async function buildProjectGraphAndSourceMapsWithoutDaemon() {
|
|||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { projectGraph, projectFileMapCache } = projectGraphResult;
|
const { projectGraph, projectFileMapCache } = projectGraphResult;
|
||||||
performance.mark('build-project-graph-using-project-file-map:end');
|
performance.mark('build-project-graph-using-project-file-map:end');
|
||||||
|
|
||||||
unregisterPluginTSTranspiler();
|
|
||||||
delete global.NX_GRAPH_CREATION;
|
delete global.NX_GRAPH_CREATION;
|
||||||
|
|
||||||
const errors = [
|
const errors = [
|
||||||
|
|||||||
@ -5,9 +5,8 @@ import {
|
|||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
} from '../../config/workspace-json-project-json';
|
} from '../../config/workspace-json-project-json';
|
||||||
import { findMatchingProjects } from '../../utils/find-matching-projects';
|
import { findMatchingProjects } from '../../utils/find-matching-projects';
|
||||||
import { NX_PREFIX } from '../../utils/logger';
|
|
||||||
import { resolveNxTokensInOptions } from '../utils/project-configuration-utils';
|
import { resolveNxTokensInOptions } from '../utils/project-configuration-utils';
|
||||||
import { CreateDependenciesContext } from '../../utils/nx-plugin';
|
import { CreateDependenciesContext } from '../plugins';
|
||||||
|
|
||||||
export async function normalizeProjectNodes(
|
export async function normalizeProjectNodes(
|
||||||
ctx: CreateDependenciesContext,
|
ctx: CreateDependenciesContext,
|
||||||
|
|||||||
@ -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 {
|
import {
|
||||||
ProjectConfiguration,
|
ProjectConfiguration,
|
||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
|
|||||||
@ -7,17 +7,26 @@ import {
|
|||||||
TargetMetadata,
|
TargetMetadata,
|
||||||
} from '../../config/workspace-json-project-json';
|
} from '../../config/workspace-json-project-json';
|
||||||
import { NX_PREFIX } from '../../utils/logger';
|
import { NX_PREFIX } from '../../utils/logger';
|
||||||
import { CreateNodesResult, LoadedNxPlugin } from '../../utils/nx-plugin';
|
|
||||||
import { readJsonFile } from '../../utils/fileutils';
|
import { readJsonFile } from '../../utils/fileutils';
|
||||||
import { workspaceRoot } from '../../utils/workspace-root';
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
import {
|
import {
|
||||||
ONLY_MODIFIES_EXISTING_TARGET,
|
ONLY_MODIFIES_EXISTING_TARGET,
|
||||||
OVERRIDE_SOURCE_FILE,
|
OVERRIDE_SOURCE_FILE,
|
||||||
} from '../../plugins/target-defaults/target-defaults-plugin';
|
} from '../../plugins/target-defaults/symbols';
|
||||||
|
|
||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
|
import {
|
||||||
|
CreateNodesResultWithContext,
|
||||||
|
LoadedNxPlugin,
|
||||||
|
} from '../plugins/internal-api';
|
||||||
|
import {
|
||||||
|
CreateNodesError,
|
||||||
|
MergeNodesError,
|
||||||
|
ProjectConfigurationsError,
|
||||||
|
isAggregateCreateNodesError,
|
||||||
|
} from '../error-types';
|
||||||
|
|
||||||
export type SourceInformation = [file: string, plugin: string];
|
export type SourceInformation = [file: string, plugin: string];
|
||||||
export type ConfigurationSourceMaps = Record<
|
export type ConfigurationSourceMaps = Record<
|
||||||
@ -294,10 +303,6 @@ export type ConfigurationResult = {
|
|||||||
projectRootMap: Record<string, string>;
|
projectRootMap: Record<string, string>;
|
||||||
sourceMaps: ConfigurationSourceMaps;
|
sourceMaps: ConfigurationSourceMaps;
|
||||||
};
|
};
|
||||||
type CreateNodesResultWithContext = CreateNodesResult & {
|
|
||||||
file: string;
|
|
||||||
pluginName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms a list of project paths into a map of project configurations.
|
* Transforms a list of project paths into a map of project configurations.
|
||||||
@ -307,10 +312,10 @@ type CreateNodesResultWithContext = CreateNodesResult & {
|
|||||||
* @param workspaceFiles A list of non-ignored workspace files
|
* @param workspaceFiles A list of non-ignored workspace files
|
||||||
* @param plugins The plugins that should be used to infer project configuration
|
* @param plugins The plugins that should be used to infer project configuration
|
||||||
*/
|
*/
|
||||||
export function createProjectConfigurations(
|
export async function createProjectConfigurations(
|
||||||
root: string = workspaceRoot,
|
root: string = workspaceRoot,
|
||||||
nxJson: NxJsonConfiguration,
|
nxJson: NxJsonConfiguration,
|
||||||
workspaceFiles: string[], // making this parameter allows devkit to pick up newly created projects
|
projectFiles: string[], // making this parameter allows devkit to pick up newly created projects
|
||||||
plugins: LoadedNxPlugin[]
|
plugins: LoadedNxPlugin[]
|
||||||
): Promise<ConfigurationResult> {
|
): Promise<ConfigurationResult> {
|
||||||
performance.mark('build-project-configs:start');
|
performance.mark('build-project-configs:start');
|
||||||
@ -319,20 +324,21 @@ export function createProjectConfigurations(
|
|||||||
const errors: Array<CreateNodesError | MergeNodesError> = [];
|
const errors: Array<CreateNodesError | MergeNodesError> = [];
|
||||||
|
|
||||||
// We iterate over plugins first - this ensures that plugins specified first take precedence.
|
// We iterate over plugins first - this ensures that plugins specified first take precedence.
|
||||||
for (const { plugin, options, include, exclude } of plugins) {
|
for (const {
|
||||||
const [pattern, createNodes] = plugin.createNodes ?? [];
|
name: pluginName,
|
||||||
const pluginResults: Array<
|
createNodes: createNodesTuple,
|
||||||
CreateNodesResultWithContext | Promise<CreateNodesResultWithContext>
|
include,
|
||||||
> = [];
|
exclude,
|
||||||
|
} of plugins) {
|
||||||
|
const [pattern, createNodes] = createNodesTuple ?? [];
|
||||||
|
|
||||||
performance.mark(`${plugin.name}:createNodes - start`);
|
|
||||||
if (!pattern) {
|
if (!pattern) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchingConfigFiles: string[] = [];
|
const matchingConfigFiles: string[] = [];
|
||||||
|
|
||||||
for (const file of workspaceFiles) {
|
for (const file of projectFiles) {
|
||||||
if (minimatch(file, pattern, { dot: true })) {
|
if (minimatch(file, pattern, { dot: true })) {
|
||||||
if (include) {
|
if (include) {
|
||||||
const included = include.some((includedPattern) =>
|
const included = include.some((includedPattern) =>
|
||||||
@ -355,76 +361,20 @@ export function createProjectConfigurations(
|
|||||||
matchingConfigFiles.push(file);
|
matchingConfigFiles.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const file of matchingConfigFiles) {
|
let r = createNodes(matchingConfigFiles, {
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - start`);
|
nxJsonConfiguration: nxJson,
|
||||||
try {
|
workspaceRoot: root,
|
||||||
let r = createNodes(file, options, {
|
configFiles: matchingConfigFiles,
|
||||||
nxJsonConfiguration: nxJson,
|
}).catch((e) => {
|
||||||
workspaceRoot: root,
|
if (isAggregateCreateNodesError(e)) {
|
||||||
configFiles: matchingConfigFiles,
|
errors.push(...e.errors);
|
||||||
});
|
return e.partialResults;
|
||||||
|
} else {
|
||||||
if (r instanceof Promise) {
|
throw e;
|
||||||
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 };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
|
||||||
performance.measure(
|
|
||||||
`${plugin.name}:createNodes:${file}`,
|
|
||||||
`${plugin.name}:createNodes:${file} - start`,
|
|
||||||
`${plugin.name}:createNodes:${file} - end`
|
|
||||||
);
|
|
||||||
pluginResults.push({
|
|
||||||
...r,
|
|
||||||
file,
|
|
||||||
pluginName: plugin.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
errors.push(
|
|
||||||
new CreateNodesError({
|
|
||||||
file,
|
|
||||||
pluginName: plugin.name,
|
|
||||||
error,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
results.push(
|
results.push(r);
|
||||||
Promise.all(pluginResults).then((results) => {
|
|
||||||
performance.mark(`${plugin.name}:createNodes - end`);
|
|
||||||
performance.measure(
|
|
||||||
`${plugin.name}:createNodes`,
|
|
||||||
`${plugin.name}:createNodes - start`,
|
|
||||||
`${plugin.name}:createNodes - end`
|
|
||||||
);
|
|
||||||
return results;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(results).then((results) => {
|
return Promise.all(results).then((results) => {
|
||||||
@ -557,62 +507,6 @@ export function readProjectConfigurationsFromRootMap(
|
|||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProjectConfigurationsError extends Error {
|
|
||||||
constructor(
|
|
||||||
public readonly errors: Array<MergeNodesError | CreateNodesError>,
|
|
||||||
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.
|
* Merges two targets.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { getDefaultPlugins } from '../../utils/nx-plugin';
|
|
||||||
import { TempFs } from '../../internal-testing-utils/temp-fs';
|
import { TempFs } from '../../internal-testing-utils/temp-fs';
|
||||||
import { retrieveProjectConfigurationPaths } from './retrieve-workspace-files';
|
import { retrieveProjectConfigurationPaths } from './retrieve-workspace-files';
|
||||||
|
|
||||||
@ -26,10 +25,18 @@ describe('retrieveProjectConfigurationPaths', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const configPaths = await retrieveProjectConfigurationPaths(
|
const configPaths = retrieveProjectConfigurationPaths(fs.tempDir, [
|
||||||
fs.tempDir,
|
{
|
||||||
await getDefaultPlugins(fs.tempDir)
|
createNodes: [
|
||||||
);
|
'{project.json,**/project.json}',
|
||||||
|
() => {
|
||||||
|
return {
|
||||||
|
projects: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
expect(configPaths).not.toContain('not-projects/project.json');
|
expect(configPaths).not.toContain('not-projects/project.json');
|
||||||
expect(configPaths).toContain('projects/project.json');
|
expect(configPaths).toContain('projects/project.json');
|
||||||
|
|||||||
@ -1,28 +1,22 @@
|
|||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { getNxRequirePaths } from '../../utils/installation-directory';
|
|
||||||
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||||
import {
|
import {
|
||||||
NX_ANGULAR_JSON_PLUGIN_NAME,
|
NX_ANGULAR_JSON_PLUGIN_NAME,
|
||||||
NxAngularJsonPlugin,
|
|
||||||
shouldMergeAngularProjects,
|
shouldMergeAngularProjects,
|
||||||
} from '../../adapter/angular-json';
|
} from '../../adapter/angular-json';
|
||||||
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
|
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
|
||||||
import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces';
|
|
||||||
import {
|
import {
|
||||||
createProjectConfigurations,
|
createProjectConfigurations,
|
||||||
ConfigurationResult,
|
ConfigurationResult,
|
||||||
} from './project-configuration-utils';
|
} from './project-configuration-utils';
|
||||||
import {
|
import { LoadedNxPlugin, loadNxPlugins } from '../plugins/internal-api';
|
||||||
getDefaultPlugins,
|
|
||||||
LoadedNxPlugin,
|
|
||||||
loadNxPlugins,
|
|
||||||
} from '../../utils/nx-plugin';
|
|
||||||
import { ProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json';
|
|
||||||
import {
|
import {
|
||||||
getNxWorkspaceFilesFromContext,
|
getNxWorkspaceFilesFromContext,
|
||||||
globWithWorkspaceContext,
|
globWithWorkspaceContext,
|
||||||
} from '../../utils/workspace-context';
|
} from '../../utils/workspace-context';
|
||||||
import { buildAllWorkspaceFiles } from './build-all-workspace-files';
|
import { buildAllWorkspaceFiles } from './build-all-workspace-files';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { NxPlugin } from '../plugins';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Walks the workspace directory to create the `projectFileMap`, `ProjectConfigurations` and `allWorkspaceFiles`
|
* Walks the workspace directory to create the `projectFileMap`, `ProjectConfigurations` and `allWorkspaceFiles`
|
||||||
@ -65,41 +59,45 @@ export async function retrieveWorkspaceFiles(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Walk through the workspace and return `ProjectConfigurations`. Only use this if the projectFileMap is not needed.
|
* Walk through the workspace and return `ProjectConfigurations`. Only use this if the projectFileMap is not needed.
|
||||||
*
|
|
||||||
* @param workspaceRoot
|
|
||||||
* @param nxJson
|
|
||||||
*/
|
*/
|
||||||
export async function retrieveProjectConfigurations(
|
export async function retrieveProjectConfigurations(
|
||||||
|
plugins: LoadedNxPlugin[],
|
||||||
workspaceRoot: string,
|
workspaceRoot: string,
|
||||||
nxJson: NxJsonConfiguration
|
nxJson: NxJsonConfiguration
|
||||||
): Promise<ConfigurationResult> {
|
): Promise<ConfigurationResult> {
|
||||||
const plugins = await loadNxPlugins(
|
const projects = await _retrieveProjectConfigurations(
|
||||||
nxJson?.plugins ?? [],
|
workspaceRoot,
|
||||||
getNxRequirePaths(workspaceRoot),
|
nxJson,
|
||||||
workspaceRoot
|
plugins
|
||||||
);
|
);
|
||||||
|
return projects;
|
||||||
return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function retrieveProjectConfigurationsWithAngularProjects(
|
export async function retrieveProjectConfigurationsWithAngularProjects(
|
||||||
workspaceRoot: string,
|
workspaceRoot: string,
|
||||||
nxJson: NxJsonConfiguration
|
nxJson: NxJsonConfiguration
|
||||||
): Promise<ConfigurationResult> {
|
): Promise<ConfigurationResult> {
|
||||||
const plugins = await loadNxPlugins(
|
const pluginsToLoad = nxJson?.plugins ?? [];
|
||||||
nxJson?.plugins ?? [],
|
|
||||||
getNxRequirePaths(workspaceRoot),
|
|
||||||
workspaceRoot
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
shouldMergeAngularProjects(workspaceRoot, true) &&
|
shouldMergeAngularProjects(workspaceRoot, true) &&
|
||||||
!plugins.some((p) => p.plugin.name === NX_ANGULAR_JSON_PLUGIN_NAME)
|
!pluginsToLoad.some(
|
||||||
|
(p) =>
|
||||||
|
p === NX_ANGULAR_JSON_PLUGIN_NAME ||
|
||||||
|
(typeof p === 'object' && p.plugin === NX_ANGULAR_JSON_PLUGIN_NAME)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
plugins.push({ plugin: NxAngularJsonPlugin });
|
pluginsToLoad.push(join(__dirname, '../../adapter/angular-json'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins);
|
const [plugins, cleanup] = await loadNxPlugins(
|
||||||
|
nxJson?.plugins ?? [],
|
||||||
|
workspaceRoot
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins);
|
||||||
|
cleanup();
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _retrieveProjectConfigurations(
|
function _retrieveProjectConfigurations(
|
||||||
@ -120,7 +118,7 @@ function _retrieveProjectConfigurations(
|
|||||||
|
|
||||||
export function retrieveProjectConfigurationPaths(
|
export function retrieveProjectConfigurationPaths(
|
||||||
root: string,
|
root: string,
|
||||||
plugins: LoadedNxPlugin[]
|
plugins: Array<{ createNodes?: readonly [string, ...unknown[]] } & unknown>
|
||||||
): string[] {
|
): string[] {
|
||||||
const projectGlobPatterns = configurationGlobs(plugins);
|
const projectGlobPatterns = configurationGlobs(plugins);
|
||||||
return globWithWorkspaceContext(root, projectGlobPatterns);
|
return globWithWorkspaceContext(root, projectGlobPatterns);
|
||||||
@ -136,7 +134,7 @@ export async function retrieveProjectConfigurationsWithoutPluginInference(
|
|||||||
root: string
|
root: string
|
||||||
): Promise<Record<string, ProjectConfiguration>> {
|
): Promise<Record<string, ProjectConfiguration>> {
|
||||||
const nxJson = readNxJson(root);
|
const nxJson = readNxJson(root);
|
||||||
const plugins = await getDefaultPlugins(root);
|
const [plugins, cleanup] = await loadNxPlugins([]); // only load default plugins
|
||||||
const projectGlobPatterns = retrieveProjectConfigurationPaths(root, plugins);
|
const projectGlobPatterns = retrieveProjectConfigurationPaths(root, plugins);
|
||||||
const cacheKey = root + ',' + projectGlobPatterns.join(',');
|
const cacheKey = root + ',' + projectGlobPatterns.join(',');
|
||||||
|
|
||||||
@ -150,21 +148,22 @@ export async function retrieveProjectConfigurationsWithoutPluginInference(
|
|||||||
root,
|
root,
|
||||||
nxJson,
|
nxJson,
|
||||||
projectFiles,
|
projectFiles,
|
||||||
[
|
plugins
|
||||||
{ plugin: getNxPackageJsonWorkspacesPlugin(root) },
|
|
||||||
{ plugin: ProjectJsonProjectsPlugin },
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
projectsWithoutPluginCache.set(cacheKey, projects);
|
projectsWithoutPluginCache.set(cacheKey, projects);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
|
||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function configurationGlobs(plugins: LoadedNxPlugin[]): string[] {
|
export function configurationGlobs(
|
||||||
|
plugins: Array<{ createNodes?: readonly [string, ...unknown[]] }>
|
||||||
|
): string[] {
|
||||||
const globPatterns = [];
|
const globPatterns = [];
|
||||||
for (const { plugin } of plugins) {
|
for (const plugin of plugins) {
|
||||||
if (plugin.createNodes) {
|
if ('createNodes' in plugin && plugin.createNodes) {
|
||||||
globPatterns.push(plugin.createNodes[0]);
|
globPatterns.push(plugin.createNodes[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,11 @@ export const logger = {
|
|||||||
fatal: (...s) => {
|
fatal: (...s) => {
|
||||||
console.error(...s);
|
console.error(...s);
|
||||||
},
|
},
|
||||||
|
verbose: (...s) => {
|
||||||
|
if (process.env.NX_VERBOSE_LOGGING) {
|
||||||
|
console.log(...s);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function stripIndent(str: string): string {
|
export function stripIndent(str: string): string {
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { shouldMergeAngularProjects } from '../adapter/angular-json';
|
import { shouldMergeAngularProjects } from '../adapter/angular-json';
|
||||||
import { ProjectGraphProcessor } from '../config/project-graph';
|
import { ProjectGraphProcessor } from '../config/project-graph';
|
||||||
import { TargetConfiguration } from '../config/workspace-json-project-json';
|
import { TargetConfiguration } from '../config/workspace-json-project-json';
|
||||||
import { ProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json';
|
import ProjectJsonProjectsPlugin from '../plugins/project-json/build-nodes/project-json';
|
||||||
import { TargetDefaultsPlugin } from '../plugins/target-defaults/target-defaults-plugin';
|
import TargetDefaultsPlugin from '../plugins/target-defaults/target-defaults-plugin';
|
||||||
import { getNxPackageJsonWorkspacesPlugin } from '../plugins/package-json-workspaces';
|
import * as PackageJsonWorkspacesPlugin from '../plugins/package-json-workspaces';
|
||||||
import { LoadedNxPlugin, NxPluginV2 } from './nx-plugin';
|
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
|
* @deprecated Add targets to the projects in a {@link CreateNodes} function instead. This will be removed in Nx 19
|
||||||
@ -39,18 +40,16 @@ export type NxPluginV1 = {
|
|||||||
/**
|
/**
|
||||||
* @todo(@agentender) v19: Remove this fn when we remove readWorkspaceConfig
|
* @todo(@agentender) v19: Remove this fn when we remove readWorkspaceConfig
|
||||||
*/
|
*/
|
||||||
export function getDefaultPluginsSync(root: string): LoadedNxPlugin[] {
|
export function getDefaultPluginsSync(root: string): NxPluginV2[] {
|
||||||
const plugins: NxPluginV2[] = [
|
const plugins: NxPluginV2[] = [
|
||||||
require('../plugins/js'),
|
require('../plugins/js'),
|
||||||
...(shouldMergeAngularProjects(root, false)
|
...(shouldMergeAngularProjects(root, false)
|
||||||
? [require('../adapter/angular-json').NxAngularJsonPlugin]
|
? [require('../adapter/angular-json').NxAngularJsonPlugin]
|
||||||
: []),
|
: []),
|
||||||
TargetDefaultsPlugin,
|
TargetDefaultsPlugin,
|
||||||
getNxPackageJsonWorkspacesPlugin(root),
|
PackageJsonWorkspacesPlugin,
|
||||||
ProjectJsonProjectsPlugin,
|
ProjectJsonProjectsPlugin,
|
||||||
];
|
];
|
||||||
|
|
||||||
return plugins.map((p) => ({
|
return plugins;
|
||||||
plugin: p,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,556 +0,0 @@
|
|||||||
import { existsSync } from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import {
|
|
||||||
FileMap,
|
|
||||||
ProjectGraph,
|
|
||||||
ProjectGraphExternalNode,
|
|
||||||
} from '../config/project-graph';
|
|
||||||
import { toProjectName } from '../config/workspaces';
|
|
||||||
|
|
||||||
import { workspaceRoot } from './workspace-root';
|
|
||||||
import { readJsonFile } from '../utils/fileutils';
|
|
||||||
import {
|
|
||||||
PackageJson,
|
|
||||||
readModulePackageJsonWithoutFallbacks,
|
|
||||||
} from './package-json';
|
|
||||||
import {
|
|
||||||
registerTranspiler,
|
|
||||||
registerTsConfigPaths,
|
|
||||||
} from '../plugins/js/utils/register';
|
|
||||||
import { ProjectConfiguration } from '../config/workspace-json-project-json';
|
|
||||||
import { logger } from './logger';
|
|
||||||
import {
|
|
||||||
createProjectRootMappingsFromProjectConfigurations,
|
|
||||||
findProjectForPath,
|
|
||||||
} from '../project-graph/utils/find-project-for-path';
|
|
||||||
import { normalizePath } from './path';
|
|
||||||
import { dirname, join } from 'path';
|
|
||||||
import { getNxRequirePaths } from './installation-directory';
|
|
||||||
import { readTsConfig } from '../plugins/js/utils/typescript';
|
|
||||||
import {
|
|
||||||
NxJsonConfiguration,
|
|
||||||
PluginConfiguration,
|
|
||||||
readNxJson,
|
|
||||||
} from '../config/nx-json';
|
|
||||||
|
|
||||||
import type * as ts from 'typescript';
|
|
||||||
import { NxPluginV1 } from './nx-plugin.deprecated';
|
|
||||||
import { RawProjectGraphDependency } from '../project-graph/project-graph-builder';
|
|
||||||
import { combineGlobPatterns } from './globs';
|
|
||||||
import { shouldMergeAngularProjects } from '../adapter/angular-json';
|
|
||||||
import { getNxPackageJsonWorkspacesPlugin } from '../plugins/package-json-workspaces';
|
|
||||||
import { ProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json';
|
|
||||||
import { PackageJsonProjectsNextToProjectJsonPlugin } from '../plugins/project-json/build-nodes/package-json-next-to-project-json';
|
|
||||||
import { retrieveProjectConfigurationsWithoutPluginInference } from '../project-graph/utils/retrieve-workspace-files';
|
|
||||||
import { TargetDefaultsPlugin } from '../plugins/target-defaults/target-defaults-plugin';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context for {@link CreateNodesFunction}
|
|
||||||
*/
|
|
||||||
export interface CreateNodesContext {
|
|
||||||
readonly nxJsonConfiguration: NxJsonConfiguration;
|
|
||||||
readonly workspaceRoot: string;
|
|
||||||
/**
|
|
||||||
* The subset of configuration files which match the createNodes pattern
|
|
||||||
*/
|
|
||||||
readonly configFiles: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function which parses a configuration file into a set of nodes.
|
|
||||||
* Used for creating nodes for the {@link ProjectGraph}
|
|
||||||
*/
|
|
||||||
export type CreateNodesFunction<T = unknown> = (
|
|
||||||
projectConfigurationFile: string,
|
|
||||||
options: T | undefined,
|
|
||||||
context: CreateNodesContext
|
|
||||||
) => CreateNodesResult | Promise<CreateNodesResult>;
|
|
||||||
|
|
||||||
export interface CreateNodesResult {
|
|
||||||
/**
|
|
||||||
* A map of project root -> project configuration
|
|
||||||
*/
|
|
||||||
projects?: Record<string, Optional<ProjectConfiguration, 'root'>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of external node name -> external node. External nodes do not have a root, so the key is their name.
|
|
||||||
*/
|
|
||||||
externalNodes?: Record<string, ProjectGraphExternalNode>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A pair of file patterns and {@link CreateNodesFunction}
|
|
||||||
*/
|
|
||||||
export type CreateNodes<T = unknown> = readonly [
|
|
||||||
configFilePattern: string,
|
|
||||||
createNodesFunction: CreateNodesFunction<T>
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context for {@link CreateDependencies}
|
|
||||||
*/
|
|
||||||
export interface CreateDependenciesContext {
|
|
||||||
/**
|
|
||||||
* The external nodes that have been added to the graph.
|
|
||||||
*/
|
|
||||||
readonly externalNodes: ProjectGraph['externalNodes'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration of each project in the workspace.
|
|
||||||
*/
|
|
||||||
readonly projects: Record<string, ProjectConfiguration>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `nx.json` configuration from the workspace
|
|
||||||
*/
|
|
||||||
readonly nxJsonConfiguration: NxJsonConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All files in the workspace
|
|
||||||
*/
|
|
||||||
readonly fileMap: FileMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Files changes since last invocation
|
|
||||||
*/
|
|
||||||
readonly filesToProcess: FileMap;
|
|
||||||
|
|
||||||
readonly workspaceRoot: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function which parses files in the workspace to create dependencies in the {@link ProjectGraph}
|
|
||||||
* Use {@link validateDependency} to validate dependencies
|
|
||||||
*/
|
|
||||||
export type CreateDependencies<T = unknown> = (
|
|
||||||
options: T | undefined,
|
|
||||||
context: CreateDependenciesContext
|
|
||||||
) => RawProjectGraphDependency[] | Promise<RawProjectGraphDependency[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A plugin for Nx which creates nodes and dependencies for the {@link ProjectGraph}
|
|
||||||
*/
|
|
||||||
export type NxPluginV2<TOptions = unknown> = {
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a file pattern and function that retrieves configuration info from
|
|
||||||
* those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile }
|
|
||||||
*/
|
|
||||||
createNodes?: CreateNodes;
|
|
||||||
|
|
||||||
// Todo(@AgentEnder): This shouldn't be a full processor, since its only responsible for defining edges between projects. What do we want the API to be?
|
|
||||||
/**
|
|
||||||
* Provides a function to analyze files to create dependencies for the {@link ProjectGraph}
|
|
||||||
*/
|
|
||||||
createDependencies?: CreateDependencies<TOptions>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export * from './nx-plugin.deprecated';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A plugin for Nx
|
|
||||||
*/
|
|
||||||
export type NxPlugin = NxPluginV1 | NxPluginV2;
|
|
||||||
|
|
||||||
export type LoadedNxPlugin = {
|
|
||||||
plugin: NxPluginV2 & Pick<NxPluginV1, 'processProjectGraph'>;
|
|
||||||
options?: unknown;
|
|
||||||
include?: string[];
|
|
||||||
exclude?: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Short lived cache (cleared between cmd runs)
|
|
||||||
// holding resolved nx plugin objects.
|
|
||||||
// Allows loadNxPlugins to be called multiple times w/o
|
|
||||||
// executing resolution mulitple times.
|
|
||||||
export const nxPluginCache: Map<string, LoadedNxPlugin['plugin']> = new Map();
|
|
||||||
|
|
||||||
export function getPluginPathAndName(
|
|
||||||
moduleName: string,
|
|
||||||
paths: string[],
|
|
||||||
projects: Record<string, ProjectConfiguration>,
|
|
||||||
root: string
|
|
||||||
) {
|
|
||||||
let pluginPath: string;
|
|
||||||
try {
|
|
||||||
pluginPath = require.resolve(moduleName, {
|
|
||||||
paths,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === 'MODULE_NOT_FOUND') {
|
|
||||||
const plugin = resolveLocalNxPlugin(
|
|
||||||
moduleName,
|
|
||||||
readNxJson(root),
|
|
||||||
projects,
|
|
||||||
root
|
|
||||||
);
|
|
||||||
if (plugin) {
|
|
||||||
const main = readPluginMainFromProjectConfiguration(
|
|
||||||
plugin.projectConfig
|
|
||||||
);
|
|
||||||
pluginPath = main ? path.join(root, main) : plugin.path;
|
|
||||||
} else {
|
|
||||||
logger.error(`Plugin listed in \`nx.json\` not found: ${moduleName}`);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const packageJsonPath = path.join(pluginPath, 'package.json');
|
|
||||||
|
|
||||||
const extension = path.extname(pluginPath);
|
|
||||||
|
|
||||||
// Register the ts-transpiler if we are pointing to a
|
|
||||||
// plain ts file that's not part of a plugin project
|
|
||||||
if (extension === '.ts' && !tsNodeAndPathsUnregisterCallback) {
|
|
||||||
registerPluginTSTranspiler();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name } =
|
|
||||||
!['.ts', '.js'].some((x) => x === extension) && // Not trying to point to a ts or js file
|
|
||||||
existsSync(packageJsonPath) // plugin has a package.json
|
|
||||||
? readJsonFile(packageJsonPath) // read name from package.json
|
|
||||||
: { name: moduleName };
|
|
||||||
return { pluginPath, name };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadNxPluginAsync(
|
|
||||||
pluginConfiguration: PluginConfiguration,
|
|
||||||
paths: string[],
|
|
||||||
projects: Record<string, ProjectConfiguration>,
|
|
||||||
root: string
|
|
||||||
): Promise<LoadedNxPlugin> {
|
|
||||||
const { plugin: moduleName, options } =
|
|
||||||
typeof pluginConfiguration === 'object'
|
|
||||||
? pluginConfiguration
|
|
||||||
: { plugin: pluginConfiguration, options: undefined };
|
|
||||||
let pluginModule = nxPluginCache.get(moduleName);
|
|
||||||
|
|
||||||
const include =
|
|
||||||
typeof pluginConfiguration === 'object'
|
|
||||||
? pluginConfiguration.include
|
|
||||||
: undefined;
|
|
||||||
const exclude =
|
|
||||||
typeof pluginConfiguration === 'object'
|
|
||||||
? pluginConfiguration.exclude
|
|
||||||
: undefined;
|
|
||||||
if (pluginModule) {
|
|
||||||
return {
|
|
||||||
plugin: pluginModule,
|
|
||||||
options,
|
|
||||||
include,
|
|
||||||
exclude,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
performance.mark(`Load Nx Plugin: ${moduleName} - start`);
|
|
||||||
let { pluginPath, name } = await getPluginPathAndName(
|
|
||||||
moduleName,
|
|
||||||
paths,
|
|
||||||
projects,
|
|
||||||
root
|
|
||||||
);
|
|
||||||
const plugin = ensurePluginIsV2(
|
|
||||||
(await import(pluginPath)) as LoadedNxPlugin['plugin']
|
|
||||||
);
|
|
||||||
plugin.name ??= name;
|
|
||||||
nxPluginCache.set(moduleName, plugin);
|
|
||||||
performance.mark(`Load Nx Plugin: ${moduleName} - end`);
|
|
||||||
performance.measure(
|
|
||||||
`Load Nx Plugin: ${moduleName}`,
|
|
||||||
`Load Nx Plugin: ${moduleName} - start`,
|
|
||||||
`Load Nx Plugin: ${moduleName} - end`
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
plugin,
|
|
||||||
options,
|
|
||||||
include,
|
|
||||||
exclude,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadNxPlugins(
|
|
||||||
plugins: PluginConfiguration[],
|
|
||||||
paths = getNxRequirePaths(),
|
|
||||||
root = workspaceRoot,
|
|
||||||
projects?: Record<string, ProjectConfiguration>
|
|
||||||
): Promise<LoadedNxPlugin[]> {
|
|
||||||
const result: LoadedNxPlugin[] = [
|
|
||||||
{ plugin: PackageJsonProjectsNextToProjectJsonPlugin },
|
|
||||||
];
|
|
||||||
|
|
||||||
plugins ??= [];
|
|
||||||
|
|
||||||
// When loading plugins for `createNodes`, we don't know what projects exist yet.
|
|
||||||
// Try resolving plugins
|
|
||||||
for (const plugin of plugins) {
|
|
||||||
try {
|
|
||||||
require.resolve(typeof plugin === 'string' ? plugin : plugin.plugin);
|
|
||||||
} catch {
|
|
||||||
// If a plugin cannot be resolved, we will need projects to resolve it
|
|
||||||
projects ??= await retrieveProjectConfigurationsWithoutPluginInference(
|
|
||||||
root
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const plugin of plugins) {
|
|
||||||
result.push(await loadNxPluginAsync(plugin, paths, projects, root));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We push the nx core node plugins onto the end, s.t. it overwrites any other plugins
|
|
||||||
result.push(...(await getDefaultPlugins(root)));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 {
|
|
||||||
if (isNxPluginV2(plugin)) {
|
|
||||||
return plugin;
|
|
||||||
}
|
|
||||||
if (isNxPluginV1(plugin) && plugin.projectFilePatterns) {
|
|
||||||
return {
|
|
||||||
...plugin,
|
|
||||||
createNodes: [
|
|
||||||
`*/**/${combineGlobPatterns(plugin.projectFilePatterns)}`,
|
|
||||||
(configFilePath) => {
|
|
||||||
const root = dirname(configFilePath);
|
|
||||||
return {
|
|
||||||
projects: {
|
|
||||||
[root]: {
|
|
||||||
name: toProjectName(configFilePath),
|
|
||||||
root,
|
|
||||||
targets: plugin.registerProjectTargets?.(configFilePath),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNxPluginV2(plugin: NxPlugin): plugin is NxPluginV2 {
|
|
||||||
return 'createNodes' in plugin || 'createDependencies' in plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNxPluginV1(plugin: NxPlugin): plugin is NxPluginV1 {
|
|
||||||
return 'processProjectGraph' in plugin || 'projectFilePatterns' in plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readPluginPackageJson(
|
|
||||||
pluginName: string,
|
|
||||||
projects: Record<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 nxJson = readNxJson();
|
|
||||||
const localPluginPath = resolveLocalNxPlugin(
|
|
||||||
pluginName,
|
|
||||||
nxJson,
|
|
||||||
projects
|
|
||||||
);
|
|
||||||
if (localPluginPath) {
|
|
||||||
const localPluginPackageJson = path.join(
|
|
||||||
localPluginPath.path,
|
|
||||||
'package.json'
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
path: localPluginPackageJson,
|
|
||||||
json: readJsonFile(localPluginPackageJson),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a plugin package and returns the path to output
|
|
||||||
* @param importPath What is the import path that refers to a potential plugin?
|
|
||||||
* @returns The path to the built plugin, or null if it doesn't exist
|
|
||||||
*/
|
|
||||||
const localPluginCache: Record<
|
|
||||||
string,
|
|
||||||
{ path: string; projectConfig: ProjectConfiguration }
|
|
||||||
> = {};
|
|
||||||
|
|
||||||
export function resolveLocalNxPlugin(
|
|
||||||
importPath: string,
|
|
||||||
nxJsonConfiguration: NxJsonConfiguration,
|
|
||||||
projects: Record<string, ProjectConfiguration>,
|
|
||||||
root = workspaceRoot
|
|
||||||
): { path: string; projectConfig: ProjectConfiguration } | null {
|
|
||||||
localPluginCache[importPath] ??= lookupLocalPlugin(
|
|
||||||
importPath,
|
|
||||||
nxJsonConfiguration,
|
|
||||||
projects,
|
|
||||||
root
|
|
||||||
);
|
|
||||||
return localPluginCache[importPath];
|
|
||||||
}
|
|
||||||
|
|
||||||
let tsNodeAndPathsUnregisterCallback: (() => void) | undefined = undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register swc-node or ts-node if they are not currently registered
|
|
||||||
* with some default settings which work well for Nx plugins.
|
|
||||||
*/
|
|
||||||
export function registerPluginTSTranspiler() {
|
|
||||||
if (!tsNodeAndPathsUnregisterCallback) {
|
|
||||||
// nx-ignore-next-line
|
|
||||||
const ts: typeof import('typescript') = require('typescript');
|
|
||||||
|
|
||||||
// Get the first tsconfig that matches the allowed set
|
|
||||||
const tsConfigName = [
|
|
||||||
join(workspaceRoot, 'tsconfig.base.json'),
|
|
||||||
join(workspaceRoot, 'tsconfig.json'),
|
|
||||||
].find((x) => existsSync(x));
|
|
||||||
|
|
||||||
const tsConfig: Partial<ts.ParsedCommandLine> = tsConfigName
|
|
||||||
? readTsConfig(tsConfigName)
|
|
||||||
: {};
|
|
||||||
|
|
||||||
const unregisterTsConfigPaths = registerTsConfigPaths(tsConfigName);
|
|
||||||
const unregisterTranspiler = registerTranspiler({
|
|
||||||
experimentalDecorators: true,
|
|
||||||
emitDecoratorMetadata: true,
|
|
||||||
...tsConfig.options,
|
|
||||||
});
|
|
||||||
tsNodeAndPathsUnregisterCallback = () => {
|
|
||||||
unregisterTsConfigPaths();
|
|
||||||
unregisterTranspiler();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregister the ts-node transpiler if it is registered
|
|
||||||
*/
|
|
||||||
export function unregisterPluginTSTranspiler() {
|
|
||||||
if (tsNodeAndPathsUnregisterCallback) {
|
|
||||||
tsNodeAndPathsUnregisterCallback();
|
|
||||||
tsNodeAndPathsUnregisterCallback = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function lookupLocalPlugin(
|
|
||||||
importPath: string,
|
|
||||||
nxJsonConfiguration: NxJsonConfiguration,
|
|
||||||
projects: Record<string, ProjectConfiguration>,
|
|
||||||
root = workspaceRoot
|
|
||||||
) {
|
|
||||||
const plugin = findNxProjectForImportPath(importPath, projects, root);
|
|
||||||
if (!plugin) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tsNodeAndPathsUnregisterCallback) {
|
|
||||||
registerPluginTSTranspiler();
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectConfig: ProjectConfiguration = projects[plugin];
|
|
||||||
return { path: path.join(root, projectConfig.root), projectConfig };
|
|
||||||
}
|
|
||||||
|
|
||||||
function findNxProjectForImportPath(
|
|
||||||
importPath: string,
|
|
||||||
projects: Record<string, ProjectConfiguration>,
|
|
||||||
root = workspaceRoot
|
|
||||||
): string | null {
|
|
||||||
const tsConfigPaths: Record<string, string[]> = readTsConfigPaths(root);
|
|
||||||
const possiblePaths = tsConfigPaths[importPath]?.map((p) =>
|
|
||||||
normalizePath(path.relative(root, path.join(root, p)))
|
|
||||||
);
|
|
||||||
if (possiblePaths?.length) {
|
|
||||||
const projectRootMappings =
|
|
||||||
createProjectRootMappingsFromProjectConfigurations(projects);
|
|
||||||
for (const tsConfigPath of possiblePaths) {
|
|
||||||
const nxProject = findProjectForPath(tsConfigPath, projectRootMappings);
|
|
||||||
if (nxProject) {
|
|
||||||
return nxProject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (process.env.NX_VERBOSE_LOGGING) {
|
|
||||||
console.log(
|
|
||||||
'Unable to find local plugin',
|
|
||||||
possiblePaths,
|
|
||||||
projectRootMappings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
'Unable to resolve local plugin with import path ' + importPath
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tsconfigPaths: Record<string, string[]>;
|
|
||||||
|
|
||||||
function readTsConfigPaths(root: string = workspaceRoot) {
|
|
||||||
if (!tsconfigPaths) {
|
|
||||||
const tsconfigPath: string | null = ['tsconfig.base.json', 'tsconfig.json']
|
|
||||||
.map((x) => path.join(root, x))
|
|
||||||
.filter((x) => existsSync(x))[0];
|
|
||||||
if (!tsconfigPath) {
|
|
||||||
throw new Error('unable to find tsconfig.base.json or tsconfig.json');
|
|
||||||
}
|
|
||||||
const { compilerOptions } = readJsonFile(tsconfigPath);
|
|
||||||
tsconfigPaths = compilerOptions?.paths;
|
|
||||||
}
|
|
||||||
return tsconfigPaths ?? {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function readPluginMainFromProjectConfiguration(
|
|
||||||
plugin: ProjectConfiguration
|
|
||||||
): string | null {
|
|
||||||
const { main } =
|
|
||||||
Object.values(plugin.targets).find((x) =>
|
|
||||||
[
|
|
||||||
'@nx/js:tsc',
|
|
||||||
'@nrwl/js:tsc',
|
|
||||||
'@nx/js:swc',
|
|
||||||
'@nrwl/js:swc',
|
|
||||||
'@nx/node:package',
|
|
||||||
'@nrwl/node:package',
|
|
||||||
].includes(x.executor)
|
|
||||||
)?.options ||
|
|
||||||
plugin.targets?.build?.options ||
|
|
||||||
{};
|
|
||||||
return main;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDefaultPlugins(
|
|
||||||
root: string
|
|
||||||
): Promise<LoadedNxPlugin[]> {
|
|
||||||
const plugins: NxPluginV2[] = [
|
|
||||||
await import('../plugins/js'),
|
|
||||||
TargetDefaultsPlugin,
|
|
||||||
...(shouldMergeAngularProjects(root, false)
|
|
||||||
? [
|
|
||||||
await import('../adapter/angular-json').then(
|
|
||||||
(m) => m.NxAngularJsonPlugin
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
getNxPackageJsonWorkspacesPlugin(root),
|
|
||||||
ProjectJsonProjectsPlugin,
|
|
||||||
];
|
|
||||||
|
|
||||||
return plugins.map((p) => ({
|
|
||||||
plugin: p,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
||||||
@ -1,19 +1,18 @@
|
|||||||
import { workspaceRoot } from '../workspace-root';
|
|
||||||
import * as chalk from 'chalk';
|
import * as chalk from 'chalk';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { output } from '../output';
|
|
||||||
import type { PluginCapabilities } from './models';
|
|
||||||
import { hasElements } from './shared';
|
|
||||||
import { readJsonFile } from '../fileutils';
|
|
||||||
import { getPackageManagerCommand } from '../package-manager';
|
|
||||||
import {
|
|
||||||
loadNxPluginAsync,
|
|
||||||
NxPlugin,
|
|
||||||
readPluginPackageJson,
|
|
||||||
} from '../nx-plugin';
|
|
||||||
import { getNxRequirePaths } from '../installation-directory';
|
|
||||||
import { PackageJson } from '../package-json';
|
|
||||||
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||||
|
import { NxPlugin, readPluginPackageJson } from '../../project-graph/plugins';
|
||||||
|
import { loadNxPlugin } from '../../project-graph/plugins/loader';
|
||||||
|
import { readJsonFile } from '../fileutils';
|
||||||
|
import { getNxRequirePaths } from '../installation-directory';
|
||||||
|
import { output } from '../output';
|
||||||
|
import { PackageJson } from '../package-json';
|
||||||
|
import { getPackageManagerCommand } from '../package-manager';
|
||||||
|
import { workspaceRoot } from '../workspace-root';
|
||||||
|
import { hasElements } from './shared';
|
||||||
|
|
||||||
|
import type { PluginCapabilities } from './models';
|
||||||
|
|
||||||
function tryGetCollection<T extends object>(
|
function tryGetCollection<T extends object>(
|
||||||
packageJsonPath: string,
|
packageJsonPath: string,
|
||||||
@ -46,7 +45,7 @@ export async function getPluginCapabilities(
|
|||||||
getNxRequirePaths(workspaceRoot)
|
getNxRequirePaths(workspaceRoot)
|
||||||
);
|
);
|
||||||
const pluginModule = includeRuntimeCapabilities
|
const pluginModule = includeRuntimeCapabilities
|
||||||
? await tryGetModule(packageJson, workspaceRoot, projects)
|
? await tryGetModule(packageJson, workspaceRoot)
|
||||||
: ({} as Record<string, unknown>);
|
: ({} as Record<string, unknown>);
|
||||||
return {
|
return {
|
||||||
name: pluginName,
|
name: pluginName,
|
||||||
@ -99,26 +98,24 @@ export async function getPluginCapabilities(
|
|||||||
|
|
||||||
async function tryGetModule(
|
async function tryGetModule(
|
||||||
packageJson: PackageJson,
|
packageJson: PackageJson,
|
||||||
workspaceRoot: string,
|
workspaceRoot: string
|
||||||
projects: Record<string, ProjectConfiguration>
|
|
||||||
): Promise<NxPlugin | null> {
|
): Promise<NxPlugin | null> {
|
||||||
try {
|
try {
|
||||||
return packageJson.generators ??
|
if (
|
||||||
|
packageJson.generators ??
|
||||||
packageJson.executors ??
|
packageJson.executors ??
|
||||||
packageJson['nx-migrations'] ??
|
packageJson['nx-migrations'] ??
|
||||||
packageJson['schematics'] ??
|
packageJson['schematics'] ??
|
||||||
packageJson['builders']
|
packageJson['builders']
|
||||||
? (
|
) {
|
||||||
await loadNxPluginAsync(
|
const [pluginPromise] = loadNxPlugin(packageJson.name, workspaceRoot);
|
||||||
packageJson.name,
|
const plugin = await pluginPromise;
|
||||||
getNxRequirePaths(workspaceRoot),
|
return plugin;
|
||||||
projects,
|
} else {
|
||||||
workspaceRoot
|
return {
|
||||||
)
|
name: packageJson.name,
|
||||||
).plugin
|
};
|
||||||
: ({
|
}
|
||||||
name: packageJson.name,
|
|
||||||
} as NxPlugin);
|
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,7 @@ describe('normalizeRollupExecutorOptions', () => {
|
|||||||
);
|
);
|
||||||
expect(result.rollupConfig).toHaveLength(1);
|
expect(result.rollupConfig).toHaveLength(1);
|
||||||
expect(result.rollupConfig[0]).toMatch('react');
|
expect(result.rollupConfig[0]).toMatch('react');
|
||||||
|
// This fails if the nx repo has been cloned in `/root/...`
|
||||||
expect(result.rollupConfig[0]).not.toMatch(root);
|
expect(result.rollupConfig[0]).not.toMatch(root);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user