nx 19.2.0-canary.20240528-7f11a1d → 19.2.0-canary.20240531-2cb7ecb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/package.json +12 -12
  2. package/src/devkit-exports.d.ts +3 -1
  3. package/src/devkit-exports.js +5 -1
  4. package/src/devkit-internals.d.ts +1 -0
  5. package/src/devkit-internals.js +3 -1
  6. package/src/plugins/js/lock-file/pnpm-parser.js +1 -1
  7. package/src/plugins/js/project-graph/build-dependencies/build-dependencies.js +16 -3
  8. package/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.d.ts +2 -1
  9. package/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.js +40 -27
  10. package/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.d.ts +2 -1
  11. package/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.js +14 -19
  12. package/src/plugins/js/project-graph/build-dependencies/target-project-locator.d.ts +32 -5
  13. package/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +146 -47
  14. package/src/plugins/js/utils/resolve-relative-to-dir.d.ts +5 -0
  15. package/src/plugins/js/utils/resolve-relative-to-dir.js +18 -0
  16. package/src/project-graph/error-types.d.ts +37 -19
  17. package/src/project-graph/error-types.js +31 -19
  18. package/src/project-graph/plugins/index.d.ts +1 -0
  19. package/src/project-graph/plugins/index.js +3 -1
  20. package/src/project-graph/plugins/internal-api.d.ts +2 -2
  21. package/src/project-graph/plugins/internal-api.js +33 -3
  22. package/src/project-graph/plugins/public-api.d.ts +34 -3
  23. package/src/project-graph/plugins/utils.d.ts +4 -3
  24. package/src/project-graph/plugins/utils.js +13 -26
  25. package/src/project-graph/utils/project-configuration-utils.js +89 -74
  26. package/src/tasks-runner/utils.js +18 -4
  27. package/src/utils/print-help.js +1 -1
@@ -1,36 +1,65 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TargetProjectLocator = void 0;
4
- const typescript_1 = require("../../utils/typescript");
3
+ exports.TargetProjectLocator = exports.isBuiltinModuleImport = void 0;
4
+ const node_module_1 = require("node:module");
5
+ const node_path_1 = require("node:path");
6
+ const find_project_for_path_1 = require("../../../../project-graph/utils/find-project-for-path");
5
7
  const fileutils_1 = require("../../../../utils/fileutils");
6
- const path_1 = require("path");
7
8
  const workspace_root_1 = require("../../../../utils/workspace-root");
8
- const module_1 = require("module");
9
- const find_project_for_path_1 = require("../../../../project-graph/utils/find-project-for-path");
9
+ const resolve_relative_to_dir_1 = require("../../utils/resolve-relative-to-dir");
10
+ const typescript_1 = require("../../utils/typescript");
11
+ /**
12
+ * Use a shared cache to avoid repeated npm package resolution work within the TargetProjectLocator.
13
+ */
14
+ const defaultNpmResolutionCache = new Map();
10
15
  const builtInModuleSet = new Set([
11
- ...module_1.builtinModules,
12
- ...module_1.builtinModules.map((x) => `node:${x}`),
16
+ ...node_module_1.builtinModules,
17
+ ...node_module_1.builtinModules.map((x) => `node:${x}`),
13
18
  ]);
19
+ function isBuiltinModuleImport(importExpr) {
20
+ const packageName = parsePackageNameFromImportExpression(importExpr);
21
+ return builtInModuleSet.has(packageName);
22
+ }
23
+ exports.isBuiltinModuleImport = isBuiltinModuleImport;
14
24
  class TargetProjectLocator {
15
- constructor(nodes, externalNodes) {
25
+ constructor(nodes, externalNodes = {}, npmResolutionCache = defaultNpmResolutionCache) {
16
26
  this.nodes = nodes;
17
27
  this.externalNodes = externalNodes;
28
+ this.npmResolutionCache = npmResolutionCache;
18
29
  this.projectRootMappings = (0, find_project_for_path_1.createProjectRootMappings)(this.nodes);
19
- this.npmProjects = filterRootExternalDependencies(this.externalNodes);
20
30
  this.tsConfig = this.getRootTsConfig();
21
31
  this.paths = this.tsConfig.config?.compilerOptions?.paths;
22
32
  this.typescriptResolutionCache = new Map();
23
- this.npmResolutionCache = new Map();
33
+ /**
34
+ * Only the npm external nodes should be included.
35
+ *
36
+ * Unlike the raw externalNodes, ensure that there is always copy of the node where the version
37
+ * is set in the key for optimal lookup.
38
+ */
39
+ this.npmProjects = Object.values(this.externalNodes).reduce((acc, node) => {
40
+ if (node.type === 'npm') {
41
+ const keyWithVersion = `npm:${node.data.packageName}@${node.data.version}`;
42
+ if (!acc[node.name]) {
43
+ acc[node.name] = node;
44
+ }
45
+ // The node.name may have already contained the version
46
+ if (!acc[keyWithVersion]) {
47
+ acc[keyWithVersion] = node;
48
+ }
49
+ }
50
+ return acc;
51
+ }, {});
24
52
  }
25
53
  /**
26
- * Find a project based on its import
54
+ * Resolve any workspace or external project that matches the given import expression,
55
+ * originating from the given filePath.
27
56
  *
28
57
  * @param importExpr
29
58
  * @param filePath
30
59
  */
31
- findProjectWithImport(importExpr, filePath) {
60
+ findProjectFromImport(importExpr, filePath) {
32
61
  if ((0, fileutils_1.isRelativePath)(importExpr)) {
33
- const resolvedModule = path_1.posix.join((0, path_1.dirname)(filePath), importExpr);
62
+ const resolvedModule = node_path_1.posix.join((0, node_path_1.dirname)(filePath), importExpr);
34
63
  return this.findProjectOfResolvedModule(resolvedModule);
35
64
  }
36
65
  // find project using tsconfig paths
@@ -39,7 +68,7 @@ class TargetProjectLocator {
39
68
  const [path, paths] = results;
40
69
  for (let p of paths) {
41
70
  const r = p.endsWith('/*')
42
- ? (0, path_1.join)((0, path_1.dirname)(p), (0, path_1.relative)(path.replace(/\*$/, ''), importExpr))
71
+ ? (0, node_path_1.join)((0, node_path_1.dirname)(p), (0, node_path_1.relative)(path.replace(/\*$/, ''), importExpr))
43
72
  : p;
44
73
  const maybeResolvedProject = this.findProjectOfResolvedModule(r);
45
74
  if (maybeResolvedProject) {
@@ -47,14 +76,14 @@ class TargetProjectLocator {
47
76
  }
48
77
  }
49
78
  }
50
- if (builtInModuleSet.has(importExpr)) {
79
+ if (isBuiltinModuleImport(importExpr)) {
51
80
  this.npmResolutionCache.set(importExpr, null);
52
81
  return null;
53
82
  }
54
83
  // try to find npm package before using expensive typescript resolution
55
- const npmProject = this.findNpmPackage(importExpr);
56
- if (npmProject) {
57
- return npmProject;
84
+ const externalProject = this.findNpmProjectFromImport(importExpr, filePath);
85
+ if (externalProject) {
86
+ return externalProject;
58
87
  }
59
88
  if (this.tsConfig.config) {
60
89
  // TODO(meeroslav): this block is probably obsolete
@@ -74,6 +103,55 @@ class TargetProjectLocator {
74
103
  this.npmResolutionCache.set(importExpr, null);
75
104
  return null;
76
105
  }
106
+ /**
107
+ * Resolve any external project that matches the given import expression,
108
+ * relative to the given file path.
109
+ *
110
+ * @param importExpr
111
+ * @param projectRoot
112
+ */
113
+ findNpmProjectFromImport(importExpr, fromFilePath) {
114
+ const packageName = parsePackageNameFromImportExpression(importExpr);
115
+ let fullFilePath = fromFilePath;
116
+ let workspaceRelativeFilePath = fromFilePath;
117
+ if (fromFilePath.startsWith(workspace_root_1.workspaceRoot)) {
118
+ workspaceRelativeFilePath = fromFilePath.replace(workspace_root_1.workspaceRoot, '');
119
+ }
120
+ else {
121
+ fullFilePath = (0, node_path_1.join)(workspace_root_1.workspaceRoot, fromFilePath);
122
+ }
123
+ const fullDirPath = (0, node_path_1.dirname)(fullFilePath);
124
+ const workspaceRelativeDirPath = (0, node_path_1.dirname)(workspaceRelativeFilePath);
125
+ const npmImportForProject = `${packageName}__${workspaceRelativeDirPath}`;
126
+ if (this.npmResolutionCache.has(npmImportForProject)) {
127
+ return this.npmResolutionCache.get(npmImportForProject);
128
+ }
129
+ try {
130
+ // package.json refers to an external package, we do not match against the version found in there, we instead try and resolve the relevant package how node would
131
+ const externalPackageJson = this.readPackageJson(packageName, fullDirPath);
132
+ // The external package.json path might be not be resolvable, e.g. if a reference has been added to a project package.json, but the install command has not been run yet.
133
+ if (!externalPackageJson) {
134
+ // Try and fall back to resolving an external node from the graph by name
135
+ const externalNode = this.npmProjects[`npm:${packageName}`];
136
+ const externalNodeName = externalNode?.name || null;
137
+ this.npmResolutionCache.set(npmImportForProject, externalNodeName);
138
+ return externalNodeName;
139
+ }
140
+ const npmProjectKey = `npm:${externalPackageJson.name}@${externalPackageJson.version}`;
141
+ const matchingExternalNode = this.npmProjects[npmProjectKey];
142
+ if (!matchingExternalNode) {
143
+ return null;
144
+ }
145
+ this.npmResolutionCache.set(npmImportForProject, matchingExternalNode.name);
146
+ return matchingExternalNode.name;
147
+ }
148
+ catch (e) {
149
+ if (process.env.NX_VERBOSE_LOGGING === 'true') {
150
+ console.error(e);
151
+ }
152
+ return null;
153
+ }
154
+ }
77
155
  /**
78
156
  * Return file paths matching the import relative to the repo root
79
157
  * @param normalizedImportExpr
@@ -113,23 +191,10 @@ class TargetProjectLocator {
113
191
  return;
114
192
  }
115
193
  resolveImportWithRequire(normalizedImportExpr, filePath) {
116
- return path_1.posix.relative(workspace_root_1.workspaceRoot, require.resolve(normalizedImportExpr, {
117
- paths: [(0, path_1.dirname)(filePath)],
194
+ return node_path_1.posix.relative(workspace_root_1.workspaceRoot, require.resolve(normalizedImportExpr, {
195
+ paths: [(0, node_path_1.dirname)(filePath)],
118
196
  }));
119
197
  }
120
- findNpmPackage(npmImport) {
121
- if (this.npmResolutionCache.has(npmImport)) {
122
- return this.npmResolutionCache.get(npmImport);
123
- }
124
- else {
125
- const pkg = this.npmProjects.find((pkg) => npmImport === pkg.data.packageName ||
126
- npmImport.startsWith(`${pkg.data.packageName}/`));
127
- if (pkg) {
128
- this.npmResolutionCache.set(npmImport, pkg.name);
129
- return pkg.name;
130
- }
131
- }
132
- }
133
198
  findProjectOfResolvedModule(resolvedModule) {
134
199
  if (resolvedModule.startsWith('node_modules/') ||
135
200
  resolvedModule.includes('/node_modules/')) {
@@ -142,7 +207,7 @@ class TargetProjectLocator {
142
207
  return importedProject ? importedProject.name : void 0;
143
208
  }
144
209
  getAbsolutePath(path) {
145
- return (0, path_1.join)(workspace_root_1.workspaceRoot, path);
210
+ return (0, node_path_1.join)(workspace_root_1.workspaceRoot, path);
146
211
  }
147
212
  getRootTsConfig() {
148
213
  const path = (0, typescript_1.getRootTsConfigFileName)();
@@ -164,20 +229,54 @@ class TargetProjectLocator {
164
229
  const project = (0, find_project_for_path_1.findProjectForPath)(file, this.projectRootMappings);
165
230
  return this.nodes[project];
166
231
  }
232
+ /**
233
+ * In many cases the package.json will be directly resolvable, so we try that first.
234
+ * If, however, package exports are used and the package.json is not defined, we will
235
+ * need to resolve the main entry point of the package and traverse upwards to find the
236
+ * package.json.
237
+ *
238
+ * In some cases, such as when multiple module formats are published, the resolved package.json
239
+ * might only contain the "type" field - no "name" or "version", so in such cases we keep traversing
240
+ * until we find a package.json that contains the "name" and "version" fields.
241
+ */
242
+ readPackageJson(packageName, relativeToDir) {
243
+ // The package.json is directly resolvable
244
+ const packageJsonPath = (0, resolve_relative_to_dir_1.resolveRelativeToDir)((0, node_path_1.join)(packageName, 'package.json'), relativeToDir);
245
+ if (packageJsonPath) {
246
+ return (0, fileutils_1.readJsonFile)(packageJsonPath);
247
+ }
248
+ try {
249
+ // Resolve the main entry point of the package
250
+ const mainPath = (0, resolve_relative_to_dir_1.resolveRelativeToDir)(packageName, relativeToDir);
251
+ let dir = (0, node_path_1.dirname)(mainPath);
252
+ while (dir !== (0, node_path_1.parse)(dir).root) {
253
+ const packageJsonPath = (0, node_path_1.join)(dir, 'package.json');
254
+ try {
255
+ const parsedPackageJson = (0, fileutils_1.readJsonFile)(packageJsonPath);
256
+ // Ensure the package.json contains the "name" and "version" fields
257
+ if (parsedPackageJson.name && parsedPackageJson.version) {
258
+ return parsedPackageJson;
259
+ }
260
+ }
261
+ catch {
262
+ // Package.json doesn't exist, keep traversing
263
+ }
264
+ dir = (0, node_path_1.dirname)(dir);
265
+ }
266
+ return null;
267
+ }
268
+ catch {
269
+ return null;
270
+ }
271
+ }
167
272
  }
168
273
  exports.TargetProjectLocator = TargetProjectLocator;
169
- // matches `npm:@scope/name`, `npm:name` but not `npm:@scope/name@version` and `npm:name@version`
170
- const ROOT_VERSION_PACKAGE_NAME_REGEX = /^npm:(?!.+@.+)/;
171
- function filterRootExternalDependencies(externalNodes) {
172
- if (!externalNodes) {
173
- return [];
174
- }
175
- const keys = Object.keys(externalNodes);
176
- const nodes = [];
177
- for (let i = 0; i < keys.length; i++) {
178
- if (keys[i].match(ROOT_VERSION_PACKAGE_NAME_REGEX)) {
179
- nodes.push(externalNodes[keys[i]]);
180
- }
274
+ function parsePackageNameFromImportExpression(importExpression) {
275
+ // Check if the package is scoped
276
+ if (importExpression.startsWith('@')) {
277
+ // For scoped packages, the package name is up to the second '/'
278
+ return importExpression.split('/').slice(0, 2).join('/');
181
279
  }
182
- return nodes;
280
+ // For unscoped packages, the package name is up to the first '/'
281
+ return importExpression.split('/')[0];
183
282
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * NOTE: This function is in its own file because it is not possible to mock
3
+ * require.resolve directly in jest https://github.com/jestjs/jest/issues/9543
4
+ */
5
+ export declare function resolveRelativeToDir(path: string, relativeToDir: any): string;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRelativeToDir = void 0;
4
+ /**
5
+ * NOTE: This function is in its own file because it is not possible to mock
6
+ * require.resolve directly in jest https://github.com/jestjs/jest/issues/9543
7
+ */
8
+ function resolveRelativeToDir(path, relativeToDir) {
9
+ try {
10
+ return require.resolve(path, {
11
+ paths: [relativeToDir],
12
+ });
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ exports.resolveRelativeToDir = resolveRelativeToDir;
@@ -1,10 +1,10 @@
1
- import { CreateNodesResultWithContext } from './plugins/internal-api';
2
1
  import { ConfigurationResult, ConfigurationSourceMaps } from './utils/project-configuration-utils';
3
2
  import { ProjectConfiguration } from '../config/workspace-json-project-json';
4
3
  import { ProjectGraph } from '../config/project-graph';
4
+ import { CreateNodesFunctionV2 } from './plugins';
5
5
  export declare class ProjectGraphError extends Error {
6
6
  #private;
7
- constructor(errors: Array<CreateNodesError | MergeNodesError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError | ProcessDependenciesError | ProcessProjectGraphError | CreateMetadataError | WorkspaceValidityError>, partialProjectGraph: ProjectGraph, partialSourceMaps: ConfigurationSourceMaps);
7
+ constructor(errors: Array<AggregateCreateNodesError | MergeNodesError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError | ProcessDependenciesError | ProcessProjectGraphError | CreateMetadataError | WorkspaceValidityError>, partialProjectGraph: ProjectGraph, partialSourceMaps: ConfigurationSourceMaps);
8
8
  /**
9
9
  * The daemon cannot throw errors which contain methods as they are not serializable.
10
10
  *
@@ -18,7 +18,7 @@ export declare class ProjectGraphError extends Error {
18
18
  */
19
19
  getPartialProjectGraph(): ProjectGraph;
20
20
  getPartialSourcemaps(): ConfigurationSourceMaps;
21
- getErrors(): (CreateNodesError | MergeNodesError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError | CreateMetadataError | ProcessDependenciesError | ProcessProjectGraphError | WorkspaceValidityError)[];
21
+ getErrors(): (AggregateCreateNodesError | MergeNodesError | CreateMetadataError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError | ProcessDependenciesError | ProcessProjectGraphError | WorkspaceValidityError)[];
22
22
  }
23
23
  export declare class MultipleProjectsWithSameNameError extends Error {
24
24
  conflicts: Map<string, string[]>;
@@ -44,25 +44,44 @@ export declare class ProjectWithNoNameError extends Error {
44
44
  }
45
45
  export declare function isProjectWithNoNameError(e: unknown): e is ProjectWithNoNameError;
46
46
  export declare class ProjectConfigurationsError extends Error {
47
- readonly errors: Array<MergeNodesError | CreateNodesError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError>;
47
+ readonly errors: Array<MergeNodesError | AggregateCreateNodesError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError>;
48
48
  readonly partialProjectConfigurationsResult: ConfigurationResult;
49
- constructor(errors: Array<MergeNodesError | CreateNodesError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError>, partialProjectConfigurationsResult: ConfigurationResult);
49
+ constructor(errors: Array<MergeNodesError | AggregateCreateNodesError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError>, partialProjectConfigurationsResult: ConfigurationResult);
50
50
  }
51
51
  export declare function isProjectConfigurationsError(e: unknown): e is ProjectConfigurationsError;
52
- export declare class CreateNodesError extends Error {
53
- file: string;
54
- pluginName: string;
55
- constructor({ file, pluginName, error, }: {
56
- file: string;
57
- pluginName: string;
58
- error: Error;
59
- });
60
- }
52
+ /**
53
+ * This error should be thrown when a `createNodesV2` function hits a recoverable error.
54
+ * It allows Nx to recieve partial results and continue processing for better UX.
55
+ */
61
56
  export declare class AggregateCreateNodesError extends Error {
62
- readonly pluginName: string;
63
- readonly errors: Array<CreateNodesError>;
64
- readonly partialResults: Array<CreateNodesResultWithContext>;
65
- constructor(pluginName: string, errors: Array<CreateNodesError>, partialResults: Array<CreateNodesResultWithContext>);
57
+ readonly errors: Array<[file: string | null, error: Error]>;
58
+ readonly partialResults: Awaited<ReturnType<CreateNodesFunctionV2>>;
59
+ /**
60
+ * Throwing this error from a `createNodesV2` function will allow Nx to continue processing and recieve partial results from your plugin.
61
+ * @example
62
+ * export async function createNodesV2(
63
+ * files: string[],
64
+ * ) {
65
+ * const partialResults = [];
66
+ * const errors = [];
67
+ * await Promise.all(files.map(async (file) => {
68
+ * try {
69
+ * const result = await createNodes(file);
70
+ * partialResults.push(result);
71
+ * } catch (e) {
72
+ * errors.push([file, e]);
73
+ * }
74
+ * }));
75
+ * if (errors.length > 0) {
76
+ * throw new AggregateCreateNodesError(errors, partialResults);
77
+ * }
78
+ * return partialResults;
79
+ * }
80
+ *
81
+ * @param errors An array of tuples that represent errors encountered when processing a given file. An example entry might look like ['path/to/project.json', [Error: 'Invalid JSON. Unexpected token 'a' in JSON at position 0]]
82
+ * @param partialResults The partial results of the `createNodesV2` function. This should be the results for each file that didn't encounter an issue.
83
+ */
84
+ constructor(errors: Array<[file: string | null, error: Error]>, partialResults: Awaited<ReturnType<CreateNodesFunctionV2>>);
66
85
  }
67
86
  export declare class MergeNodesError extends Error {
68
87
  file: string;
@@ -102,7 +121,6 @@ export declare class AggregateProjectGraphError extends Error {
102
121
  }
103
122
  export declare function isAggregateProjectGraphError(e: unknown): e is AggregateProjectGraphError;
104
123
  export declare function isCreateMetadataError(e: unknown): e is CreateMetadataError;
105
- export declare function isCreateNodesError(e: unknown): e is CreateNodesError;
106
124
  export declare function isAggregateCreateNodesError(e: unknown): e is AggregateCreateNodesError;
107
125
  export declare function isMergeNodesError(e: unknown): e is MergeNodesError;
108
126
  export declare class DaemonProjectGraphError extends Error {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  var _ProjectGraphError_errors, _ProjectGraphError_partialProjectGraph, _ProjectGraphError_partialSourceMaps;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.LoadPluginError = exports.DaemonProjectGraphError = exports.isMergeNodesError = exports.isAggregateCreateNodesError = exports.isCreateNodesError = exports.isCreateMetadataError = exports.isAggregateProjectGraphError = exports.AggregateProjectGraphError = exports.ProcessProjectGraphError = exports.isWorkspaceValidityError = exports.WorkspaceValidityError = exports.ProcessDependenciesError = exports.CreateMetadataError = exports.MergeNodesError = exports.AggregateCreateNodesError = exports.CreateNodesError = exports.isProjectConfigurationsError = exports.ProjectConfigurationsError = exports.isProjectWithNoNameError = exports.ProjectWithNoNameError = exports.isProjectsWithNoNameError = exports.ProjectsWithNoNameError = exports.isMultipleProjectsWithSameNameError = exports.isProjectWithExistingNameError = exports.ProjectWithExistingNameError = exports.MultipleProjectsWithSameNameError = exports.ProjectGraphError = void 0;
4
+ exports.LoadPluginError = exports.DaemonProjectGraphError = exports.isMergeNodesError = exports.isAggregateCreateNodesError = exports.isCreateMetadataError = exports.isAggregateProjectGraphError = exports.AggregateProjectGraphError = exports.ProcessProjectGraphError = exports.isWorkspaceValidityError = exports.WorkspaceValidityError = exports.ProcessDependenciesError = exports.CreateMetadataError = exports.MergeNodesError = exports.AggregateCreateNodesError = exports.isProjectConfigurationsError = exports.ProjectConfigurationsError = exports.isProjectWithNoNameError = exports.ProjectWithNoNameError = exports.isProjectsWithNoNameError = exports.ProjectsWithNoNameError = exports.isMultipleProjectsWithSameNameError = exports.isProjectWithExistingNameError = exports.ProjectWithExistingNameError = exports.MultipleProjectsWithSameNameError = exports.ProjectGraphError = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  class ProjectGraphError extends Error {
7
7
  constructor(errors, partialProjectGraph, partialSourceMaps) {
@@ -126,21 +126,38 @@ function isProjectConfigurationsError(e) {
126
126
  e?.name === ProjectConfigurationsError.name));
127
127
  }
128
128
  exports.isProjectConfigurationsError = isProjectConfigurationsError;
129
- class CreateNodesError extends Error {
130
- constructor({ file, pluginName, error, }) {
131
- const msg = `The "${pluginName}" plugin threw an error while creating nodes from ${file}:`;
132
- super(msg, { cause: error });
133
- this.name = this.constructor.name;
134
- this.file = file;
135
- this.pluginName = pluginName;
136
- this.stack = `${this.message}\n ${error.stack.split('\n').join('\n ')}`;
137
- }
138
- }
139
- exports.CreateNodesError = CreateNodesError;
129
+ /**
130
+ * This error should be thrown when a `createNodesV2` function hits a recoverable error.
131
+ * It allows Nx to recieve partial results and continue processing for better UX.
132
+ */
140
133
  class AggregateCreateNodesError extends Error {
141
- constructor(pluginName, errors, partialResults) {
134
+ /**
135
+ * Throwing this error from a `createNodesV2` function will allow Nx to continue processing and recieve partial results from your plugin.
136
+ * @example
137
+ * export async function createNodesV2(
138
+ * files: string[],
139
+ * ) {
140
+ * const partialResults = [];
141
+ * const errors = [];
142
+ * await Promise.all(files.map(async (file) => {
143
+ * try {
144
+ * const result = await createNodes(file);
145
+ * partialResults.push(result);
146
+ * } catch (e) {
147
+ * errors.push([file, e]);
148
+ * }
149
+ * }));
150
+ * if (errors.length > 0) {
151
+ * throw new AggregateCreateNodesError(errors, partialResults);
152
+ * }
153
+ * return partialResults;
154
+ * }
155
+ *
156
+ * @param errors An array of tuples that represent errors encountered when processing a given file. An example entry might look like ['path/to/project.json', [Error: 'Invalid JSON. Unexpected token 'a' in JSON at position 0]]
157
+ * @param partialResults The partial results of the `createNodesV2` function. This should be the results for each file that didn't encounter an issue.
158
+ */
159
+ constructor(errors, partialResults) {
142
160
  super('Failed to create nodes');
143
- this.pluginName = pluginName;
144
161
  this.errors = errors;
145
162
  this.partialResults = partialResults;
146
163
  this.name = this.constructor.name;
@@ -230,11 +247,6 @@ function isCreateMetadataError(e) {
230
247
  e?.name === CreateMetadataError.name));
231
248
  }
232
249
  exports.isCreateMetadataError = isCreateMetadataError;
233
- function isCreateNodesError(e) {
234
- return (e instanceof CreateNodesError ||
235
- (typeof e === 'object' && 'name' in e && e?.name === CreateNodesError.name));
236
- }
237
- exports.isCreateNodesError = isCreateNodesError;
238
250
  function isAggregateCreateNodesError(e) {
239
251
  return (e instanceof AggregateCreateNodesError ||
240
252
  (typeof e === 'object' &&
@@ -1,2 +1,3 @@
1
1
  export * from './public-api';
2
2
  export { readPluginPackageJson, registerPluginTSTranspiler } from './loader';
3
+ export { createNodesFromFiles } from './utils';
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerPluginTSTranspiler = exports.readPluginPackageJson = void 0;
3
+ exports.createNodesFromFiles = exports.registerPluginTSTranspiler = exports.readPluginPackageJson = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  tslib_1.__exportStar(require("./public-api"), exports);
6
6
  var loader_1 = require("./loader");
7
7
  Object.defineProperty(exports, "readPluginPackageJson", { enumerable: true, get: function () { return loader_1.readPluginPackageJson; } });
8
8
  Object.defineProperty(exports, "registerPluginTSTranspiler", { enumerable: true, get: function () { return loader_1.registerPluginTSTranspiler; } });
9
+ var utils_1 = require("./utils");
10
+ Object.defineProperty(exports, "createNodesFromFiles", { enumerable: true, get: function () { return utils_1.createNodesFromFiles; } });
@@ -1,12 +1,12 @@
1
1
  import { PluginConfiguration } from '../../config/nx-json';
2
2
  import { NxPluginV1 } from '../../utils/nx-plugin.deprecated';
3
- import { CreateDependencies, CreateDependenciesContext, CreateMetadata, CreateMetadataContext, CreateNodesContext, CreateNodesResult, NxPluginV2 } from './public-api';
3
+ import { CreateDependencies, CreateDependenciesContext, CreateMetadata, CreateMetadataContext, CreateNodesContextV2, CreateNodesResult, NxPluginV2 } from './public-api';
4
4
  import { ProjectGraph, ProjectGraphProcessor } from '../../config/project-graph';
5
5
  export declare class LoadedNxPlugin {
6
6
  readonly name: string;
7
7
  readonly createNodes?: [
8
8
  filePattern: string,
9
- fn: (matchedFiles: string[], context: CreateNodesContext) => Promise<CreateNodesResultWithContext[]>
9
+ fn: (matchedFiles: string[], context: CreateNodesContextV2) => Promise<Array<readonly [plugin: string, file: string, result: CreateNodesResult]>>
10
10
  ];
11
11
  readonly createDependencies?: (context: CreateDependenciesContext) => ReturnType<CreateDependencies>;
12
12
  readonly createMetadata?: (graph: ProjectGraph, context: CreateMetadataContext) => ReturnType<CreateMetadata>;
@@ -6,9 +6,10 @@ exports.getDefaultPlugins = exports.loadNxPlugins = exports.nxPluginCache = expo
6
6
  const path_1 = require("path");
7
7
  const workspace_root_1 = require("../../utils/workspace-root");
8
8
  const angular_json_1 = require("../../adapter/angular-json");
9
- const utils_1 = require("./utils");
10
9
  const isolation_1 = require("./isolation");
11
10
  const loader_1 = require("./loader");
11
+ const utils_1 = require("./utils");
12
+ const error_types_1 = require("../error-types");
12
13
  class LoadedNxPlugin {
13
14
  constructor(plugin, pluginDefinition) {
14
15
  this.name = plugin.name;
@@ -17,12 +18,41 @@ class LoadedNxPlugin {
17
18
  this.include = pluginDefinition.include;
18
19
  this.exclude = pluginDefinition.exclude;
19
20
  }
20
- if (plugin.createNodes) {
21
+ if (plugin.createNodes && !plugin.createNodesV2) {
21
22
  this.createNodes = [
22
23
  plugin.createNodes[0],
23
- (files, context) => (0, utils_1.runCreateNodesInParallel)(files, plugin, this.options, context),
24
+ (configFiles, context) => (0, utils_1.createNodesFromFiles)(plugin.createNodes[1], configFiles, this.options, context).then((results) => results.map((r) => [this.name, r[0], r[1]])),
25
+ ];
26
+ }
27
+ if (plugin.createNodesV2) {
28
+ this.createNodes = [
29
+ plugin.createNodesV2[0],
30
+ async (configFiles, context) => {
31
+ const result = await plugin.createNodesV2[1](configFiles, this.options, context);
32
+ return result.map((r) => [this.name, r[0], r[1]]);
33
+ },
24
34
  ];
25
35
  }
36
+ if (this.createNodes) {
37
+ const inner = this.createNodes[1];
38
+ this.createNodes[1] = async (...args) => {
39
+ performance.mark(`${plugin.name}:createNodes - start`);
40
+ try {
41
+ return await inner(...args);
42
+ }
43
+ catch (e) {
44
+ if ((0, error_types_1.isAggregateCreateNodesError)(e)) {
45
+ throw e;
46
+ }
47
+ // The underlying plugin errored out. We can't know any partial results.
48
+ throw new error_types_1.AggregateCreateNodesError([null, e], []);
49
+ }
50
+ finally {
51
+ performance.mark(`${plugin.name}:createNodes - end`);
52
+ performance.measure(`${plugin.name}:createNodes`, `${plugin.name}:createNodes - start`, `${plugin.name}:createNodes - end`);
53
+ }
54
+ };
55
+ }
26
56
  if (plugin.createDependencies) {
27
57
  this.createDependencies = (context) => plugin.createDependencies(this.options, context);
28
58
  }
@@ -6,19 +6,23 @@ import { RawProjectGraphDependency } from '../project-graph-builder';
6
6
  /**
7
7
  * Context for {@link CreateNodesFunction}
8
8
  */
9
- export interface CreateNodesContext {
10
- readonly nxJsonConfiguration: NxJsonConfiguration;
11
- readonly workspaceRoot: string;
9
+ export interface CreateNodesContext extends CreateNodesContextV2 {
12
10
  /**
13
11
  * The subset of configuration files which match the createNodes pattern
14
12
  */
15
13
  readonly configFiles: readonly string[];
16
14
  }
15
+ export interface CreateNodesContextV2 {
16
+ readonly nxJsonConfiguration: NxJsonConfiguration;
17
+ readonly workspaceRoot: string;
18
+ }
17
19
  /**
18
20
  * A function which parses a configuration file into a set of nodes.
19
21
  * Used for creating nodes for the {@link ProjectGraph}
20
22
  */
21
23
  export type CreateNodesFunction<T = unknown> = (projectConfigurationFile: string, options: T | undefined, context: CreateNodesContext) => CreateNodesResult | Promise<CreateNodesResult>;
24
+ export type CreateNodesResultV2 = Array<readonly [configFileSource: string, result: CreateNodesResult]>;
25
+ export type CreateNodesFunctionV2<T = unknown> = (projectConfigurationFiles: readonly string[], options: T | undefined, context: CreateNodesContextV2) => CreateNodesResultV2 | Promise<CreateNodesResultV2>;
22
26
  export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
23
27
  export interface CreateNodesResult {
24
28
  /**
@@ -32,11 +36,29 @@ export interface CreateNodesResult {
32
36
  }
33
37
  /**
34
38
  * A pair of file patterns and {@link CreateNodesFunction}
39
+ *
40
+ * Nx 19.2+: Both original `CreateNodes` and `CreateNodesV2` are supported. Nx will only invoke `CreateNodesV2` if it is present.
41
+ * Nx 20.X : The `CreateNodesV2` will be the only supported API. This typing will still exist, but be identical to `CreateNodesV2`.
42
+ Nx **will not** invoke the original `plugin.createNodes` callback. This should give plugin authors a window to transition.
43
+ Plugin authors should update their plugin's `createNodes` function to align with `CreateNodesV2` / the updated `CreateNodes`.
44
+ The plugin should contain something like: `export createNodes = createNodesV2;` during this period. This will allow the plugin
45
+ to maintain compatibility with Nx 19.2 and up.
46
+ * Nx 21.X : The `CreateNodesV2` typing will be removed, as it has replaced `CreateNodes`.
47
+ *
48
+ * @deprecated Use {@link CreateNodesV2} instead. CreateNodesV2 will replace this API. Read more about the transition above.
35
49
  */
36
50
  export type CreateNodes<T = unknown> = readonly [
37
51
  projectFilePattern: string,
38
52
  createNodesFunction: CreateNodesFunction<T>
39
53
  ];
54
+ /**
55
+ * A pair of file patterns and {@link CreateNodesFunctionV2}
56
+ * In Nx 20 {@link CreateNodes} will be replaced with this type. In Nx 21, this type will be removed.
57
+ */
58
+ export type CreateNodesV2<T = unknown> = readonly [
59
+ projectFilePattern: string,
60
+ createNodesFunction: CreateNodesFunctionV2<T>
61
+ ];
40
62
  /**
41
63
  * Context for {@link CreateDependencies}
42
64
  */
@@ -82,8 +104,17 @@ export type NxPluginV2<TOptions = unknown> = {
82
104
  /**
83
105
  * Provides a file pattern and function that retrieves configuration info from
84
106
  * those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile }
107
+ *
108
+ * @deprecated Use {@link createNodesV2} instead. In Nx 20 support for calling createNodes with a single file for the first argument will be removed.
85
109
  */
86
110
  createNodes?: CreateNodes<TOptions>;
111
+ /**
112
+ * Provides a file pattern and function that retrieves configuration info from
113
+ * those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFiles }
114
+ *
115
+ * In Nx 20 {@link createNodes} will be replaced with this property. In Nx 21, this property will be removed.
116
+ */
117
+ createNodesV2?: CreateNodesV2<TOptions>;
87
118
  /**
88
119
  * Provides a function to analyze files to create dependencies for the {@link ProjectGraph}
89
120
  */
@@ -1,7 +1,8 @@
1
1
  import type { NxPluginV1 } from '../../utils/nx-plugin.deprecated';
2
- import type { CreateNodesResultWithContext, LoadedNxPlugin, NormalizedPlugin } from './internal-api';
3
- import { type CreateNodesContext, type NxPlugin, type NxPluginV2 } from './public-api';
2
+ import type { LoadedNxPlugin, NormalizedPlugin } from './internal-api';
3
+ import { CreateNodesContextV2, CreateNodesFunction, CreateNodesResult, type NxPlugin, type NxPluginV2 } from './public-api';
4
4
  export declare function isNxPluginV2(plugin: NxPlugin): plugin is NxPluginV2;
5
5
  export declare function isNxPluginV1(plugin: NxPlugin | LoadedNxPlugin): plugin is NxPluginV1;
6
6
  export declare function normalizeNxPlugin(plugin: NxPlugin): NormalizedPlugin;
7
- export declare function runCreateNodesInParallel(configFiles: readonly string[], plugin: NormalizedPlugin, options: unknown, context: CreateNodesContext): Promise<CreateNodesResultWithContext[]>;
7
+ export type AsyncFn<T extends Function> = T extends (...args: infer A) => infer R ? (...args: A) => Promise<Awaited<R>> : never;
8
+ export declare function createNodesFromFiles<T = unknown>(createNodes: CreateNodesFunction, configFiles: readonly string[], options: T, context: CreateNodesContextV2): Promise<[file: string, value: CreateNodesResult][]>;