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
@@ -412,6 +412,16 @@ export declare function getEventDimensions(): EventDimensions
412
412
  */
413
413
  export declare function getFilesForOutputsBatch(directory: string, entriesBatch: Array<Array<string>>): Array<Array<string>>
414
414
 
415
+ /**
416
+ * The same list, for JavaScript callers that walk a tree rather than the
417
+ * filesystem - `visitNotIgnoredFiles` - so both sides apply one baseline
418
+ * instead of maintaining a second copy that drifts.
419
+ *
420
+ * The patterns are gitignore-shaped, so they read the same to the `ignore`
421
+ * crate here and the `ignore` npm package there.
422
+ */
423
+ export declare function getHardcodedIgnorePatterns(): Array<string>
424
+
415
425
  /**
416
426
  * If `workspace_root` is inside a git worktree, returns the main repo root.
417
427
  * Returns `None` when already in the main repo (or not in a git repo at all).
@@ -1,15 +1,11 @@
1
1
  const { join, basename } = require('path');
2
- const {
3
- copyFileSync,
4
- existsSync,
5
- mkdirSync,
6
- renameSync,
7
- statSync,
8
- unlinkSync,
9
- } = require('fs');
2
+ const { copyFileSync, renameSync, statSync, unlinkSync } = require('fs');
3
+ const { createHash } = require('crypto');
10
4
  const Module = require('module');
11
5
  const { nxVersion } = require('../utils/versions');
12
- const { getNativeFileCacheLocation } = require('./native-file-cache-location');
6
+ const {
7
+ ensureSecureNativeFileCacheLocation,
8
+ } = require('./native-file-cache-location');
13
9
 
14
10
  const MAX_COPY_RETRIES = 3;
15
11
 
@@ -73,6 +69,14 @@ function statsOrNull(path) {
73
69
  }
74
70
  }
75
71
 
72
+ function removeIfPresent(path) {
73
+ try {
74
+ unlinkSync(path);
75
+ } catch {
76
+ // Ignore cleanup errors
77
+ }
78
+ }
79
+
76
80
  function isNoExecError(e) {
77
81
  return e.code === 'EACCES' || e.code === 'EPERM';
78
82
  }
@@ -95,20 +99,44 @@ Module._load = function (request, parent, isMain) {
95
99
  const nativeLocation = require.resolve(modulePath);
96
100
  const fileName = basename(nativeLocation);
97
101
 
98
- // we copy the file to a workspace-scoped tmp directory and prefix with nxVersion to avoid stale files being loaded
99
- const nativeFileCacheLocation = getNativeFileCacheLocation();
102
+ // null when the cache dir cannot be created *and trusted*; loading in place
103
+ // from node_modules is safer than loading out of a directory we don't own.
104
+ const nativeFileCacheLocation = ensureSecureNativeFileCacheLocation();
105
+ if (!nativeFileCacheLocation) {
106
+ return originalLoad.apply(this, [nativeLocation, parent, isMain]);
107
+ }
108
+
109
+ // nxVersion alone is not enough: in a source checkout it is the placeholder
110
+ // 0.0.1 for every worktree, so hashing the resolved binding path keeps each
111
+ // checkout in its own entry.
112
+ const cacheKey =
113
+ nxVersion +
114
+ '-' +
115
+ createHash('sha256')
116
+ .update(nativeLocation)
117
+ .digest('hex')
118
+ .substring(0, 12);
119
+
100
120
  // This is a path to copy to, not the one that gets loaded
101
121
  const tmpTmpFile = join(
102
122
  nativeFileCacheLocation,
103
- nxVersion + '-' + Math.random() + fileName
123
+ cacheKey + '-' + Math.random() + fileName
104
124
  );
105
125
  // This is the path that will get loaded
106
- const tmpFile = join(nativeFileCacheLocation, nxVersion + '-' + fileName);
107
- const expectedFileSize = statSync(nativeLocation).size;
126
+ const tmpFile = join(nativeFileCacheLocation, cacheKey + '-' + fileName);
127
+ const sourceStats = statSync(nativeLocation);
128
+ const expectedFileSize = sourceStats.size;
108
129
  const existingFileStats = statsOrNull(tmpFile);
109
130
 
110
- // If the file to be loaded already exists, just load it
111
- if (existingFileStats?.size === expectedFileSize) {
131
+ // Size alone does not detect a rebuilt binding: a small Rust edit routinely
132
+ // produces a byte-identical size. The copy is taken after the build, so a
133
+ // cache entry older than the source means the source was rebuilt since.
134
+ const isFresh =
135
+ existingFileStats?.size === expectedFileSize &&
136
+ existingFileStats.mtimeMs >= sourceStats.mtimeMs;
137
+
138
+ // If the file to be loaded already exists and is current, just load it
139
+ if (isFresh) {
112
140
  try {
113
141
  return originalLoad.apply(this, [tmpFile, parent, isMain]);
114
142
  } catch (e) {
@@ -119,20 +147,36 @@ Module._load = function (request, parent, isMain) {
119
147
  throw e;
120
148
  }
121
149
  }
122
- if (!existsSync(nativeFileCacheLocation)) {
123
- mkdirSync(nativeFileCacheLocation, { recursive: true });
124
- }
125
150
 
126
151
  // Retry copying up to 3 times, validating after each copy
152
+ let attemptsMade = 0;
153
+ let cacheError = null;
127
154
  for (let attempt = 1; attempt <= MAX_COPY_RETRIES; attempt++) {
155
+ attemptsMade = attempt;
128
156
  // First copy to a unique location for each process
129
- copyFileSync(nativeLocation, tmpTmpFile);
157
+ try {
158
+ copyFileSync(nativeLocation, tmpTmpFile);
159
+ } catch (e) {
160
+ // Permission errors won't heal on retry, and a throwing copy can still
161
+ // have left a partial file behind.
162
+ cacheError = e;
163
+ removeIfPresent(tmpTmpFile);
164
+ break;
165
+ }
130
166
 
131
167
  // Validate the copy - check file size matches expected
132
168
  const copiedFileStats = statsOrNull(tmpTmpFile);
133
169
  if (copiedFileStats?.size === expectedFileSize) {
134
170
  // Copy succeeded, rename to final location and load
135
- renameSync(tmpTmpFile, tmpFile);
171
+ try {
172
+ renameSync(tmpTmpFile, tmpFile);
173
+ } catch (e) {
174
+ // Unguarded, this throws out of `require` and native-bindings.js
175
+ // reports it as a missing optional dependency, which it is not.
176
+ cacheError = e;
177
+ removeIfPresent(tmpTmpFile);
178
+ break;
179
+ }
136
180
  try {
137
181
  return originalLoad.apply(this, [tmpFile, parent, isMain]);
138
182
  } catch (e) {
@@ -145,16 +189,15 @@ Module._load = function (request, parent, isMain) {
145
189
  }
146
190
 
147
191
  // Copy failed validation, clean up the malformed file
148
- try {
149
- unlinkSync(tmpTmpFile);
150
- } catch {
151
- // Ignore cleanup errors
152
- }
192
+ removeIfPresent(tmpTmpFile);
153
193
  }
154
194
 
155
- // All retries failed - warn and load from original location
195
+ // Name the errno: ENOSPC, EROFS, EACCES and EDQUOT are all actionable and
196
+ // otherwise indistinguishable.
156
197
  console.warn(
157
- `Warning: Failed to copy native module to cache after ${MAX_COPY_RETRIES} attempts. ` +
198
+ `Warning: Failed to copy native module to cache after ${attemptsMade} attempt${
199
+ attemptsMade === 1 ? '' : 's'
200
+ }${cacheError ? ` (${cacheError.code ?? cacheError.message})` : ''}. ` +
158
201
  `Loading from original location instead. ` +
159
202
  `This may cause file locking issues on Windows.`
160
203
  );
@@ -610,6 +610,7 @@ module.exports.getBinaryTarget = nativeBinding.getBinaryTarget
610
610
  module.exports.getDefaultMaxCacheSize = nativeBinding.getDefaultMaxCacheSize
611
611
  module.exports.getEventDimensions = nativeBinding.getEventDimensions
612
612
  module.exports.getFilesForOutputsBatch = nativeBinding.getFilesForOutputsBatch
613
+ module.exports.getHardcodedIgnorePatterns = nativeBinding.getHardcodedIgnorePatterns
613
614
  module.exports.getMainWorktreeRoot = nativeBinding.getMainWorktreeRoot
614
615
  module.exports.getTransformableOutputs = nativeBinding.getTransformableOutputs
615
616
  module.exports.GroupType = nativeBinding.GroupType
@@ -1 +1,17 @@
1
- export declare function getNativeFileCacheLocation(): string;
1
+ /**
2
+ * Path of the current user's native binary cache. Nothing about the constant is
3
+ * owner-only — `ensureSecureNativeFileCacheLocation` is what establishes that,
4
+ * and on Windows nothing does, since the OS temp dir is already per-account.
5
+ */
6
+ export declare const NATIVE_CACHE_ROOT: string;
7
+ export declare function getNativeFileCacheLocationToDelete(): string | null;
8
+ /**
9
+ * Create the native file cache dir, or return `null` if it cannot be created
10
+ * *securely* — in which case the caller loads the binding in place.
11
+ *
12
+ * The stable top-level container is verified as safe for private children. The
13
+ * uid directory and every directory loaded through are owner-only.
14
+ * `ensureOwnedPrivateDir` refuses a directory or symlink another local user
15
+ * planted before us.
16
+ */
17
+ export declare function ensureSecureNativeFileCacheLocation(cacheRoot?: string): string | null;
@@ -1,23 +1,97 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getNativeFileCacheLocation = getNativeFileCacheLocation;
4
- const os_1 = require("os");
3
+ exports.NATIVE_CACHE_ROOT = void 0;
4
+ exports.getNativeFileCacheLocationToDelete = getNativeFileCacheLocationToDelete;
5
+ exports.ensureSecureNativeFileCacheLocation = ensureSecureNativeFileCacheLocation;
5
6
  const path_1 = require("path");
6
- const crypto_1 = require("crypto");
7
- const workspace_root_1 = require("../utils/workspace-root");
7
+ const fs_1 = require("fs");
8
+ const nx_tmp_dir_1 = require("../utils/nx-tmp-dir");
8
9
  const versions_1 = require("../utils/versions");
9
- function getNativeFileCacheLocation() {
10
- if (process.env.NX_NATIVE_FILE_CACHE_DIRECTORY) {
11
- return process.env.NX_NATIVE_FILE_CACHE_DIRECTORY;
10
+ const owned_private_dir_1 = require("../utils/owned-private-dir");
11
+ /**
12
+ * Path of the current user's native binary cache. Nothing about the constant is
13
+ * owner-only — `ensureSecureNativeFileCacheLocation` is what establishes that,
14
+ * and on Windows nothing does, since the OS temp dir is already per-account.
15
+ */
16
+ exports.NATIVE_CACHE_ROOT = (0, path_1.join)(nx_tmp_dir_1.NX_USER_TMP_DIR, 'native-cache');
17
+ /**
18
+ * The configured cache dir, normalized. `resolve` strips a trailing slash, which
19
+ * would otherwise defeat the guards downstream: `lstat` on a path ending in `/`
20
+ * resolves a symlink rather than reporting it, and `O_NOFOLLOW` then opens the
21
+ * target. `NX_SOCKET_DIR` is normalized for the same reason.
22
+ */
23
+ function configuredCacheDir() {
24
+ const dir = process.env.NX_NATIVE_FILE_CACHE_DIRECTORY;
25
+ return dir ? (0, path_1.resolve)(dir) : undefined;
26
+ }
27
+ function getNativeFileCacheLocationToDelete() {
28
+ const configured = configuredCacheDir();
29
+ if (configured !== undefined) {
30
+ // Checked for ownership before it is handed to a recursive delete: being
31
+ // configured does not make it ours. Weaker than the load path below, which
32
+ // also enforces mode and validates the version directory.
33
+ return (0, owned_private_dir_1.isOwnedRealDirectory)(configured) ? configured : null;
12
34
  }
13
- else {
14
- const hash = (0, crypto_1.createHash)('sha256').update(workspace_root_1.workspaceRoot).update(versions_1.nxVersion);
35
+ return (0, owned_private_dir_1.isSafeSharedRoot)(nx_tmp_dir_1.NX_TMP_DIR).status === 'ok' &&
36
+ (0, owned_private_dir_1.isOwnedRealDirectory)(nx_tmp_dir_1.NX_USER_TMP_DIR) &&
37
+ (0, owned_private_dir_1.isOwnedRealDirectory)(exports.NATIVE_CACHE_ROOT)
38
+ ? (0, path_1.join)(exports.NATIVE_CACHE_ROOT, versions_1.nxVersion)
39
+ : null;
40
+ }
41
+ /**
42
+ * Create the native file cache dir, or return `null` if it cannot be created
43
+ * *securely* — in which case the caller loads the binding in place.
44
+ *
45
+ * The stable top-level container is verified as safe for private children. The
46
+ * uid directory and every directory loaded through are owner-only.
47
+ * `ensureOwnedPrivateDir` refuses a directory or symlink another local user
48
+ * planted before us.
49
+ */
50
+ function ensureSecureNativeFileCacheLocation(
51
+ // Test seam: lets a spec plant a hostile directory under a root it controls.
52
+ cacheRoot = exports.NATIVE_CACHE_ROOT) {
53
+ const configured = configuredCacheDir();
54
+ if (configured !== undefined) {
55
+ const dir = configured;
15
56
  try {
16
- hash.update((0, os_1.userInfo)().username);
57
+ // Held to the same 0700 bar as the default location: a `.node` is loaded
58
+ // out of here. Unlike NX_SOCKET_DIR it is not additionally refused for
59
+ // naming one of Nx's own roots, which is the only case that throws.
60
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(dir), { recursive: true });
61
+ const established = (0, owned_private_dir_1.ensureOwnedPrivateDir)(dir);
62
+ if (established.status !== 'ok') {
63
+ // The guard's own reason rather than a fixed sentence: `not-created
64
+ // (EACCES)` and `foreign-owner` are exactly what the catch below
65
+ // promises to tell apart, and one string for all of them cannot.
66
+ throw new owned_private_dir_1.DirectoryRefusedError(established.refusal);
67
+ }
68
+ return dir;
17
69
  }
18
70
  catch (e) {
19
- // if there's no user, we only use the workspace root for the hash and move on
71
+ // Never discard a configured directory silently — the socket directory
72
+ // follows the same rule. A typo, EACCES or EROFS is otherwise
73
+ // indistinguishable from a working cache.
74
+ console.warn(`Nx could not use the configured native file cache directory ${dir} (${e?.code ?? e?.message ?? e}). Loading the native binding in place instead.`);
75
+ return null;
76
+ }
77
+ }
78
+ const userRoot = (0, path_1.dirname)(cacheRoot);
79
+ const sharedRoot = (0, path_1.dirname)(userRoot);
80
+ // Outermost first: the stable shared container must either belong to root or
81
+ // to us, and must be sticky if peers can write there. The uid directory then
82
+ // becomes the owner-only boundary for sockets and native-cache alike.
83
+ if ((0, owned_private_dir_1.ensureSafeSharedRoot)(sharedRoot).status !== 'ok') {
84
+ return null;
85
+ }
86
+ for (const root of [userRoot, cacheRoot]) {
87
+ if ((0, owned_private_dir_1.ensureOwnedPrivateDir)(root).status !== 'ok') {
88
+ return null;
20
89
  }
21
- return (0, path_1.join)((0, os_1.tmpdir)(), `nx-native-file-cache-${hash.digest('hex').substring(0, 7)}`);
22
90
  }
91
+ // Verified, not assumed: this is the directory we load a `.node` out of.
92
+ const versionDir = (0, path_1.join)(cacheRoot, versions_1.nxVersion);
93
+ if ((0, owned_private_dir_1.ensureOwnedPrivateDir)(versionDir).status !== 'ok') {
94
+ return null;
95
+ }
96
+ return versionDir;
23
97
  }
Binary file
@@ -5,12 +5,15 @@ exports.getCloudUrl = getCloudUrl;
5
5
  exports.removeTrailingSlash = removeTrailingSlash;
6
6
  exports.isNxCloudId = isNxCloudId;
7
7
  const nx_json_1 = require("../../config/nx-json");
8
- const run_command_1 = require("../../tasks-runner/run-command");
9
8
  const workspace_root_1 = require("../../utils/workspace-root");
10
9
  function getCloudOptions(directory = workspace_root_1.workspaceRoot) {
10
+ // Required lazily: this module is reachable from the @nx/devkit/internal
11
+ // barrel that plugin workers load, and run-command eagerly pulls in the whole
12
+ // task-execution subsystem.
13
+ const { getRunnerOptions, } = require('../../tasks-runner/run-command');
11
14
  const nxJson = (0, nx_json_1.readNxJson)(directory);
12
15
  // TODO: The default is not always cloud? But it's not handled at the moment
13
- return (0, run_command_1.getRunnerOptions)('default', nxJson, {}, true);
16
+ return getRunnerOptions('default', nxJson, {}, true);
14
17
  }
15
18
  function getCloudUrl() {
16
19
  return removeTrailingSlash(process.env.NX_CLOUD_API || process.env.NRWL_API || `https://cloud.nx.app`);
@@ -5,7 +5,6 @@ exports.getNxCloudAppOnBoardingUrl = getNxCloudAppOnBoardingUrl;
5
5
  exports.readNxCloudToken = readNxCloudToken;
6
6
  const is_workspace_claimed_1 = require("./is-workspace-claimed");
7
7
  const url_shorten_1 = require("./url-shorten");
8
- const run_command_1 = require("../../tasks-runner/run-command");
9
8
  const nx_json_1 = require("../../generators/utils/nx-json");
10
9
  async function createNxCloudOnboardingURLForWelcomeApp(tree, token) {
11
10
  token = token || readNxCloudToken(tree);
@@ -22,7 +21,11 @@ async function getNxCloudAppOnBoardingUrl(token) {
22
21
  return onboardingUrl;
23
22
  }
24
23
  function readNxCloudToken(tree) {
24
+ // Required lazily: this module is re-exported from the @nx/devkit/internal
25
+ // barrel that plugin workers load, and run-command eagerly pulls in the whole
26
+ // task-execution subsystem.
27
+ const { getRunnerOptions, } = require('../../tasks-runner/run-command');
25
28
  const nxJson = (0, nx_json_1.readNxJson)(tree);
26
- const { accessToken, nxCloudId } = (0, run_command_1.getRunnerOptions)('default', nxJson, {}, true);
29
+ const { accessToken, nxCloudId } = getRunnerOptions('default', nxJson, {}, true);
27
30
  return accessToken || nxCloudId;
28
31
  }
@@ -12,6 +12,16 @@ const project_graph_1 = require("../../../config/project-graph");
12
12
  const file_hasher_1 = require("../../../hasher/file-hasher");
13
13
  const project_graph_builder_1 = require("../../../project-graph/project-graph-builder");
14
14
  const json_1 = require("../../../utils/json");
15
+ // Highest bun.lock version this parser has been verified against:
16
+ // - 1: workspace packages are no longer listed with their dependencies
17
+ // - 2: identical content, stricter validation in Bun's own reader
18
+ // - 3: values in "overrides" may be objects holding rules scoped to a parent
19
+ // package (`"parent": { "child": "1.0.0" }`) and keys may carry a version
20
+ // selector (`"parent@1"`, `"child@<2"`). This parser never reads
21
+ // "overrides"; the resolved packages are listed in "packages" as before.
22
+ // Newer versions are parsed on a best-effort basis instead of being rejected
23
+ // outright.
24
+ const MAX_KNOWN_LOCKFILE_VERSION = 3;
15
25
  const DEPENDENCY_TYPES = [
16
26
  'dependencies',
17
27
  'devDependencies',
@@ -356,9 +366,12 @@ function parseLockFile(lockFileContent, lockFileHash) {
356
366
  if (typeof result.lockfileVersion !== 'number') {
357
367
  throw new Error(`Lockfile version must be a number, got ${typeof result.lockfileVersion}`);
358
368
  }
359
- const supportedVersions = [0, 1];
360
- if (!supportedVersions.includes(result.lockfileVersion)) {
361
- throw new Error(`Unsupported lockfile version ${result.lockfileVersion}. Supported versions: ${supportedVersions.join(', ')}`);
369
+ if (!Number.isInteger(result.lockfileVersion) ||
370
+ result.lockfileVersion < 0) {
371
+ throw new Error(`Unsupported lockfile version ${result.lockfileVersion}. Lockfile version must be a non-negative integer`);
372
+ }
373
+ if (result.lockfileVersion > MAX_KNOWN_LOCKFILE_VERSION) {
374
+ console.warn(`bun.lock has lockfileVersion ${result.lockfileVersion}, which is newer than the version ${MAX_KNOWN_LOCKFILE_VERSION} this version of Nx was tested with. Attempting to parse it anyway.`);
362
375
  }
363
376
  }
364
377
  if (!result.packages || typeof result.packages !== 'object') {
@@ -616,7 +629,7 @@ function processPackageForDependencies(packageKey, packageData, lockFile, index,
616
629
  const deps = packageDependencies[depType];
617
630
  if (!deps || typeof deps !== 'object')
618
631
  continue;
619
- const depDependencies = processDependencyEntries(deps, sourceNodeName, index, ctx, workspacePackages, lockFile.manifests);
632
+ const depDependencies = processDependencyEntries(deps, packageKey, sourceNodeName, lockFile, index, ctx, workspacePackages);
620
633
  dependencies.push(...depDependencies);
621
634
  }
622
635
  return dependencies;
@@ -634,12 +647,12 @@ function extractPackageDependencies(packageData) {
634
647
  }
635
648
  return undefined;
636
649
  }
637
- function processDependencyEntries(deps, sourceNodeName, index, ctx, workspacePackages, manifests) {
650
+ function processDependencyEntries(deps, sourcePackageKey, sourceNodeName, lockFile, index, ctx, workspacePackages) {
638
651
  const dependencies = [];
639
652
  const depsEntries = Object.entries(deps);
640
653
  for (const [packageName, versionSpec] of depsEntries) {
641
654
  try {
642
- const dependency = processSingleDependency(packageName, versionSpec, sourceNodeName, index, ctx, workspacePackages, manifests);
655
+ const dependency = processSingleDependency(packageName, versionSpec, sourcePackageKey, sourceNodeName, lockFile, index, ctx, workspacePackages);
643
656
  if (dependency) {
644
657
  dependencies.push(dependency);
645
658
  }
@@ -650,7 +663,7 @@ function processDependencyEntries(deps, sourceNodeName, index, ctx, workspacePac
650
663
  }
651
664
  return dependencies;
652
665
  }
653
- function processSingleDependency(packageName, versionSpec, sourceNodeName, index, ctx, workspacePackages, manifests) {
666
+ function processSingleDependency(packageName, versionSpec, sourcePackageKey, sourceNodeName, lockFile, index, ctx, workspacePackages) {
654
667
  if (typeof packageName !== 'string' || typeof versionSpec !== 'string') {
655
668
  return null;
656
669
  }
@@ -663,14 +676,22 @@ function processSingleDependency(packageName, versionSpec, sourceNodeName, index
663
676
  }
664
677
  let targetPackageName = packageName;
665
678
  let targetVersion = versionSpec;
666
- const aliasTarget = resolveAliasTarget(versionSpec);
667
- if (aliasTarget) {
679
+ // A "<parent>/<name>" entry records what this parent actually resolved the
680
+ // dependency to. Overrides scoped to a parent (bun.lock v3) can pick a
681
+ // version outside the declared range, so it takes precedence over matching
682
+ // the range against the versions in the lockfile.
683
+ const nestedVersion = resolveNestedVersion(sourcePackageKey, packageName, lockFile);
684
+ const aliasTarget = nestedVersion ? null : resolveAliasTarget(versionSpec);
685
+ if (nestedVersion) {
686
+ targetVersion = nestedVersion;
687
+ }
688
+ else if (aliasTarget) {
668
689
  targetPackageName = aliasTarget.packageName;
669
690
  targetVersion = aliasTarget.version;
670
691
  }
671
692
  else {
672
693
  // O(1) lookup using index instead of O(n) scan
673
- const resolvedVersion = findResolvedVersion(packageName, versionSpec, index, manifests);
694
+ const resolvedVersion = findResolvedVersion(packageName, versionSpec, index, lockFile.manifests);
674
695
  if (!resolvedVersion) {
675
696
  return null;
676
697
  }
@@ -693,6 +714,18 @@ function processSingleDependency(packageName, versionSpec, sourceNodeName, index
693
714
  return null;
694
715
  }
695
716
  }
717
+ function resolveNestedVersion(sourcePackageKey, packageName, lockFile) {
718
+ const nestedEntry = lockFile.packages[`${sourcePackageKey}/${packageName}`];
719
+ if (!Array.isArray(nestedEntry) || typeof nestedEntry[0] !== 'string') {
720
+ return null;
721
+ }
722
+ const { name, version } = getCachedSpecInfo(nestedEntry[0]);
723
+ // aliases ("alias@npm:real@1.0.0") are resolved to the real package by the caller
724
+ if (name !== packageName || !version || version.startsWith('npm:')) {
725
+ return null;
726
+ }
727
+ return version;
728
+ }
696
729
  function resolveTargetNodeName(targetPackageName, targetVersion, ctx) {
697
730
  const hoistedNodeName = `npm:${targetPackageName}`;
698
731
  const versionedNodeName = `npm:${targetPackageName}@${targetVersion}`;
@@ -12,6 +12,7 @@ const file_hasher_1 = require("../../../hasher/file-hasher");
12
12
  const catalog_1 = require("../../../utils/catalog");
13
13
  const project_graph_pruning_1 = require("./project-graph-pruning");
14
14
  const path_1 = require("path");
15
+ const workspace_root_1 = require("../../../utils/workspace-root");
15
16
  const get_workspace_packages_from_graph_1 = require("../utils/get-workspace-packages-from-graph");
16
17
  const semver_1 = require("semver");
17
18
  const WORKSPACE_DEP_TYPES = [
@@ -133,6 +134,17 @@ function findPatchHash(patchEntriesByPackage, packageName, version) {
133
134
  const nameOnlyMatch = entries.find((entry) => entry.versionSpecifier === null);
134
135
  return nameOnlyMatch?.hash;
135
136
  }
137
+ // Segment-aware: `..` and `../x` escape, a directory literally named `..cache`
138
+ // does not. Absolute targets are resolved against the workspace root.
139
+ function linkTargetEscapesWorkspace(importerPath, depVersion) {
140
+ const rawTarget = depVersion.slice('link:'.length);
141
+ if (path_1.posix.isAbsolute(rawTarget) || (0, path_1.isAbsolute)(rawTarget)) {
142
+ const rel = (0, path_1.relative)(workspace_root_1.workspaceRoot, rawTarget);
143
+ return rel === '..' || rel.startsWith(`..${path_1.sep}`) || (0, path_1.isAbsolute)(rel);
144
+ }
145
+ const combined = path_1.posix.normalize(path_1.posix.join(importerPath === '.' ? '' : importerPath, rawTarget));
146
+ return combined === '..' || combined.startsWith('../');
147
+ }
136
148
  function getNodes(data, isV5) {
137
149
  cachedInvertedRecords.clear();
138
150
  const keyMap = new Map();
@@ -345,6 +357,54 @@ function getNodes(data, isV5) {
345
357
  results[node.name] = node;
346
358
  });
347
359
  }
360
+ // `link:` dependencies pointing outside the workspace never appear in the
361
+ // packages section, so nothing above minted a node for them — and a task
362
+ // input naming one via `externalDependencies` fails hashing with "could not
363
+ // be found". Mint a node under the bare `npm:<name>`, hashed on the link
364
+ // path: the hash changes only when the path does, not when the linked
365
+ // content does, which is how linked dependencies behave everywhere else in
366
+ // Nx. Links that resolve inside the workspace are workspace projects, not
367
+ // externals, and are skipped.
368
+ //
369
+ // One node per package name, chosen deterministically by visiting importers
370
+ // root-first, then lexicographically. An `externalDependencies` input names
371
+ // a package, not a link target, so it could not distinguish two linked
372
+ // targets anyway.
373
+ const importerPaths = Object.keys(data.importers ?? {}).sort((a, b) => a === '.' ? -1 : b === '.' ? 1 : a.localeCompare(b));
374
+ for (const importerPath of importerPaths) {
375
+ const importer = data.importers[importerPath];
376
+ for (const depType of [
377
+ 'dependencies',
378
+ 'devDependencies',
379
+ 'optionalDependencies',
380
+ ]) {
381
+ const deps = importer[depType];
382
+ if (!deps) {
383
+ continue;
384
+ }
385
+ for (const [depName, depVersion] of Object.entries(deps)) {
386
+ if (typeof depVersion !== 'string' || !depVersion.startsWith('link:')) {
387
+ continue;
388
+ }
389
+ if (!linkTargetEscapesWorkspace(importerPath, depVersion)) {
390
+ continue; // inside the workspace -> a workspace project
391
+ }
392
+ const bareName = `npm:${depName}`;
393
+ if (results[bareName]) {
394
+ continue; // a hoisted registry version or an earlier link wins
395
+ }
396
+ results[bareName] = {
397
+ type: 'npm',
398
+ name: bareName,
399
+ data: {
400
+ version: depVersion,
401
+ packageName: depName,
402
+ hash: (0, file_hasher_1.hashArray)([depName, depVersion]),
403
+ },
404
+ };
405
+ }
406
+ }
407
+ }
348
408
  return { nodes: results, keyMap };
349
409
  }
350
410
  function getHoistedVersion(packageName, isV5, hoistedKeysByPackage) {
@@ -177,17 +177,23 @@ function switchNodeToHoisted(node, builder, invBuilder) {
177
177
  const sources = Object.keys(builder.graph.dependencies).filter((name) => builder.graph.dependencies[name].some((d) => d.target === previousName));
178
178
  builder.removeNode(node.name);
179
179
  invBuilder.removeNode(node.name);
180
- // modify the node and re-add it
181
- node.name = `npm:${node.data.packageName}`;
182
- builder.addExternalNode(node);
183
- invBuilder.addExternalNode(node);
180
+ // Re-add under the hoisted name as a new object. The node is shared by
181
+ // reference with the caller's graph, so renaming it in place would leave that
182
+ // graph with a node keyed `npm:<pkg>@<version>` but named `npm:<pkg>`, and
183
+ // every later prune of the same graph would fail to resolve the edge.
184
+ const hoistedNode = {
185
+ ...node,
186
+ name: `npm:${node.data.packageName}`,
187
+ };
188
+ builder.addExternalNode(hoistedNode);
189
+ invBuilder.addExternalNode(hoistedNode);
184
190
  targets.forEach((target) => {
185
- builder.addStaticDependency(node.name, target);
186
- invBuilder.addStaticDependency(target, node.name);
191
+ builder.addStaticDependency(hoistedNode.name, target);
192
+ invBuilder.addStaticDependency(target, hoistedNode.name);
187
193
  });
188
194
  sources.forEach((source) => {
189
- builder.addStaticDependency(source, node.name);
190
- invBuilder.addStaticDependency(node.name, source);
195
+ builder.addStaticDependency(source, hoistedNode.name);
196
+ invBuilder.addStaticDependency(hoistedNode.name, source);
191
197
  });
192
198
  }
193
199
  // BFS to find the shortest path to a dependency specified in package.json