fix(core): ensure create nodes functions are properly parallelized (#23005)
This commit is contained in:
parent
c6fe9696fd
commit
0fd6d23e3f
@ -6,7 +6,7 @@ Context for [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
|
|||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
- [configFiles](../../devkit/documents/CreateNodesContext#configfiles): string[]
|
- [configFiles](../../devkit/documents/CreateNodesContext#configfiles): readonly string[]
|
||||||
- [nxJsonConfiguration](../../devkit/documents/CreateNodesContext#nxjsonconfiguration): NxJsonConfiguration<string[] | "\*">
|
- [nxJsonConfiguration](../../devkit/documents/CreateNodesContext#nxjsonconfiguration): NxJsonConfiguration<string[] | "\*">
|
||||||
- [workspaceRoot](../../devkit/documents/CreateNodesContext#workspaceroot): string
|
- [workspaceRoot](../../devkit/documents/CreateNodesContext#workspaceroot): string
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ Context for [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
|
|||||||
|
|
||||||
### configFiles
|
### configFiles
|
||||||
|
|
||||||
• `Readonly` **configFiles**: `string`[]
|
• `Readonly` **configFiles**: readonly `string`[]
|
||||||
|
|
||||||
The subset of configuration files which match the createNodes pattern
|
The subset of configuration files which match the createNodes pattern
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export interface CreateNodesContext {
|
|||||||
/**
|
/**
|
||||||
* The subset of configuration files which match the createNodes pattern
|
* The subset of configuration files which match the createNodes pattern
|
||||||
*/
|
*/
|
||||||
readonly configFiles: string[];
|
readonly configFiles: readonly string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
123
packages/nx/src/project-graph/plugins/utils.spec.ts
Normal file
123
packages/nx/src/project-graph/plugins/utils.spec.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import { runCreateNodesInParallel } from './utils';
|
||||||
|
|
||||||
|
const configFiles = ['file1', 'file2'] as const;
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
file: 'file1',
|
||||||
|
nxJsonConfiguration: {},
|
||||||
|
workspaceRoot: '',
|
||||||
|
configFiles,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
describe('createNodesInParallel', () => {
|
||||||
|
it('should return results with context', async () => {
|
||||||
|
const plugin = {
|
||||||
|
name: 'test',
|
||||||
|
createNodes: [
|
||||||
|
'*/**/*',
|
||||||
|
async (file: string) => {
|
||||||
|
return {
|
||||||
|
projects: {
|
||||||
|
[file]: {
|
||||||
|
root: file,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
|
const options = {};
|
||||||
|
|
||||||
|
const results = await runCreateNodesInParallel(
|
||||||
|
configFiles,
|
||||||
|
plugin,
|
||||||
|
options,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(results).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"file": "file1",
|
||||||
|
"pluginName": "test",
|
||||||
|
"projects": {
|
||||||
|
"file1": {
|
||||||
|
"root": "file1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "file2",
|
||||||
|
"pluginName": "test",
|
||||||
|
"projects": {
|
||||||
|
"file2": {
|
||||||
|
"root": "file2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle async errors', async () => {
|
||||||
|
const plugin = {
|
||||||
|
name: 'test',
|
||||||
|
createNodes: [
|
||||||
|
'*/**/*',
|
||||||
|
async () => {
|
||||||
|
throw new Error('Async Error');
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
|
const options = {};
|
||||||
|
|
||||||
|
const error = await runCreateNodesInParallel(
|
||||||
|
configFiles,
|
||||||
|
plugin,
|
||||||
|
options,
|
||||||
|
context
|
||||||
|
).catch((e) => e);
|
||||||
|
|
||||||
|
expect(error).toMatchInlineSnapshot(
|
||||||
|
`[AggregateCreateNodesError: Failed to create nodes]`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(error.errors).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[CreateNodesError: The "test" plugin threw an error while creating nodes from file1:],
|
||||||
|
[CreateNodesError: The "test" plugin threw an error while creating nodes from file2:],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle sync errors', async () => {
|
||||||
|
const plugin = {
|
||||||
|
name: 'test',
|
||||||
|
createNodes: [
|
||||||
|
'*/**/*',
|
||||||
|
() => {
|
||||||
|
throw new Error('Sync Error');
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
|
const options = {};
|
||||||
|
|
||||||
|
const error = await runCreateNodesInParallel(
|
||||||
|
configFiles,
|
||||||
|
plugin,
|
||||||
|
options,
|
||||||
|
context
|
||||||
|
).catch((e) => e);
|
||||||
|
|
||||||
|
expect(error).toMatchInlineSnapshot(
|
||||||
|
`[AggregateCreateNodesError: Failed to create nodes]`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(error.errors).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[CreateNodesError: The "test" plugin threw an error while creating nodes from file1:],
|
||||||
|
[CreateNodesError: The "test" plugin threw an error while creating nodes from file2:],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -9,7 +9,12 @@ import type {
|
|||||||
LoadedNxPlugin,
|
LoadedNxPlugin,
|
||||||
NormalizedPlugin,
|
NormalizedPlugin,
|
||||||
} from './internal-api';
|
} from './internal-api';
|
||||||
import type { CreateNodesContext, NxPlugin, NxPluginV2 } from './public-api';
|
import {
|
||||||
|
CreateNodesResult,
|
||||||
|
type CreateNodesContext,
|
||||||
|
type NxPlugin,
|
||||||
|
type NxPluginV2,
|
||||||
|
} from './public-api';
|
||||||
import { AggregateCreateNodesError, CreateNodesError } from '../error-types';
|
import { AggregateCreateNodesError, CreateNodesError } from '../error-types';
|
||||||
|
|
||||||
export function isNxPluginV2(plugin: NxPlugin): plugin is NxPluginV2 {
|
export function isNxPluginV2(plugin: NxPlugin): plugin is NxPluginV2 {
|
||||||
@ -49,7 +54,7 @@ export function normalizeNxPlugin(plugin: NxPlugin): NormalizedPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runCreateNodesInParallel(
|
export async function runCreateNodesInParallel(
|
||||||
configFiles: string[],
|
configFiles: readonly string[],
|
||||||
plugin: NormalizedPlugin,
|
plugin: NormalizedPlugin,
|
||||||
options: unknown,
|
options: unknown,
|
||||||
context: CreateNodesContext
|
context: CreateNodesContext
|
||||||
@ -59,39 +64,33 @@ export async function runCreateNodesInParallel(
|
|||||||
const errors: CreateNodesError[] = [];
|
const errors: CreateNodesError[] = [];
|
||||||
const results: CreateNodesResultWithContext[] = [];
|
const results: CreateNodesResultWithContext[] = [];
|
||||||
|
|
||||||
const promises: Array<Promise<void>> = configFiles.map((file) => {
|
const promises: Array<Promise<void>> = configFiles.map(async (file) => {
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - start`);
|
performance.mark(`${plugin.name}:createNodes:${file} - start`);
|
||||||
// Result is either static or a promise, using Promise.resolve lets us
|
try {
|
||||||
// handle both cases with same logic
|
const value = await plugin.createNodes[1](file, options, context);
|
||||||
const value = Promise.resolve(
|
if (value) {
|
||||||
plugin.createNodes[1](file, options, context)
|
results.push({
|
||||||
);
|
...value,
|
||||||
return value
|
file,
|
||||||
.catch((e) => {
|
pluginName: plugin.name,
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
});
|
||||||
errors.push(
|
}
|
||||||
new CreateNodesError({
|
} catch (e) {
|
||||||
error: e,
|
errors.push(
|
||||||
pluginName: plugin.name,
|
new CreateNodesError({
|
||||||
file,
|
error: e,
|
||||||
})
|
pluginName: plugin.name,
|
||||||
);
|
file,
|
||||||
return null;
|
})
|
||||||
})
|
);
|
||||||
.then((r) => {
|
} finally {
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
||||||
performance.measure(
|
performance.measure(
|
||||||
`${plugin.name}:createNodes:${file}`,
|
`${plugin.name}:createNodes:${file}`,
|
||||||
`${plugin.name}:createNodes:${file} - start`,
|
`${plugin.name}:createNodes:${file} - start`,
|
||||||
`${plugin.name}:createNodes:${file} - end`
|
`${plugin.name}:createNodes:${file} - end`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// Existing behavior is to ignore null results of
|
|
||||||
// createNodes function.
|
|
||||||
if (r) {
|
|
||||||
results.push({ ...r, file, pluginName: plugin.name });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(promises).then(() => {
|
await Promise.all(promises).then(() => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user