nx 23.1.1 → 23.1.3

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 (128) hide show
  1. package/dist/release/changelog-renderer/index.js +4 -5
  2. package/dist/release/index.d.ts +1 -1
  3. package/dist/src/command-line/add/add.js +5 -0
  4. package/dist/src/command-line/affected/affected.js +1 -0
  5. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
  6. package/dist/src/command-line/import/import.js +1 -2
  7. package/dist/src/command-line/init/init-v2.js +4 -5
  8. package/dist/src/command-line/migrate/migrate.d.ts +4 -0
  9. package/dist/src/command-line/migrate/migrate.js +89 -26
  10. package/dist/src/command-line/release/changelog.js +5 -0
  11. package/dist/src/command-line/release/index.d.ts +1 -1
  12. package/dist/src/command-line/release/utils/git.js +1 -1
  13. package/dist/src/command-line/release/utils/release-graph.d.ts +17 -0
  14. package/dist/src/command-line/release/utils/release-graph.js +71 -19
  15. package/dist/src/command-line/release/utils/remote-release-clients/github.js +6 -0
  16. package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
  17. package/dist/src/command-line/release/version/release-group-processor.js +80 -6
  18. package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
  19. package/dist/src/command-line/release/version/version-actions.js +9 -1
  20. package/dist/src/command-line/release/version.js +9 -5
  21. package/dist/src/command-line/reset/reset.js +6 -1
  22. package/dist/src/command-line/run/run-one.js +1 -0
  23. package/dist/src/command-line/run-many/run-many.js +1 -0
  24. package/dist/src/command-line/watch/watch.js +13 -0
  25. package/dist/src/core/graph/main.js +1 -1
  26. package/dist/src/core/graph/styles.js +1 -1
  27. package/dist/src/daemon/client/client.d.ts +52 -1
  28. package/dist/src/daemon/client/client.js +160 -36
  29. package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
  30. package/dist/src/daemon/client/daemon-environment.js +110 -2
  31. package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
  32. package/dist/src/daemon/message-types/daemon-message.d.ts +33 -0
  33. package/dist/src/daemon/message-types/daemon-message.js +48 -0
  34. package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
  35. package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
  36. package/dist/src/daemon/server/latest-nx.js +5 -2
  37. package/dist/src/daemon/server/server.js +24 -0
  38. package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
  39. package/dist/src/daemon/server/shutdown-utils.js +11 -4
  40. package/dist/src/daemon/socket-utils.d.ts +1 -0
  41. package/dist/src/daemon/socket-utils.js +24 -3
  42. package/dist/src/daemon/tmp-dir.d.ts +43 -3
  43. package/dist/src/daemon/tmp-dir.js +437 -15
  44. package/dist/src/devkit-internals.d.ts +94 -4
  45. package/dist/src/devkit-internals.js +194 -5
  46. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
  47. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
  48. package/dist/src/generators/tree.d.ts +4 -2
  49. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
  50. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
  51. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
  52. package/dist/src/native/index.d.ts +10 -0
  53. package/dist/src/native/index.js +71 -28
  54. package/dist/src/native/native-bindings.js +1 -0
  55. package/dist/src/native/native-file-cache-location.d.ts +17 -1
  56. package/dist/src/native/native-file-cache-location.js +86 -12
  57. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  58. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  59. package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
  60. package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
  61. package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
  62. package/dist/src/plugins/js/lock-file/pnpm-parser.js +60 -0
  63. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +14 -8
  64. package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
  65. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +2 -0
  66. package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
  67. package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
  68. package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
  69. package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
  70. package/dist/src/project-graph/error-types.d.ts +12 -1
  71. package/dist/src/project-graph/error-types.js +27 -2
  72. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
  73. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +18 -1
  74. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
  75. package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
  76. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
  77. package/dist/src/project-graph/project-graph.js +20 -0
  78. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +5 -1
  79. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +133 -3
  80. package/dist/src/tasks-runner/task-env.js +5 -2
  81. package/dist/src/tasks-runner/utils.js +2 -6
  82. package/dist/src/utils/assert-workspace-validity.js +7 -2
  83. package/dist/src/utils/fileutils.d.ts +5 -0
  84. package/dist/src/utils/fileutils.js +7 -2
  85. package/dist/src/utils/git-worktrees.d.ts +30 -0
  86. package/dist/src/utils/git-worktrees.js +212 -0
  87. package/dist/src/utils/ignore.d.ts +128 -1
  88. package/dist/src/utils/ignore.js +182 -12
  89. package/dist/src/utils/long-running-target.d.ts +10 -0
  90. package/dist/src/utils/long-running-target.js +19 -0
  91. package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
  92. package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
  93. package/dist/src/utils/min-release-age/packument.js +1 -1
  94. package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
  95. package/dist/src/utils/nx-tmp-dir.js +40 -0
  96. package/dist/src/utils/owned-private-dir.d.ts +132 -0
  97. package/dist/src/utils/owned-private-dir.js +323 -0
  98. package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
  99. package/dist/src/utils/package-manager-config/bunfig.js +49 -0
  100. package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
  101. package/dist/src/utils/package-manager-config/npmrc.js +124 -0
  102. package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
  103. package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
  104. package/dist/src/utils/package-manager.d.ts +21 -2
  105. package/dist/src/utils/package-manager.js +244 -47
  106. package/dist/src/utils/provenance.js +6 -14
  107. package/dist/src/utils/registry-config/bun.d.ts +2 -0
  108. package/dist/src/utils/registry-config/bun.js +257 -0
  109. package/dist/src/utils/registry-config/index.d.ts +13 -0
  110. package/dist/src/utils/registry-config/index.js +102 -0
  111. package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
  112. package/dist/src/utils/registry-config/pnpm.js +1551 -0
  113. package/dist/src/utils/registry-config/utils.d.ts +195 -0
  114. package/dist/src/utils/registry-config/utils.js +496 -0
  115. package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
  116. package/dist/src/utils/registry-config/yarn-berry.js +612 -0
  117. package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
  118. package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
  119. package/dist/src/utils/safe-spawn.d.ts +24 -0
  120. package/dist/src/utils/safe-spawn.js +104 -0
  121. package/dist/src/utils/shell-quoting.d.ts +15 -0
  122. package/dist/src/utils/shell-quoting.js +35 -0
  123. package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
  124. package/dist/src/utils/wait-for-socket-connection.js +14 -8
  125. package/migrations.json +6 -0
  126. package/package.json +12 -12
  127. package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
  128. package/dist/src/utils/min-release-age/npmrc.js +0 -45
@@ -13,6 +13,7 @@ const getTouchedNpmPackages = (touchedFiles, _, nxJson, packageJson, projectGrap
13
13
  let touched = [];
14
14
  const changes = packageJsonChange.getChanges();
15
15
  const npmPackages = Object.values(projectGraph.externalNodes);
16
+ let packagesByName;
16
17
  const missingTouchedNpmPackages = [];
17
18
  for (const c of changes) {
18
19
  if ((0, json_diff_1.isJsonChange)(c) &&
@@ -53,26 +54,20 @@ const getTouchedNpmPackages = (touchedFiles, _, nxJson, packageJson, projectGrap
53
54
  (c.path[0] === 'overrides' ||
54
55
  c.path[0] === 'resolutions' ||
55
56
  (c.path[0] === 'pnpm' && c.path[1] === 'overrides'))) {
56
- // Changes to overrides, resolutions, or pnpm.overrides
57
- // Find which package was changed and mark projects that depend on it as affected
58
- const packageName = c.path[0] === 'pnpm' ? c.path[2] : c.path[1];
59
- if (packageName) {
60
- // Look for the npm package in external nodes
61
- let npmPackage = npmPackages.find((pkg) => pkg.data.packageName === packageName);
62
- if (npmPackage) {
63
- touched.push(npmPackage.name);
64
- // If it's a global package, all projects are affected
65
- if ('packageName' in npmPackage.data &&
66
- globalPackages.has(npmPackage.data.packageName)) {
67
- return Object.keys(projectGraph.nodes);
68
- }
69
- }
70
- else {
71
- // If the package isn't found in external nodes, it might affect all projects
72
- // since overrides can affect transitive dependencies
73
- return Object.keys(projectGraph.nodes);
74
- }
57
+ const packageSelector = getPackageSelector(c);
58
+ if (!packageSelector)
59
+ continue;
60
+ packagesByName ??= groupPackagesByName(npmPackages);
61
+ const matchingNpmPackages = findPackagesForSelector(packageSelector, packagesByName, c.path[0] === 'pnpm');
62
+ // An unresolved selector can still target a transitive dependency,
63
+ // so fall back to marking every project affected.
64
+ if (!matchingNpmPackages.length) {
65
+ return Object.keys(projectGraph.nodes);
75
66
  }
67
+ if (matchingNpmPackages.some((pkg) => globalPackages.has(pkg.data.packageName))) {
68
+ return Object.keys(projectGraph.nodes);
69
+ }
70
+ touched.push(...matchingNpmPackages.map((pkg) => pkg.name));
76
71
  }
77
72
  else if ((0, file_utils_1.isWholeFileChange)(c)) {
78
73
  // Whole file was touched, so all npm packages are touched.
@@ -83,9 +78,45 @@ const getTouchedNpmPackages = (touchedFiles, _, nxJson, packageJson, projectGrap
83
78
  if (missingTouchedNpmPackages.length) {
84
79
  logger_1.logger.warn(`The affected projects might have not been identified properly. The package(s) ${missingTouchedNpmPackages.join(', ')} were not found. Please open an issue in GitHub including the package.json file.`);
85
80
  }
86
- return touched;
81
+ return [...new Set(touched)];
87
82
  };
88
83
  exports.getTouchedNpmPackages = getTouchedNpmPackages;
84
+ function getPackageSelector(change) {
85
+ if (typeof change.value.lhs !== 'string' &&
86
+ typeof change.value.rhs !== 'string') {
87
+ return;
88
+ }
89
+ const selectorIndex = change.path[0] === 'pnpm' ? 2 : change.path.length - 1;
90
+ const selector = change.path[selectorIndex];
91
+ return selector === '.' ? change.path[selectorIndex - 1] : selector;
92
+ }
93
+ function groupPackagesByName(npmPackages) {
94
+ const packagesByName = new Map();
95
+ for (const pkg of npmPackages) {
96
+ const packageName = pkg.data.packageName;
97
+ if (!packageName)
98
+ continue;
99
+ const packages = packagesByName.get(packageName);
100
+ if (packages) {
101
+ packages.push(pkg);
102
+ }
103
+ else {
104
+ packagesByName.set(packageName, [pkg]);
105
+ }
106
+ }
107
+ return packagesByName;
108
+ }
109
+ function findPackagesForSelector(selector, packagesByName, isPnpmOverride) {
110
+ if (isPnpmOverride) {
111
+ // Pnpm does not treat `>` as a parent delimiter when it starts a range.
112
+ const parentDelimiterIndex = selector.search(/[^ |@]>/);
113
+ if (parentDelimiterIndex !== -1) {
114
+ selector = selector.slice(parentDelimiterIndex + 2);
115
+ }
116
+ }
117
+ const packageName = selector.match(/(?:^|\/)(@[^/@>\s]+\/[^/@>\s]+|[^/@>\s]+)(?:@[^/]*)?$/)?.[1];
118
+ return packageName ? (packagesByName.get(packageName) ?? []) : [];
119
+ }
89
120
  function getGlobalPackages(plugins) {
90
121
  return (plugins ?? [])
91
122
  .map((p) => (0, get_package_name_from_import_path_1.getPackageNameFromImportPath)(typeof p === 'string' ? p : p.plugin))
@@ -8,6 +8,7 @@ const semver_1 = require("semver");
8
8
  const find_project_for_path_1 = require("../../../../project-graph/utils/find-project-for-path");
9
9
  const fileutils_1 = require("../../../../utils/fileutils");
10
10
  const get_package_name_from_import_path_1 = require("../../../../utils/get-package-name-from-import-path");
11
+ const path_1 = require("../../../../utils/path");
11
12
  const workspace_root_1 = require("../../../../utils/workspace-root");
12
13
  const packages_1 = require("../../utils/packages");
13
14
  const resolve_relative_to_dir_1 = require("../../utils/resolve-relative-to-dir");
@@ -346,6 +347,7 @@ class TargetProjectLocator {
346
347
  }));
347
348
  }
348
349
  findProjectOfResolvedModule(resolvedModule) {
350
+ resolvedModule = (0, path_1.normalizePath)(resolvedModule);
349
351
  if (resolvedModule.startsWith('node_modules/') ||
350
352
  resolvedModule.includes('/node_modules/')) {
351
353
  return undefined;
@@ -7,5 +7,5 @@ export interface AffectedProjectGraphContext {
7
7
  touchedProjects: string[];
8
8
  }
9
9
  export interface TouchedProjectLocator<T extends Change = Change> {
10
- (fileChanges: FileChange<T>[], projectGraphNodes?: Record<string, ProjectGraphProjectNode>, nxJson?: NxJsonConfiguration<any>, packageJson?: any, projectGraph?: ProjectGraph): string[] | Promise<string[]>;
10
+ (fileChanges: FileChange<T>[], projectGraphNodes?: Record<string, ProjectGraphProjectNode>, nxJson?: NxJsonConfiguration<any>, packageJson?: any, projectGraph?: ProjectGraph, projectDeletionAffectsAllProjects?: boolean): string[] | Promise<string[]>;
11
11
  }
@@ -1,4 +1,4 @@
1
1
  import { FileChange } from '../file-utils';
2
2
  import { NxJsonConfiguration } from '../../config/nx-json';
3
3
  import { ProjectGraph } from '../../config/project-graph';
4
- export declare function filterAffected(graph: ProjectGraph, touchedFiles: FileChange[], nxJson?: NxJsonConfiguration, packageJson?: any): Promise<ProjectGraph>;
4
+ export declare function filterAffected(graph: ProjectGraph, touchedFiles: FileChange[], nxJson?: NxJsonConfiguration, packageJson?: any, projectDeletionAffectsAllProjects?: boolean): Promise<ProjectGraph>;
@@ -7,7 +7,7 @@ const touched_projects_1 = require("../../plugins/js/project-graph/affected/touc
7
7
  const operators_1 = require("../operators");
8
8
  const configuration_1 = require("../../config/configuration");
9
9
  const project_glob_changes_1 = require("./locators/project-glob-changes");
10
- async function filterAffected(graph, touchedFiles, nxJson = (0, configuration_1.readNxJson)(), packageJson = (0, file_utils_1.readPackageJson)()) {
10
+ async function filterAffected(graph, touchedFiles, nxJson = (0, configuration_1.readNxJson)(), packageJson = (0, file_utils_1.readPackageJson)(), projectDeletionAffectsAllProjects = true) {
11
11
  // Additional affected logic should be in this array.
12
12
  const touchedProjectLocators = [
13
13
  workspace_projects_1.getTouchedProjects,
@@ -18,7 +18,7 @@ async function filterAffected(graph, touchedFiles, nxJson = (0, configuration_1.
18
18
  const touchedProjects = [];
19
19
  for (const locator of touchedProjectLocators) {
20
20
  performance.mark(locator.name + ':start');
21
- const projects = await locator(touchedFiles, graph.nodes, nxJson, packageJson, graph);
21
+ const projects = await locator(touchedFiles, graph.nodes, nxJson, packageJson, graph, projectDeletionAffectsAllProjects);
22
22
  performance.mark(locator.name + ':end');
23
23
  performance.measure(locator.name, locator.name + ':start', locator.name + ':end');
24
24
  touchedProjects.push(...projects);
@@ -9,7 +9,7 @@ const fs_1 = require("fs");
9
9
  const retrieve_workspace_files_1 = require("../../utils/retrieve-workspace-files");
10
10
  const globs_1 = require("../../../utils/globs");
11
11
  const get_plugins_1 = require("../../plugins/get-plugins");
12
- const getTouchedProjectsFromProjectGlobChanges = async (touchedFiles, projectGraphNodes) => {
12
+ const getTouchedProjectsFromProjectGlobChanges = async (touchedFiles, projectGraphNodes, _nxJson, _packageJson, _projectGraph, projectDeletionAffectsAllProjects = true) => {
13
13
  const globPattern = await (async () => {
14
14
  // TODO: We need a quicker way to get patterns that should not
15
15
  // require starting up plugin workers
@@ -33,7 +33,10 @@ const getTouchedProjectsFromProjectGlobChanges = async (touchedFiles, projectGra
33
33
  // If the file no longer exists on disk, then it was deleted
34
34
  if (!(0, fs_1.existsSync)((0, path_1.join)(workspace_root_1.workspaceRoot, touchedFile.file))) {
35
35
  // If any project has been deleted, we must assume all projects were affected
36
- return Object.keys(projectGraphNodes);
36
+ if (projectDeletionAffectsAllProjects) {
37
+ return Object.keys(projectGraphNodes);
38
+ }
39
+ continue;
37
40
  }
38
41
  // Modified project config files are under a project's root, and implicitly
39
42
  // mark it as affected. Thus, we don't need to handle it here.
@@ -3,6 +3,7 @@ import { ProjectConfiguration } from '../config/workspace-json-project-json';
3
3
  import { CreateNodesFunction } from './plugins/public-api';
4
4
  import { ConfigurationResult } from './utils/project-configuration-utils';
5
5
  import type { ConfigurationSourceMaps } from './utils/project-configuration/source-maps';
6
+ import type { WorktreeConflictAdvice } from '../utils/git-worktrees';
6
7
  export type ProjectGraphErrorTypes = AggregateCreateNodesError | MergeNodesError | CreateMetadataError | ProjectsWithNoNameError | MultipleProjectsWithSameNameError | ProcessDependenciesError | WorkspaceValidityError;
7
8
  export declare class StaleProjectGraphCacheError extends Error {
8
9
  constructor();
@@ -29,7 +30,17 @@ export declare class ProjectGraphError extends Error {
29
30
  export declare class MultipleProjectsWithSameNameError extends Error {
30
31
  conflicts: Map<string, string[]>;
31
32
  projects: Record<string, ProjectConfiguration>;
32
- constructor(conflicts: Map<string, string[]>, projects: Record<string, ProjectConfiguration>);
33
+ /**
34
+ * Set when some of the duplicates come from git worktrees nested in the
35
+ * workspace, which is a different fix than renaming them.
36
+ */
37
+ worktreeAdvice?: WorktreeConflictAdvice;
38
+ constructor(conflicts: Map<string, string[]>, projects: Record<string, ProjectConfiguration>,
39
+ /**
40
+ * Set when some of the duplicates come from git worktrees nested in the
41
+ * workspace, which is a different fix than renaming them.
42
+ */
43
+ worktreeAdvice?: WorktreeConflictAdvice);
33
44
  }
34
45
  export declare class ProjectWithExistingNameError extends Error {
35
46
  projectName: string;
@@ -92,19 +92,44 @@ class ProjectGraphError extends Error {
92
92
  exports.ProjectGraphError = ProjectGraphError;
93
93
  _ProjectGraphError_partialProjectGraph = new WeakMap(), _ProjectGraphError_partialSourceMaps = new WeakMap();
94
94
  class MultipleProjectsWithSameNameError extends Error {
95
- constructor(conflicts, projects) {
95
+ constructor(conflicts, projects,
96
+ /**
97
+ * Set when some of the duplicates come from git worktrees nested in the
98
+ * workspace, which is a different fix than renaming them.
99
+ */
100
+ worktreeAdvice) {
96
101
  super([
97
102
  `The following projects are defined in multiple locations:`,
98
103
  ...Array.from(conflicts.entries()).map(([project, roots]) => [`- ${project}: `, ...roots.map((r) => ` - ${r}`)].join('\n')),
99
104
  '',
100
- "To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.",
105
+ ...(worktreeAdvice?.ignoreTargets.length
106
+ ? [
107
+ 'Some of these are inside git worktrees nested in this workspace. A worktree is a full checkout, so every project in it collides with the one it was checked out from.',
108
+ '',
109
+ // Which `.gitignore` matters: a leading slash anchors to the
110
+ // directory holding the file, and these paths are relative to
111
+ // the workspace, which is not always the repository root.
112
+ 'To fix those, add the following to the .gitignore in the workspace root:',
113
+ ...worktreeAdvice.ignoreTargets.map((target) => ` ${target}`),
114
+ // Ignoring the worktrees settles only the duplicates they
115
+ // explain; anything left is an ordinary name collision and still
116
+ // needs the ordinary answer.
117
+ ...(worktreeAdvice.explainsAllConflicts
118
+ ? []
119
+ : ['', `The rest are not from worktrees. ${RENAME_ADVICE}`]),
120
+ ]
121
+ : [
122
+ `To fix this, ${RENAME_ADVICE[0].toLowerCase()}${RENAME_ADVICE.slice(1)}`,
123
+ ]),
101
124
  ].join('\n'));
102
125
  this.conflicts = conflicts;
103
126
  this.projects = projects;
127
+ this.worktreeAdvice = worktreeAdvice;
104
128
  this.name = this.constructor.name;
105
129
  }
106
130
  }
107
131
  exports.MultipleProjectsWithSameNameError = MultipleProjectsWithSameNameError;
132
+ const RENAME_ADVICE = "Set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.";
108
133
  class ProjectWithExistingNameError extends Error {
109
134
  constructor(projectName, projectRoot) {
110
135
  super(`The project "${projectName}" is defined in multiple locations.`);
@@ -60,3 +60,4 @@ export declare class IsolatedPlugin implements LoadedNxPlugin {
60
60
  shutdown(): void;
61
61
  private registerProcessMetrics;
62
62
  }
63
+ export declare function getPluginWorkerSocketId(): string;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.IsolatedPlugin = void 0;
4
+ exports.getPluginWorkerSocketId = getPluginWorkerSocketId;
4
5
  const child_process_1 = require("child_process");
6
+ const crypto_1 = require("crypto");
5
7
  const net_1 = require("net");
6
8
  const path = require("path");
7
9
  const logger_1 = require("../../../daemon/logger");
@@ -12,6 +14,7 @@ const installation_directory_1 = require("../../../utils/installation-directory"
12
14
  const logger_2 = require("../../../utils/logger");
13
15
  const progress_topics_1 = require("../../../utils/progress-topics");
14
16
  const wait_for_socket_connection_1 = require("../../../utils/wait-for-socket-connection");
17
+ const workspace_root_1 = require("../../../utils/workspace-root");
15
18
  const resolve_plugin_1 = require("../resolve-plugin");
16
19
  const messaging_1 = require("./messaging");
17
20
  const plugin_lifecycle_manager_1 = require("./plugin-lifecycle-manager");
@@ -336,6 +339,10 @@ class IsolatedPlugin {
336
339
  exports.IsolatedPlugin = IsolatedPlugin;
337
340
  // --- Worker Spawning Utilities ---
338
341
  global.nxPluginWorkerCount ??= 0;
342
+ function getPluginWorkerSocketId() {
343
+ const counter = global.nxPluginWorkerCount++;
344
+ return `${process.pid}-${counter.toString(36)}-${(0, crypto_1.randomBytes)(4).toString('hex')}`;
345
+ }
339
346
  async function startPluginWorker(name) {
340
347
  performance.mark(`start-plugin-worker:${name}`);
341
348
  const isWorkerTypescript = path.extname(__filename) === '.ts';
@@ -352,7 +359,14 @@ async function startPluginWorker(name) {
352
359
  }
353
360
  : {}),
354
361
  };
355
- const ipcPath = (0, socket_utils_1.getPluginOsSocketPath)([process.pid, global.nxPluginWorkerCount++, performance.now()].join('-'));
362
+ // Keep the host pid readable for diagnostics, the counter collision-free
363
+ // within this process, and entropy for a reused pid across process runs. The
364
+ // suffix is a base36 counter plus four random bytes: eleven characters after
365
+ // the pid until the counter passes 36, and fixed-length where the
366
+ // `performance.now()` stamp it replaced varied with process uptime. That
367
+ // keeps the socket path inside the 95-character budget `assertValidSocketPath`
368
+ // enforces.
369
+ const ipcPath = (0, socket_utils_1.getPluginOsSocketPath)(getPluginWorkerSocketId());
356
370
  const worker = (0, child_process_1.spawn)(process.execPath, [
357
371
  // Spawn the worker with the same resolve conditions Nx uses for plugin
358
372
  // entries so the plugin's transitive workspace imports resolve to source.
@@ -361,6 +375,9 @@ async function startPluginWorker(name) {
361
375
  workerPath,
362
376
  ipcPath,
363
377
  name,
378
+ // The host's root. The worker validates against this rather than re-resolving,
379
+ // so the two agree by construction.
380
+ workspace_root_1.workspaceRoot,
364
381
  ], {
365
382
  stdio: ['ignore', 'pipe', 'pipe'],
366
383
  env,
@@ -107,10 +107,23 @@ type PluginMessageDefs = DefineMessages<{
107
107
  };
108
108
  };
109
109
  }>;
110
+ /**
111
+ * The sender's workspace root. Only the *worker* asserts on it, for host→worker
112
+ * messages; results and notifications carry the field but the host does not
113
+ * check it. The daemon channel is checked in the same one direction — the
114
+ * server validates inbound client messages and the client does not validate
115
+ * responses — so the two are alike rather than one being weaker.
116
+ * Optional and stamped centrally in `sendMessageOverSocket`, so the many
117
+ * message constructors need not set it; undefined is treated as not foreign,
118
+ * which is why this detects a shared socket directory rather than an attacker.
119
+ */
120
+ export type WorkspaceStampedMessage = {
121
+ workspaceRoot?: string;
122
+ };
110
123
  /** Union of all plugin worker message types */
111
- export type PluginWorkerMessage = AllMessages<PluginMessageDefs>;
124
+ export type PluginWorkerMessage = AllMessages<PluginMessageDefs> & WorkspaceStampedMessage;
112
125
  /** Union of all plugin worker result types */
113
- export type PluginWorkerResult = AllResults<PluginMessageDefs>;
126
+ export type PluginWorkerResult = AllResults<PluginMessageDefs> & WorkspaceStampedMessage;
114
127
  /** Result type for the load message */
115
128
  export type PluginWorkerLoadResult = ResultOf<PluginMessageDefs, 'load'>;
116
129
  /**
@@ -122,7 +135,7 @@ export type PluginWorkerEmitLogNotification = {
122
135
  type: 'emitLog';
123
136
  level: 'log' | 'warn' | 'error';
124
137
  message: string;
125
- };
138
+ } & WorkspaceStampedMessage;
126
139
  export type PluginWorkerNotification = PluginWorkerEmitLogNotification;
127
140
  export declare function isPluginWorkerNotification(message: Serializable): message is PluginWorkerNotification;
128
141
  export declare function isPluginWorkerMessage(message: Serializable): message is PluginWorkerMessage;
@@ -7,6 +7,7 @@ exports.consumeMessage = consumeMessage;
7
7
  exports.sendMessageOverSocket = sendMessageOverSocket;
8
8
  const socket_utils_1 = require("../../../daemon/socket-utils");
9
9
  const consume_messages_from_socket_1 = require("../../../utils/consume-messages-from-socket");
10
+ const workspace_root_1 = require("../../../utils/workspace-root");
10
11
  const NOTIFICATION_TYPES = [
11
12
  'emitLog',
12
13
  ];
@@ -81,6 +82,8 @@ async function consumeMessage(socket, raw, handlers) {
81
82
  * Sends a message over the socket with proper formatting.
82
83
  */
83
84
  function sendMessageOverSocket(socket, message) {
85
+ // Stamped here rather than in each constructor, mirroring the daemon.
86
+ message.workspaceRoot = workspace_root_1.workspaceRoot;
84
87
  socket.write((0, socket_utils_1.serialize)(message));
85
88
  socket.write(consume_messages_from_socket_1.MESSAGE_END_SEQ);
86
89
  }
@@ -7,6 +7,7 @@ node_perf_hooks_1.performance.mark(`plugin worker ${process.pid} code loading --
7
7
  const consume_messages_from_socket_1 = require("../../../utils/consume-messages-from-socket");
8
8
  const logger_1 = require("../../../utils/logger");
9
9
  const serializable_error_1 = require("../../../utils/serializable-error");
10
+ const daemon_message_1 = require("../../../daemon/message-types/daemon-message");
10
11
  const messaging_1 = require("./messaging");
11
12
  const worker_streaming_1 = require("./worker-streaming");
12
13
  const fs_1 = require("fs");
@@ -23,6 +24,19 @@ global.NX_PLUGIN_WORKER = true;
23
24
  let plugin;
24
25
  const socketPath = process.argv[2];
25
26
  const expectedPluginName = process.argv[3];
27
+ // The host's root, passed explicitly rather than re-resolved: a host that set
28
+ // its root at runtime would resolve a different one here and drop every
29
+ // legitimate message as foreign.
30
+ const hostWorkspaceRoot = process.argv[4];
31
+ // Positional, so inserting an argument host-side shifts all of them — and the
32
+ // symptom is silent and total: an undefined hostWorkspaceRoot makes every
33
+ // message look foreign while the host waits out its plugin timeout.
34
+ if (!socketPath || !expectedPluginName || !hostWorkspaceRoot) {
35
+ console.error(`[plugin-worker] started with an incomplete argument list ` +
36
+ `(socketPath=${socketPath}, pluginName=${expectedPluginName}, hostWorkspaceRoot=${hostWorkspaceRoot}). ` +
37
+ `This is an Nx bug — please report it at https://github.com/nrwl/nx/issues.`);
38
+ process.exit(1);
39
+ }
26
40
  const CONNECT_TIMEOUT_MS = 30_000;
27
41
  let connectErrorTimeout = setErrorTimeout(CONNECT_TIMEOUT_MS, `The plugin worker for ${expectedPluginName} is exiting as it was not connected to within ${CONNECT_TIMEOUT_MS / 1000} seconds. ` +
28
42
  'Plugin workers expect to receive a socket connection from the parent process shortly after being started. ' +
@@ -45,6 +59,16 @@ const server = (0, net_1.createServer)((socket) => {
45
59
  if (!(0, messaging_1.isPluginWorkerMessage)(message)) {
46
60
  return;
47
61
  }
62
+ // Same check the daemon applies to its own socket. Dropped rather than thrown:
63
+ // a stray foreign message must not kill a worker serving its host. The daemon
64
+ // has a response channel and surfaces it to the client instead.
65
+ try {
66
+ (0, daemon_message_1.assertNotForeignWorkspaceMessage)(message, hostWorkspaceRoot, `The Nx plugin worker "${expectedPluginName}" (pid: ${process.pid})`);
67
+ }
68
+ catch (e) {
69
+ logger_1.logger.verbose(`[plugin-worker] ignored a "${message.type}" message: ${e instanceof Error ? e.message : e}`);
70
+ return;
71
+ }
48
72
  return (0, messaging_1.consumeMessage)(socket, message, {
49
73
  load: async ({ plugin: pluginConfiguration, root, name, pluginPath, shouldRegisterTSTranspiler, }) => {
50
74
  loadErrorTimeout?.clear();
@@ -104,8 +128,24 @@ const server = (0, net_1.createServer)((socket) => {
104
128
  process.exit(0);
105
129
  });
106
130
  });
107
- server.listen(socketPath);
108
- logger_1.logger.verbose(`[plugin-worker] "${expectedPluginName}" (pid: ${process.pid}) listening on ${socketPath}`);
131
+ server.on('error', (err) => {
132
+ // Without this the host only sees "exited before the connection was
133
+ // established"; the errno distinguishes a denied bind from EADDRINUSE.
134
+ console.error(`[plugin-worker] "${expectedPluginName}" (pid: ${process.pid}) failed to listen on ${socketPath}: ${err.message}`);
135
+ process.exit(1);
136
+ });
137
+ // A worker killed without its 'end' handler leaves the socket behind. Colliding
138
+ // takes a recycled host pid landing on the same counter *and* the same 4 random
139
+ // bytes; the random component is what this unlink rests on, and on Windows it is
140
+ // the only barrier, since the endpoint is a namespace object and the mode never
141
+ // applies. A failed bind surfaces through the error handler above.
142
+ try {
143
+ (0, fs_1.unlinkSync)(socketPath);
144
+ }
145
+ catch { }
146
+ server.listen(socketPath, () => {
147
+ logger_1.logger.verbose(`[plugin-worker] "${expectedPluginName}" (pid: ${process.pid}) listening on ${socketPath}`);
148
+ });
109
149
  async function withErrorHandling(cb) {
110
150
  try {
111
151
  const result = await cb();
@@ -136,15 +176,25 @@ function setErrorTimeout(timeoutMs, errorMessage) {
136
176
  },
137
177
  };
138
178
  }
139
- const exitHandler = (exitCode) => () => {
179
+ const cleanup = () => {
140
180
  server.close();
141
181
  try {
142
182
  (0, fs_1.unlinkSync)(socketPath);
143
183
  }
144
184
  catch (e) { }
145
- process.exit(exitCode);
146
185
  };
147
- const events = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'exit'];
148
- events.forEach((event) => process.once(event, exitHandler(0)));
149
- process.once('uncaughtException', exitHandler(1));
150
- process.once('unhandledRejection', exitHandler(1));
186
+ const events = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
187
+ events.forEach((event) => process.once(event, () => {
188
+ cleanup();
189
+ process.exit(0);
190
+ }));
191
+ // Cleanup only: process.exit() here would override the real exit code.
192
+ process.once('exit', cleanup);
193
+ const fatalHandler = (error) => {
194
+ // Registering this handler suppresses Node's default reporting.
195
+ console.error(error);
196
+ cleanup();
197
+ process.exit(1);
198
+ };
199
+ process.once('uncaughtException', fatalHandler);
200
+ process.once('unhandledRejection', fatalHandler);
@@ -354,6 +354,26 @@ async function createProjectGraphAndSourceMapsAsync(opts = {
354
354
  (0, tmp_dir_1.markDaemonAsDisabled)(e.message);
355
355
  return buildProjectGraphAndSourceMapsWithoutDaemon();
356
356
  }
357
+ if (e.daemonPermissionError) {
358
+ // Deliberately not disabled: unlike the inotify limit above, a socket
359
+ // owned by someone else stops being there when it is removed or the
360
+ // machine reboots, and disabling until `nx reset` would outlive the
361
+ // cause and hide the fix from anyone who followed the advice.
362
+ // The first line carries the errno, and it is the one token that tells
363
+ // the two causes apart: EACCES is a socket owned by someone else (delete
364
+ // it), EPERM is a sandbox refusing the connect syscall (allow unix
365
+ // sockets under the Nx socket root). The message hedges between exactly
366
+ // those two because it cannot tell them apart, and this branch does not
367
+ // call writeDaemonLogs, so dropping the line loses the errno for good.
368
+ const [summary, ...details] = e.message.split('\n');
369
+ output_1.output.note({
370
+ title: `${summary} Continuing without the daemon.`,
371
+ // The blank line after the summary separates paragraphs when the
372
+ // message is printed as one blob; as bodyLines it is a leading gap.
373
+ bodyLines: details[0] === '' ? details.slice(1) : details,
374
+ });
375
+ return buildProjectGraphAndSourceMapsWithoutDaemon();
376
+ }
357
377
  if (e.internalDaemonError) {
358
378
  const errorLogFile = (0, tmp_dir_1.writeDaemonLogs)(e.message);
359
379
  output_1.output.warn({
@@ -7,6 +7,8 @@ exports.createRootMap = createRootMap;
7
7
  const error_types_1 = require("../../error-types");
8
8
  const target_merging_1 = require("./target-merging");
9
9
  const target_normalization_1 = require("./target-normalization");
10
+ const git_worktrees_1 = require("../../../utils/git-worktrees");
11
+ const workspace_root_1 = require("../../../utils/workspace-root");
10
12
  const name_substitution_manager_1 = require("./name-substitution-manager");
11
13
  const source_maps_1 = require("./source-maps");
12
14
  const minimatch_1 = require("minimatch");
@@ -194,7 +196,9 @@ function readProjectConfigurationsFromRootMap(projectRootMap) {
194
196
  }
195
197
  }
196
198
  if (conflicts.size > 0) {
197
- throw new error_types_1.MultipleProjectsWithSameNameError(conflicts, projects);
199
+ // Only on the way to throwing, so a workspace without duplicates never
200
+ // pays for reading git's worktree registry.
201
+ throw new error_types_1.MultipleProjectsWithSameNameError(conflicts, projects, (0, git_worktrees_1.analyzeWorktreeConflicts)(workspace_root_1.workspaceRoot, conflicts) ?? undefined);
198
202
  }
199
203
  if (projectRootsWithNoName.length > 0) {
200
204
  throw new error_types_1.ProjectsWithNoNameError(projectRootsWithNoName, projects);