nx 19.2.0-canary.20240530-316dcb9 → 19.2.0-canary.20240531-2cb7ecb

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx",
3
- "version": "19.2.0-canary.20240530-316dcb9",
3
+ "version": "19.2.0-canary.20240531-2cb7ecb",
4
4
  "private": false,
5
5
  "description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
6
6
  "repository": {
@@ -69,7 +69,7 @@
69
69
  "yargs-parser": "21.1.1",
70
70
  "node-machine-id": "1.1.12",
71
71
  "ora": "5.3.0",
72
- "@nrwl/tao": "19.2.0-canary.20240530-316dcb9"
72
+ "@nrwl/tao": "19.2.0-canary.20240531-2cb7ecb"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "@swc-node/register": "^1.8.0",
@@ -84,16 +84,16 @@
84
84
  }
85
85
  },
86
86
  "optionalDependencies": {
87
- "@nx/nx-darwin-x64": "19.2.0-canary.20240530-316dcb9",
88
- "@nx/nx-darwin-arm64": "19.2.0-canary.20240530-316dcb9",
89
- "@nx/nx-linux-x64-gnu": "19.2.0-canary.20240530-316dcb9",
90
- "@nx/nx-linux-x64-musl": "19.2.0-canary.20240530-316dcb9",
91
- "@nx/nx-win32-x64-msvc": "19.2.0-canary.20240530-316dcb9",
92
- "@nx/nx-linux-arm64-gnu": "19.2.0-canary.20240530-316dcb9",
93
- "@nx/nx-linux-arm64-musl": "19.2.0-canary.20240530-316dcb9",
94
- "@nx/nx-linux-arm-gnueabihf": "19.2.0-canary.20240530-316dcb9",
95
- "@nx/nx-win32-arm64-msvc": "19.2.0-canary.20240530-316dcb9",
96
- "@nx/nx-freebsd-x64": "19.2.0-canary.20240530-316dcb9"
87
+ "@nx/nx-darwin-x64": "19.2.0-canary.20240531-2cb7ecb",
88
+ "@nx/nx-darwin-arm64": "19.2.0-canary.20240531-2cb7ecb",
89
+ "@nx/nx-linux-x64-gnu": "19.2.0-canary.20240531-2cb7ecb",
90
+ "@nx/nx-linux-x64-musl": "19.2.0-canary.20240531-2cb7ecb",
91
+ "@nx/nx-win32-x64-msvc": "19.2.0-canary.20240531-2cb7ecb",
92
+ "@nx/nx-linux-arm64-gnu": "19.2.0-canary.20240531-2cb7ecb",
93
+ "@nx/nx-linux-arm64-musl": "19.2.0-canary.20240531-2cb7ecb",
94
+ "@nx/nx-linux-arm-gnueabihf": "19.2.0-canary.20240531-2cb7ecb",
95
+ "@nx/nx-win32-arm64-msvc": "19.2.0-canary.20240531-2cb7ecb",
96
+ "@nx/nx-freebsd-x64": "19.2.0-canary.20240531-2cb7ecb"
97
97
  },
98
98
  "nx-migrations": {
99
99
  "migrations": "./migrations.json",
@@ -15,7 +15,9 @@ export type { WorkspaceJsonConfiguration, ProjectsConfigurations, TargetDependen
15
15
  */
16
16
  export type { Generator, GeneratorCallback, Executor, ExecutorContext, TaskGraphExecutor, GeneratorsJson, ExecutorsJson, MigrationsJson, CustomHasher, HasherContext, } from './config/misc-interfaces';
17
17
  export { workspaceLayout } from './config/configuration';
18
- export type { NxPlugin, NxPluginV2, CreateNodes, CreateNodesFunction, CreateNodesResult, CreateNodesContext, CreateDependencies, CreateDependenciesContext, CreateMetadata, CreateMetadataContext, ProjectsMetadata, } from './project-graph/plugins';
18
+ export type { NxPlugin, NxPluginV2, CreateNodes, CreateNodesFunction, CreateNodesResult, CreateNodesContext, CreateNodesContextV2, CreateNodesFunctionV2, CreateNodesResultV2, CreateNodesV2, CreateDependencies, CreateDependenciesContext, CreateMetadata, CreateMetadataContext, ProjectsMetadata, } from './project-graph/plugins';
19
+ export { AggregateCreateNodesError } from './project-graph/error-types';
20
+ export { createNodesFromFiles } from './project-graph/plugins';
19
21
  export type { NxPluginV1, ProjectTargetConfigurator, } from './utils/nx-plugin.deprecated';
20
22
  /**
21
23
  * @category Workspace
@@ -4,9 +4,13 @@
4
4
  * Try hard to not add to this API to reduce the surface area we need to maintain.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.createProjectFileMapUsingProjectGraph = exports.cacheDir = exports.hashArray = exports.defaultTasksRunner = exports.getOutputsForTargetAndConfiguration = exports.readProjectsConfigurationFromProjectGraph = exports.readCachedProjectGraph = exports.createProjectGraphAsync = exports.reverse = exports.appRootPath = exports.workspaceRoot = exports.normalizePath = exports.joinPathFragments = exports.stripIndents = exports.writeJsonFile = exports.readJsonFile = exports.stripJsonComments = exports.serializeJson = exports.parseJson = exports.updateJson = exports.writeJson = exports.readJson = exports.validateDependency = exports.ProjectGraphBuilder = exports.DependencyType = exports.updateNxJson = exports.readNxJson = exports.glob = exports.getProjects = exports.updateProjectConfiguration = exports.removeProjectConfiguration = exports.readProjectConfiguration = exports.addProjectConfiguration = exports.runExecutor = exports.isWorkspacesEnabled = exports.getPackageManagerVersion = exports.detectPackageManager = exports.getPackageManagerCommand = exports.output = exports.logger = exports.workspaceLayout = void 0;
7
+ exports.createProjectFileMapUsingProjectGraph = exports.cacheDir = exports.hashArray = exports.defaultTasksRunner = exports.getOutputsForTargetAndConfiguration = exports.readProjectsConfigurationFromProjectGraph = exports.readCachedProjectGraph = exports.createProjectGraphAsync = exports.reverse = exports.appRootPath = exports.workspaceRoot = exports.normalizePath = exports.joinPathFragments = exports.stripIndents = exports.writeJsonFile = exports.readJsonFile = exports.stripJsonComments = exports.serializeJson = exports.parseJson = exports.updateJson = exports.writeJson = exports.readJson = exports.validateDependency = exports.ProjectGraphBuilder = exports.DependencyType = exports.updateNxJson = exports.readNxJson = exports.glob = exports.getProjects = exports.updateProjectConfiguration = exports.removeProjectConfiguration = exports.readProjectConfiguration = exports.addProjectConfiguration = exports.runExecutor = exports.isWorkspacesEnabled = exports.getPackageManagerVersion = exports.detectPackageManager = exports.getPackageManagerCommand = exports.output = exports.logger = exports.createNodesFromFiles = exports.AggregateCreateNodesError = exports.workspaceLayout = void 0;
8
8
  var configuration_1 = require("./config/configuration");
9
9
  Object.defineProperty(exports, "workspaceLayout", { enumerable: true, get: function () { return configuration_1.workspaceLayout; } });
10
+ var error_types_1 = require("./project-graph/error-types");
11
+ Object.defineProperty(exports, "AggregateCreateNodesError", { enumerable: true, get: function () { return error_types_1.AggregateCreateNodesError; } });
12
+ var plugins_1 = require("./project-graph/plugins");
13
+ Object.defineProperty(exports, "createNodesFromFiles", { enumerable: true, get: function () { return plugins_1.createNodesFromFiles; } });
10
14
  /**
11
15
  * @category Logger
12
16
  */
@@ -1,12 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildExplicitDependencies = void 0;
4
- const explicit_project_dependencies_1 = require("./explicit-project-dependencies");
5
4
  const explicit_package_json_dependencies_1 = require("./explicit-package-json-dependencies");
5
+ const explicit_project_dependencies_1 = require("./explicit-project-dependencies");
6
+ const target_project_locator_1 = require("./target-project-locator");
6
7
  function buildExplicitDependencies(jsPluginConfig, ctx) {
7
8
  if (totalNumberOfFilesToProcess(ctx) === 0)
8
9
  return [];
9
10
  let dependencies = [];
11
+ // TODO: TargetProjectLocator is a public API, so we can't change the shape of it
12
+ // We should eventually let it accept Record<string, ProjectConfiguration> s.t. we
13
+ // don't have to reshape the CreateDependenciesContext here.
14
+ const nodes = Object.fromEntries(Object.entries(ctx.projects).map(([key, config]) => [
15
+ key,
16
+ {
17
+ name: key,
18
+ type: null,
19
+ data: config,
20
+ },
21
+ ]));
22
+ const targetProjectLocator = new target_project_locator_1.TargetProjectLocator(nodes, ctx.externalNodes);
10
23
  if (jsPluginConfig.analyzeSourceFiles === undefined ||
11
24
  jsPluginConfig.analyzeSourceFiles === true) {
12
25
  let tsExists = false;
@@ -16,12 +29,12 @@ function buildExplicitDependencies(jsPluginConfig, ctx) {
16
29
  }
17
30
  catch { }
18
31
  if (tsExists) {
19
- dependencies = dependencies.concat((0, explicit_project_dependencies_1.buildExplicitTypeScriptDependencies)(ctx));
32
+ dependencies = dependencies.concat((0, explicit_project_dependencies_1.buildExplicitTypeScriptDependencies)(ctx, targetProjectLocator));
20
33
  }
21
34
  }
22
35
  if (jsPluginConfig.analyzePackageJson === undefined ||
23
36
  jsPluginConfig.analyzePackageJson === true) {
24
- dependencies = dependencies.concat((0, explicit_package_json_dependencies_1.buildExplicitPackageJsonDependencies)(ctx));
37
+ dependencies = dependencies.concat((0, explicit_package_json_dependencies_1.buildExplicitPackageJsonDependencies)(ctx, targetProjectLocator));
25
38
  }
26
39
  return dependencies;
27
40
  }
@@ -1,3 +1,4 @@
1
1
  import { CreateDependenciesContext } from '../../../../project-graph/plugins';
2
2
  import { RawProjectGraphDependency } from '../../../../project-graph/project-graph-builder';
3
- export declare function buildExplicitPackageJsonDependencies(ctx: CreateDependenciesContext): RawProjectGraphDependency[];
3
+ import { TargetProjectLocator } from './target-project-locator';
4
+ export declare function buildExplicitPackageJsonDependencies(ctx: CreateDependenciesContext, targetProjectLocator: TargetProjectLocator): RawProjectGraphDependency[];
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildExplicitPackageJsonDependencies = void 0;
4
- const file_utils_1 = require("../../../../project-graph/file-utils");
5
- const path_1 = require("path");
4
+ const node_path_1 = require("node:path");
6
5
  const project_graph_1 = require("../../../../config/project-graph");
7
- const json_1 = require("../../../../utils/json");
8
- const path_2 = require("../../../../utils/path");
6
+ const file_utils_1 = require("../../../../project-graph/file-utils");
9
7
  const project_graph_builder_1 = require("../../../../project-graph/project-graph-builder");
10
- function buildExplicitPackageJsonDependencies(ctx) {
8
+ const json_1 = require("../../../../utils/json");
9
+ const path_1 = require("../../../../utils/path");
10
+ function buildExplicitPackageJsonDependencies(ctx, targetProjectLocator) {
11
11
  const res = [];
12
12
  let packageNameMap = undefined;
13
13
  const nodes = Object.values(ctx.projects);
@@ -16,7 +16,7 @@ function buildExplicitPackageJsonDependencies(ctx) {
16
16
  if (isPackageJsonAtProjectRoot(nodes, f.file)) {
17
17
  // we only create the package name map once and only if a package.json file changes
18
18
  packageNameMap = packageNameMap || createPackageNameMap(ctx.projects);
19
- processPackageJson(source, f.file, ctx, res, packageNameMap);
19
+ processPackageJson(source, f.file, ctx, targetProjectLocator, res, packageNameMap);
20
20
  }
21
21
  });
22
22
  });
@@ -27,7 +27,7 @@ function createPackageNameMap(projects) {
27
27
  const res = {};
28
28
  for (let projectName of Object.keys(projects)) {
29
29
  try {
30
- const packageJson = (0, json_1.parseJson)((0, file_utils_1.defaultFileRead)((0, path_1.join)(projects[projectName].root, 'package.json')));
30
+ const packageJson = (0, json_1.parseJson)((0, file_utils_1.defaultFileRead)((0, node_path_1.join)(projects[projectName].root, 'package.json')));
31
31
  res[packageJson.name ?? projectName] = projectName;
32
32
  }
33
33
  catch (e) { }
@@ -36,13 +36,12 @@ function createPackageNameMap(projects) {
36
36
  }
37
37
  function isPackageJsonAtProjectRoot(nodes, fileName) {
38
38
  return (fileName.endsWith('package.json') &&
39
- nodes.find((projectNode) => (0, path_2.joinPathFragments)(projectNode.root, 'package.json') === fileName));
39
+ nodes.find((projectNode) => (0, path_1.joinPathFragments)(projectNode.root, 'package.json') === fileName));
40
40
  }
41
- function processPackageJson(sourceProject, fileName, ctx, collectedDeps, packageNameMap) {
41
+ function processPackageJson(sourceProject, fileName, ctx, targetProjectLocator, collectedDeps, packageNameMap) {
42
42
  try {
43
43
  const deps = readDeps((0, json_1.parseJson)((0, file_utils_1.defaultFileRead)(fileName)));
44
- // the name matches the import path
45
- deps.forEach((d) => {
44
+ for (const d of Object.keys(deps)) {
46
45
  // package.json refers to another project in the monorepo
47
46
  if (packageNameMap[d]) {
48
47
  const dependency = {
@@ -53,30 +52,44 @@ function processPackageJson(sourceProject, fileName, ctx, collectedDeps, package
53
52
  };
54
53
  (0, project_graph_builder_1.validateDependency)(dependency, ctx);
55
54
  collectedDeps.push(dependency);
55
+ continue;
56
56
  }
57
- else if (ctx.externalNodes[`npm:${d}`]) {
58
- const dependency = {
59
- source: sourceProject,
60
- target: `npm:${d}`,
61
- sourceFile: fileName,
62
- type: project_graph_1.DependencyType.static,
63
- };
64
- (0, project_graph_builder_1.validateDependency)(dependency, ctx);
65
- collectedDeps.push(dependency);
57
+ const externalNodeName = targetProjectLocator.findNpmProjectFromImport(d, fileName);
58
+ if (!externalNodeName) {
59
+ continue;
66
60
  }
67
- });
61
+ const dependency = {
62
+ source: sourceProject,
63
+ target: externalNodeName,
64
+ sourceFile: fileName,
65
+ type: project_graph_1.DependencyType.static,
66
+ };
67
+ (0, project_graph_builder_1.validateDependency)(dependency, ctx);
68
+ collectedDeps.push(dependency);
69
+ }
68
70
  }
69
71
  catch (e) {
70
72
  if (process.env.NX_VERBOSE_LOGGING === 'true') {
71
- console.log(e);
73
+ console.error(e);
72
74
  }
73
75
  }
74
76
  }
75
77
  function readDeps(packageJson) {
76
- return [
77
- ...Object.keys(packageJson?.dependencies ?? {}),
78
- ...Object.keys(packageJson?.devDependencies ?? {}),
79
- ...Object.keys(packageJson?.peerDependencies ?? {}),
80
- ...Object.keys(packageJson?.optionalDependencies ?? {}),
78
+ const deps = {};
79
+ /**
80
+ * We process dependencies in a rough order of increasing importance such that if a dependency is listed in multiple
81
+ * sections, the version listed under the "most important" one wins, with production dependencies being the most important.
82
+ */
83
+ const depType = [
84
+ 'optionalDependencies',
85
+ 'peerDependencies',
86
+ 'devDependencies',
87
+ 'dependencies',
81
88
  ];
89
+ for (const type of depType) {
90
+ for (const [depName, depVersion] of Object.entries(packageJson[type] || {})) {
91
+ deps[depName] = depVersion;
92
+ }
93
+ }
94
+ return deps;
82
95
  }
@@ -1,3 +1,4 @@
1
1
  import { CreateDependenciesContext } from '../../../../project-graph/plugins';
2
2
  import { RawProjectGraphDependency } from '../../../../project-graph/project-graph-builder';
3
- export declare function buildExplicitTypeScriptDependencies(ctx: CreateDependenciesContext): RawProjectGraphDependency[];
3
+ import { TargetProjectLocator } from './target-project-locator';
4
+ export declare function buildExplicitTypeScriptDependencies(ctx: CreateDependenciesContext, targetProjectLocator: TargetProjectLocator): RawProjectGraphDependency[];
@@ -1,18 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildExplicitTypeScriptDependencies = void 0;
4
- const target_project_locator_1 = require("./target-project-locator");
5
- const project_graph_1 = require("../../../../config/project-graph");
6
4
  const path_1 = require("path");
7
- const workspace_root_1 = require("../../../../utils/workspace-root");
8
- const path_2 = require("../../../../utils/path");
5
+ const project_graph_1 = require("../../../../config/project-graph");
9
6
  const project_graph_builder_1 = require("../../../../project-graph/project-graph-builder");
7
+ const path_2 = require("../../../../utils/path");
8
+ const workspace_root_1 = require("../../../../utils/workspace-root");
10
9
  function isRoot(projects, projectName) {
11
10
  return projects[projectName]?.root === '.';
12
11
  }
13
12
  function convertImportToDependency(importExpr, sourceFile, source, type, targetProjectLocator) {
14
- const target = targetProjectLocator.findProjectWithImport(importExpr, sourceFile) ??
15
- `npm:${importExpr}`;
13
+ const target = targetProjectLocator.findProjectFromImport(importExpr, sourceFile);
14
+ if (!target) {
15
+ return;
16
+ }
16
17
  return {
17
18
  source,
18
19
  target,
@@ -20,19 +21,7 @@ function convertImportToDependency(importExpr, sourceFile, source, type, targetP
20
21
  type,
21
22
  };
22
23
  }
23
- function buildExplicitTypeScriptDependencies(ctx) {
24
- // TODO: TargetProjectLocator is a public API, so we can't change the shape of it
25
- // We should eventually let it accept Record<string, ProjectConfiguration> s.t. we
26
- // don't have to reshape the CreateDependenciesContext here.
27
- const nodes = Object.fromEntries(Object.entries(ctx.projects).map(([key, config]) => [
28
- key,
29
- {
30
- name: key,
31
- type: null,
32
- data: config,
33
- },
34
- ]));
35
- const targetProjectLocator = new target_project_locator_1.TargetProjectLocator(nodes, ctx.externalNodes);
24
+ function buildExplicitTypeScriptDependencies(ctx, targetProjectLocator) {
36
25
  const res = [];
37
26
  const filesToProcess = {};
38
27
  const moduleExtensions = [
@@ -63,6 +52,9 @@ function buildExplicitTypeScriptDependencies(ctx) {
63
52
  const normalizedFilePath = (0, path_2.normalizePath)((0, path_1.relative)(workspace_root_1.workspaceRoot, file));
64
53
  for (const importExpr of staticImportExpressions) {
65
54
  const dependency = convertImportToDependency(importExpr, normalizedFilePath, sourceProject, project_graph_1.DependencyType.static, targetProjectLocator);
55
+ if (!dependency) {
56
+ continue;
57
+ }
66
58
  // TODO: These edges technically should be allowed but we need to figure out how to separate config files out from root
67
59
  if (isRoot(ctx.projects, dependency.source) ||
68
60
  !isRoot(ctx.projects, dependency.target)) {
@@ -71,6 +63,9 @@ function buildExplicitTypeScriptDependencies(ctx) {
71
63
  }
72
64
  for (const importExpr of dynamicImportExpressions) {
73
65
  const dependency = convertImportToDependency(importExpr, normalizedFilePath, sourceProject, project_graph_1.DependencyType.dynamic, targetProjectLocator);
66
+ if (!dependency) {
67
+ continue;
68
+ }
74
69
  // TODO: These edges technically should be allowed but we need to figure out how to separate config files out from root
75
70
  if (isRoot(ctx.projects, dependency.source) ||
76
71
  !isRoot(ctx.projects, dependency.target)) {
@@ -1,21 +1,37 @@
1
1
  import { ProjectGraphExternalNode, ProjectGraphProjectNode } from '../../../../config/project-graph';
2
+ /**
3
+ * The key is a combination of the package name and the workspace relative directory
4
+ * containing the file importing it e.g. `lodash__packages/my-lib`, the value is the
5
+ * resolved external node name from the project graph.
6
+ */
7
+ type NpmResolutionCache = Map<string, string | null>;
8
+ export declare function isBuiltinModuleImport(importExpr: string): boolean;
2
9
  export declare class TargetProjectLocator {
3
10
  private readonly nodes;
4
11
  private readonly externalNodes;
12
+ private readonly npmResolutionCache;
5
13
  private projectRootMappings;
6
14
  private npmProjects;
7
15
  private tsConfig;
8
16
  private paths;
9
17
  private typescriptResolutionCache;
10
- private npmResolutionCache;
11
- constructor(nodes: Record<string, ProjectGraphProjectNode>, externalNodes: Record<string, ProjectGraphExternalNode>);
18
+ constructor(nodes: Record<string, ProjectGraphProjectNode>, externalNodes?: Record<string, ProjectGraphExternalNode>, npmResolutionCache?: NpmResolutionCache);
12
19
  /**
13
- * Find a project based on its import
20
+ * Resolve any workspace or external project that matches the given import expression,
21
+ * originating from the given filePath.
14
22
  *
15
23
  * @param importExpr
16
24
  * @param filePath
17
25
  */
18
- findProjectWithImport(importExpr: string, filePath: string): string;
26
+ findProjectFromImport(importExpr: string, filePath: string): string;
27
+ /**
28
+ * Resolve any external project that matches the given import expression,
29
+ * relative to the given file path.
30
+ *
31
+ * @param importExpr
32
+ * @param projectRoot
33
+ */
34
+ findNpmProjectFromImport(importExpr: string, fromFilePath: string): string | null;
19
35
  /**
20
36
  * Return file paths matching the import relative to the repo root
21
37
  * @param normalizedImportExpr
@@ -24,9 +40,20 @@ export declare class TargetProjectLocator {
24
40
  findPaths(normalizedImportExpr: string): string[] | undefined;
25
41
  private resolveImportWithTypescript;
26
42
  private resolveImportWithRequire;
27
- private findNpmPackage;
28
43
  private findProjectOfResolvedModule;
29
44
  private getAbsolutePath;
30
45
  private getRootTsConfig;
31
46
  private findMatchingProjectFiles;
47
+ /**
48
+ * In many cases the package.json will be directly resolvable, so we try that first.
49
+ * If, however, package exports are used and the package.json is not defined, we will
50
+ * need to resolve the main entry point of the package and traverse upwards to find the
51
+ * package.json.
52
+ *
53
+ * In some cases, such as when multiple module formats are published, the resolved package.json
54
+ * might only contain the "type" field - no "name" or "version", so in such cases we keep traversing
55
+ * until we find a package.json that contains the "name" and "version" fields.
56
+ */
57
+ private readPackageJson;
32
58
  }
59
+ export {};
@@ -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][]>;
@@ -1,11 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runCreateNodesInParallel = exports.normalizeNxPlugin = exports.isNxPluginV1 = exports.isNxPluginV2 = void 0;
3
+ exports.createNodesFromFiles = exports.normalizeNxPlugin = exports.isNxPluginV1 = exports.isNxPluginV2 = void 0;
4
4
  const node_path_1 = require("node:path");
5
5
  const to_project_name_1 = require("../../config/to-project-name");
6
6
  const globs_1 = require("../../utils/globs");
7
7
  const error_types_1 = require("../error-types");
8
- const perf_hooks_1 = require("perf_hooks");
9
8
  function isNxPluginV2(plugin) {
10
9
  return 'createNodes' in plugin || 'createDependencies' in plugin;
11
10
  }
@@ -40,36 +39,24 @@ function normalizeNxPlugin(plugin) {
40
39
  return plugin;
41
40
  }
42
41
  exports.normalizeNxPlugin = normalizeNxPlugin;
43
- async function runCreateNodesInParallel(configFiles, plugin, options, context) {
44
- perf_hooks_1.performance.mark(`${plugin.name}:createNodes - start`);
45
- const errors = [];
42
+ async function createNodesFromFiles(createNodes, configFiles, options, context) {
46
43
  const results = [];
47
- const promises = configFiles.map(async (file) => {
44
+ const errors = [];
45
+ await Promise.all(configFiles.map(async (file) => {
48
46
  try {
49
- const value = await plugin.createNodes[1](file, options, context);
50
- if (value) {
51
- results.push({
52
- ...value,
53
- file,
54
- pluginName: plugin.name,
55
- });
56
- }
47
+ const value = await createNodes(file, options, {
48
+ ...context,
49
+ configFiles,
50
+ });
51
+ results.push([file, value]);
57
52
  }
58
53
  catch (e) {
59
- errors.push(new error_types_1.CreateNodesError({
60
- error: e,
61
- pluginName: plugin.name,
62
- file,
63
- }));
54
+ errors.push([file, e]);
64
55
  }
65
- });
66
- await Promise.all(promises).then(() => {
67
- perf_hooks_1.performance.mark(`${plugin.name}:createNodes - end`);
68
- perf_hooks_1.performance.measure(`${plugin.name}:createNodes`, `${plugin.name}:createNodes - start`, `${plugin.name}:createNodes - end`);
69
- });
56
+ }));
70
57
  if (errors.length > 0) {
71
- throw new error_types_1.AggregateCreateNodesError(plugin.name, errors, results);
58
+ throw new error_types_1.AggregateCreateNodesError(errors, results);
72
59
  }
73
60
  return results;
74
61
  }
75
- exports.runCreateNodesInParallel = runCreateNodesInParallel;
62
+ exports.createNodesFromFiles = createNodesFromFiles;
@@ -227,92 +227,35 @@ plugins) {
227
227
  const results = [];
228
228
  const errors = [];
229
229
  // We iterate over plugins first - this ensures that plugins specified first take precedence.
230
- for (const { name: pluginName, createNodes: createNodesTuple, include, exclude, } of plugins) {
230
+ for (const { createNodes: createNodesTuple, include, exclude, name: pluginName, } of plugins) {
231
231
  const [pattern, createNodes] = createNodesTuple ?? [];
232
232
  if (!pattern) {
233
233
  continue;
234
234
  }
235
- const matchingConfigFiles = [];
236
- for (const file of projectFiles) {
237
- if ((0, minimatch_1.minimatch)(file, pattern, { dot: true })) {
238
- if (include) {
239
- const included = include.some((includedPattern) => (0, minimatch_1.minimatch)(file, includedPattern, { dot: true }));
240
- if (!included) {
241
- continue;
242
- }
243
- }
244
- if (exclude) {
245
- const excluded = exclude.some((excludedPattern) => (0, minimatch_1.minimatch)(file, excludedPattern, { dot: true }));
246
- if (excluded) {
247
- continue;
248
- }
249
- }
250
- matchingConfigFiles.push(file);
251
- }
252
- }
235
+ const matchingConfigFiles = findMatchingConfigFiles(projectFiles, pattern, include, exclude);
253
236
  let r = createNodes(matchingConfigFiles, {
254
237
  nxJsonConfiguration: nxJson,
255
238
  workspaceRoot: root,
256
- configFiles: matchingConfigFiles,
257
239
  }).catch((e) => {
258
- if ((0, error_types_1.isAggregateCreateNodesError)(e)) {
259
- errors.push(...e.errors);
260
- return e.partialResults;
261
- }
262
- else {
263
- throw e;
264
- }
240
+ const errorBodyLines = [
241
+ `An error occurred while processing files for the ${pluginName} plugin.`,
242
+ ];
243
+ const error = (0, error_types_1.isAggregateCreateNodesError)(e)
244
+ ? // This is an expected error if something goes wrong while processing files.
245
+ e
246
+ : // This represents a single plugin erroring out with a hard error.
247
+ new error_types_1.AggregateCreateNodesError([[null, e]], []);
248
+ errorBodyLines.push(...error.errors.map(([file, e]) => ` - ${file}: ${e.message}`));
249
+ error.message = errorBodyLines.join('\n');
250
+ // This represents a single plugin erroring out with a hard error.
251
+ errors.push(error);
252
+ // The plugin didn't return partial results, so we return an empty array.
253
+ return error.partialResults.map((r) => [pluginName, r[0], r[1]]);
265
254
  });
266
255
  results.push(r);
267
256
  }
268
257
  return Promise.all(results).then((results) => {
269
- perf_hooks_1.performance.mark('createNodes:merge - start');
270
- const projectRootMap = {};
271
- const externalNodes = {};
272
- const configurationSourceMaps = {};
273
- for (const result of results.flat()) {
274
- const { projects: projectNodes, externalNodes: pluginExternalNodes, file, pluginName, } = result;
275
- const sourceInfo = [file, pluginName];
276
- if (result[symbols_1.OVERRIDE_SOURCE_FILE]) {
277
- sourceInfo[0] = result[symbols_1.OVERRIDE_SOURCE_FILE];
278
- }
279
- for (const node in projectNodes) {
280
- // Handles `{projects: {'libs/foo': undefined}}`.
281
- if (!projectNodes[node]) {
282
- continue;
283
- }
284
- const project = {
285
- root: node,
286
- ...projectNodes[node],
287
- };
288
- try {
289
- mergeProjectConfigurationIntoRootMap(projectRootMap, project, configurationSourceMaps, sourceInfo);
290
- }
291
- catch (error) {
292
- errors.push(new error_types_1.MergeNodesError({
293
- file,
294
- pluginName,
295
- error,
296
- }));
297
- }
298
- }
299
- Object.assign(externalNodes, pluginExternalNodes);
300
- }
301
- try {
302
- validateAndNormalizeProjectRootMap(projectRootMap);
303
- }
304
- catch (e) {
305
- if ((0, error_types_1.isProjectsWithNoNameError)(e) ||
306
- (0, error_types_1.isMultipleProjectsWithSameNameError)(e)) {
307
- errors.push(e);
308
- }
309
- else {
310
- throw e;
311
- }
312
- }
313
- const rootMap = createRootMap(projectRootMap);
314
- perf_hooks_1.performance.mark('createNodes:merge - end');
315
- perf_hooks_1.performance.measure('createNodes:merge', 'createNodes:merge - start', 'createNodes:merge - end');
258
+ const { projectRootMap, externalNodes, rootMap, configurationSourceMaps } = mergeCreateNodesResults(results, errors);
316
259
  perf_hooks_1.performance.mark('build-project-configs:end');
317
260
  perf_hooks_1.performance.measure('build-project-configs', 'build-project-configs:start', 'build-project-configs:end');
318
261
  if (errors.length === 0) {
@@ -336,6 +279,78 @@ plugins) {
336
279
  });
337
280
  }
338
281
  exports.createProjectConfigurations = createProjectConfigurations;
282
+ function mergeCreateNodesResults(results, errors) {
283
+ perf_hooks_1.performance.mark('createNodes:merge - start');
284
+ const projectRootMap = {};
285
+ const externalNodes = {};
286
+ const configurationSourceMaps = {};
287
+ for (const result of results.flat()) {
288
+ const [file, pluginName, nodes] = result;
289
+ const { projects: projectNodes, externalNodes: pluginExternalNodes } = nodes;
290
+ const sourceInfo = [file, pluginName];
291
+ if (result[symbols_1.OVERRIDE_SOURCE_FILE]) {
292
+ sourceInfo[0] = result[symbols_1.OVERRIDE_SOURCE_FILE];
293
+ }
294
+ for (const node in projectNodes) {
295
+ // Handles `{projects: {'libs/foo': undefined}}`.
296
+ if (!projectNodes[node]) {
297
+ continue;
298
+ }
299
+ const project = {
300
+ root: node,
301
+ ...projectNodes[node],
302
+ };
303
+ try {
304
+ mergeProjectConfigurationIntoRootMap(projectRootMap, project, configurationSourceMaps, sourceInfo);
305
+ }
306
+ catch (error) {
307
+ errors.push(new error_types_1.MergeNodesError({
308
+ file,
309
+ pluginName,
310
+ error,
311
+ }));
312
+ }
313
+ }
314
+ Object.assign(externalNodes, pluginExternalNodes);
315
+ }
316
+ try {
317
+ validateAndNormalizeProjectRootMap(projectRootMap);
318
+ }
319
+ catch (e) {
320
+ if ((0, error_types_1.isProjectsWithNoNameError)(e) ||
321
+ (0, error_types_1.isMultipleProjectsWithSameNameError)(e)) {
322
+ errors.push(e);
323
+ }
324
+ else {
325
+ throw e;
326
+ }
327
+ }
328
+ const rootMap = createRootMap(projectRootMap);
329
+ perf_hooks_1.performance.mark('createNodes:merge - end');
330
+ perf_hooks_1.performance.measure('createNodes:merge', 'createNodes:merge - start', 'createNodes:merge - end');
331
+ return { projectRootMap, externalNodes, rootMap, configurationSourceMaps };
332
+ }
333
+ function findMatchingConfigFiles(projectFiles, pattern, include, exclude) {
334
+ const matchingConfigFiles = [];
335
+ for (const file of projectFiles) {
336
+ if ((0, minimatch_1.minimatch)(file, pattern, { dot: true })) {
337
+ if (include) {
338
+ const included = include.some((includedPattern) => (0, minimatch_1.minimatch)(file, includedPattern, { dot: true }));
339
+ if (!included) {
340
+ continue;
341
+ }
342
+ }
343
+ if (exclude) {
344
+ const excluded = exclude.some((excludedPattern) => (0, minimatch_1.minimatch)(file, excludedPattern, { dot: true }));
345
+ if (excluded) {
346
+ continue;
347
+ }
348
+ }
349
+ matchingConfigFiles.push(file);
350
+ }
351
+ }
352
+ return matchingConfigFiles;
353
+ }
339
354
  function readProjectConfigurationsFromRootMap(projectRootMap) {
340
355
  const projects = {};
341
356
  // If there are projects that have the same name, that is an error.