nx 23.3.0 → 24.0.0-beta.0

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 (66) hide show
  1. package/README.md +3 -6
  2. package/dist/src/adapter/ngcli-adapter.js +1 -0
  3. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +0 -12
  4. package/dist/src/command-line/graph/graph.js +3 -5
  5. package/dist/src/command-line/init/command-object.js +0 -12
  6. package/dist/src/command-line/init/init-v2.js +0 -2
  7. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +17 -12
  8. package/dist/src/command-line/release/utils/remote-release-clients/github.js +5 -6
  9. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +5 -6
  10. package/dist/src/command-line/run/executor-utils.js +1 -0
  11. package/dist/src/command-line/run/run-one.d.ts +20 -1
  12. package/dist/src/command-line/run/run-one.js +153 -5
  13. package/dist/src/command-line/show/show-target/utils.js +1 -0
  14. package/dist/src/command-line/watch/command-object.js +3 -22
  15. package/dist/src/command-line/watch/watch.d.ts +0 -7
  16. package/dist/src/command-line/watch/watch.js +1 -1
  17. package/dist/src/config/misc-interfaces.d.ts +12 -0
  18. package/dist/src/config/workspace-json-project-json.d.ts +5 -0
  19. package/dist/src/daemon/client/client.d.ts +9 -0
  20. package/dist/src/daemon/client/client.js +89 -37
  21. package/dist/src/daemon/server/shutdown-utils.js +0 -3
  22. package/dist/src/daemon/socket-utils.d.ts +9 -4
  23. package/dist/src/daemon/socket-utils.js +12 -49
  24. package/dist/src/daemon/tmp-dir.d.ts +19 -14
  25. package/dist/src/daemon/tmp-dir.js +142 -354
  26. package/dist/src/devkit-internals.d.ts +1 -1
  27. package/dist/src/devkit-internals.js +1 -0
  28. package/dist/src/hasher/file-hasher.js +9 -7
  29. package/dist/src/hasher/hash-task.js +4 -0
  30. package/dist/src/hasher/native-task-hasher-impl.js +1 -1
  31. package/dist/src/migrations/update-24-0-0/find-custom-hashers.d.ts +3 -0
  32. package/dist/src/migrations/update-24-0-0/find-custom-hashers.js +100 -0
  33. package/dist/src/migrations/update-24-0-0/find-custom-hashers.md +63 -0
  34. package/dist/src/migrations/update-24-0-0/move-custom-hashers-to-inputs.md +145 -0
  35. package/dist/src/native/index.d.ts +127 -8
  36. package/dist/src/native/native-bindings.js +5 -0
  37. package/dist/src/native/nx.wasi-browser.js +1 -0
  38. package/dist/src/native/nx.wasi.cjs +1 -0
  39. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  40. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  41. package/dist/src/nx-cloud/update-manager.d.ts +7 -0
  42. package/dist/src/nx-cloud/update-manager.js +237 -50
  43. package/dist/src/project-graph/affected/affected-tasks.js +1 -0
  44. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +7 -0
  45. package/dist/src/tasks-runner/create-task-graph.d.ts +10 -0
  46. package/dist/src/tasks-runner/create-task-graph.js +37 -2
  47. package/dist/src/tasks-runner/task-env.d.ts +10 -0
  48. package/dist/src/tasks-runner/task-env.js +26 -5
  49. package/dist/src/tasks-runner/task-orchestrator.d.ts +10 -4
  50. package/dist/src/tasks-runner/task-orchestrator.js +87 -53
  51. package/dist/src/tasks-runner/utils.d.ts +20 -5
  52. package/dist/src/tasks-runner/utils.js +80 -29
  53. package/dist/src/ultracache/overrides.js +4 -0
  54. package/dist/src/utils/owned-private-dir.d.ts +1 -17
  55. package/dist/src/utils/owned-private-dir.js +10 -62
  56. package/dist/src/utils/perf-hooks.d.ts +1 -0
  57. package/dist/src/utils/plugins/core-plugins.js +1 -1
  58. package/dist/src/utils/string-similarity.d.ts +38 -0
  59. package/dist/src/utils/string-similarity.js +108 -0
  60. package/migrations.json +7 -0
  61. package/package.json +11 -20
  62. package/readme-template.md +3 -3
  63. package/schemas/nx-schema.json +5 -0
  64. package/schemas/project-schema.json +5 -0
  65. package/dist/src/tasks-runner/legacy-depends-on-warning.d.ts +0 -18
  66. package/dist/src/tasks-runner/legacy-depends-on-warning.js +0 -109
@@ -1,25 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDaemonSocketDir = exports.DAEMON_OUTPUT_LOG_FILE = exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE = exports.InvalidSocketDirConfigured = void 0;
3
+ exports.DAEMON_OUTPUT_LOG_FILE = exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE = exports.InvalidSocketDirConfigured = void 0;
4
4
  exports.writeDaemonLogs = writeDaemonLogs;
5
5
  exports.markDaemonAsDisabled = markDaemonAsDisabled;
6
6
  exports.disableDaemonForThisProcess = disableDaemonForThisProcess;
7
7
  exports.isDaemonDisabled = isDaemonDisabled;
8
- exports.getSocketDir = getSocketDir;
9
- exports.getPluginSocketDir = getPluginSocketDir;
10
8
  exports.resetSocketDirWarningsForTesting = resetSocketDirWarningsForTesting;
11
- exports.getSocketDirFallbackCause = getSocketDirFallbackCause;
12
- exports.getRefusedConfiguredSocketDir = getRefusedConfiguredSocketDir;
9
+ exports.getDaemonSocketPath = getDaemonSocketPath;
10
+ exports.getForkedProcessSocketPath = getForkedProcessSocketPath;
11
+ exports.getPluginSocketPath = getPluginSocketPath;
12
+ exports.getNxConsoleSocketPath = getNxConsoleSocketPath;
13
13
  exports.removeSocketDir = removeSocketDir;
14
14
  /**
15
- * Where the daemon, forked task processes and plugin workers put their sockets.
15
+ * Where the daemon, forked task processes, plugin workers and Nx Console put
16
+ * their sockets.
16
17
  *
17
- * Not `os.tmpdir()`, and not one location: the root is a literal `/tmp/.nx` so a
18
- * sandbox can allowlist it once, and resolution walks an ordered chain —
19
- * `$NX_SOCKET_DIR`, then `/tmp/.nx/<uid>/sockets`, then `~/.nx/sockets`, then the
20
- * workspace data dir. The socket directory beneath the winning root is per-run,
21
- * since its name hashes the pid; clients read the daemon's path back out of the
22
- * process cache rather than deriving it.
18
+ * Resolution itself lives in the native crate, in `native/utils/socket_path.rs`.
19
+ * It has to: the root is picked at runtime from an ordered chain that can
20
+ * demote, so a second derivation cannot track it, and Nx Console binds the
21
+ * socket Nx dials. What stays here is the wording and the once-per-process
22
+ * latches, because the logger is here.
23
23
  *
24
24
  * Daemon logs are not here — they live in the workspace data dir alongside the
25
25
  * `disabled` marker, which is why that path survives a socket-root change.
@@ -27,13 +27,7 @@ exports.removeSocketDir = removeSocketDir;
27
27
  const node_fs_1 = require("node:fs");
28
28
  const path_1 = require("path");
29
29
  const cache_directory_1 = require("../utils/cache-directory");
30
- const owned_private_dir_1 = require("../utils/owned-private-dir");
31
- const crypto_1 = require("crypto");
32
- // Refused as a socket *directory* (see InvalidSocketDirConfigured), and also
33
- // the socket root itself on Windows, where named pipes are not filesystem
34
- // objects and there is nothing to lock down beneath it.
35
- const tmp_1 = require("tmp");
36
- const native_file_cache_location_1 = require("../native/native-file-cache-location");
30
+ const native_1 = require("../native");
37
31
  const nx_tmp_dir_1 = require("../utils/nx-tmp-dir");
38
32
  const is_sandbox_1 = require("../utils/is-sandbox");
39
33
  const workspace_root_1 = require("../utils/workspace-root");
@@ -59,8 +53,8 @@ class InvalidSocketDirConfigured extends Error {
59
53
  // root is *not* peer-writable, so what it holds is this account's
60
54
  // own. And "Nx already puts its sockets in a subdirectory of this
61
55
  // root" is false on POSIX, where the default root is a literal
62
- // /tmp/.nx and this one is os.tmpdir() — which is exactly when the
63
- // two differ.
56
+ // /tmp/.nx and this one is the OS temp dir — which is exactly when
57
+ // the two differ.
64
58
  `The configured Nx socket directory ${dir} is the operating system temp directory. Nx deletes the socket directory and everything in it when the daemon stops, which here would take everything else using this temp directory with it. Point NX_SOCKET_DIR at a directory of your own instead — one nested beneath this root is fine.`
65
59
  : `The configured Nx socket directory ${dir} is a directory Nx keeps its own runtime state in, and Nx creates and removes socket directories beneath it. Point NX_SOCKET_DIR at a directory of your own instead — one nested beneath this root is fine.`);
66
60
  this.dir = dir;
@@ -71,11 +65,6 @@ class InvalidSocketDirConfigured extends Error {
71
65
  exports.InvalidSocketDirConfigured = InvalidSocketDirConfigured;
72
66
  exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, 'd');
73
67
  exports.DAEMON_OUTPUT_LOG_FILE = (0, path_1.join)(exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE, 'daemon.log');
74
- const getDaemonSocketDir = () => (0, path_1.join)(getSocketDir(),
75
- // Kept intentionally short to stay under the socket/named pipe path length
76
- // limit enforced by `assertValidSocketPath` in socket-utils.ts.
77
- 'd.sock');
78
- exports.getDaemonSocketDir = getDaemonSocketDir;
79
68
  function writeDaemonLogs(error) {
80
69
  const file = (0, path_1.join)(exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE, 'daemon-error.log');
81
70
  (0, node_fs_1.writeFileSync)(file, error);
@@ -109,225 +98,6 @@ function isDaemonDisabled() {
109
98
  return false;
110
99
  }
111
100
  }
112
- /**
113
- * One short root beneath the current user's owner-only runtime directory. No
114
- * extra segment on Windows: `%TMP%` is already per-user and already contains the
115
- * username, so `\.nx\sockets` would spend 12 characters of the 95-char budget
116
- * `assertValidSocketPath` enforces and newly overrun it for long account names.
117
- */
118
- function defaultSocketRoot() {
119
- return process.platform === 'win32'
120
- ? tmp_1.tmpdir
121
- : (0, path_1.join)(nx_tmp_dir_1.NX_USER_TMP_DIR, 'sockets');
122
- }
123
- function homeSocketRoot() {
124
- return nx_tmp_dir_1.NX_HOME_TMP_DIR ? (0, path_1.join)(nx_tmp_dir_1.NX_HOME_TMP_DIR, 'sockets') : undefined;
125
- }
126
- /**
127
- * Whether `~/.nx` is somewhere other than the shared container. With
128
- * `HOME=/tmp` they are the same path, and offering it as a second tier would
129
- * point `ensureOwnedPrivateDir` at `/tmp/.nx` itself — which, when the container
130
- * is already ours (or Nx runs as root), takes a `1777` container to `0700` and
131
- * undoes the documented provisioning, with nothing to put it back.
132
- */
133
- function homeTierIsDistinct() {
134
- if (!nx_tmp_dir_1.NX_HOME_TMP_DIR) {
135
- return false;
136
- }
137
- // Hoisted: the callback runs once per root, and this operand does not change.
138
- const home = (0, owned_private_dir_1.canonicalDir)(nx_tmp_dir_1.NX_HOME_TMP_DIR);
139
- return ![
140
- tmp_1.tmpdir,
141
- nx_tmp_dir_1.NX_TMP_DIR,
142
- nx_tmp_dir_1.NX_USER_TMP_DIR,
143
- defaultSocketRoot(),
144
- native_file_cache_location_1.NATIVE_CACHE_ROOT,
145
- ].some((shared) => (0, owned_private_dir_1.canonicalDir)(shared) === home);
146
- }
147
- /**
148
- * Socket roots to try, best first. Each entry establishes its own containment
149
- * before it can be used; the first that succeeds wins, and the workspace data
150
- * dir is the last resort when none does.
151
- *
152
- * `/tmp` first because it is the shortest path — the socket budget is 95
153
- * characters. Home second because it needs no administrator. Windows has one
154
- * tier: named pipes are not filesystem objects, so there is nothing to establish.
155
- */
156
- function socketRootTiers() {
157
- if (process.platform === 'win32') {
158
- return [{ root: tmp_1.tmpdir, establish: () => true }];
159
- }
160
- // Stops at the first refusal and records it: once the shared container is
161
- // unusable, whether the directories beneath it would also have failed says
162
- // nothing the user can act on.
163
- const establishAll = (dirs, refusals, guard) => dirs.every((d) => {
164
- const result = guard(d);
165
- if (result.status === 'refused') {
166
- refusals.push(result.refusal);
167
- return false;
168
- }
169
- return true;
170
- });
171
- return [
172
- {
173
- root: defaultSocketRoot(),
174
- establish: (refusals) => establishAll([nx_tmp_dir_1.NX_TMP_DIR], refusals, owned_private_dir_1.ensureSafeSharedRoot) &&
175
- establishAll([nx_tmp_dir_1.NX_USER_TMP_DIR, defaultSocketRoot()], refusals, owned_private_dir_1.ensureOwnedPrivateDir),
176
- },
177
- // Omitted entirely when there is no home directory to use, or when it is
178
- // the shared container under another name, rather than offered and then
179
- // damaging what it lands on.
180
- ...(homeTierIsDistinct() && homeSocketRoot()
181
- ? [
182
- {
183
- root: homeSocketRoot(),
184
- // No shared level to verify: the home directory is the user's own,
185
- // so there is no container another user could have created first.
186
- establish: (refusals) => establishAll([nx_tmp_dir_1.NX_HOME_TMP_DIR, homeSocketRoot()], refusals, owned_private_dir_1.ensureOwnedPrivateDir),
187
- },
188
- ]
189
- : []),
190
- ];
191
- }
192
- /**
193
- * The first socket root whose containment could be established, or `undefined`
194
- * when none could and the caller should fall back to the workspace.
195
- */
196
- function establishSocketRoot(refusals) {
197
- const tiers = socketRootTiers();
198
- for (const [index, tier] of tiers.entries()) {
199
- if (tier.establish(refusals)) {
200
- // `preferred` is set only on a demotion, and names the tier that was
201
- // skipped — the caller records it so a later length failure can say the
202
- // path was not the one Nx wanted.
203
- return index === 0
204
- ? { root: tier.root }
205
- : { root: tier.root, preferred: tiers[0].root };
206
- }
207
- }
208
- return undefined;
209
- }
210
- /** Directories that may not *be* the socket directory, and why. */
211
- function dirsUnusableAsSocketDir() {
212
- const onWindows = process.platform === 'win32';
213
- return [
214
- // Refused even when no peer can reach it: a configured directory becomes the
215
- // socket directory itself, and `removeSocketDir` deletes that recursively —
216
- // here, the user's whole temp directory.
217
- {
218
- dir: tmp_1.tmpdir,
219
- reason: (0, owned_private_dir_1.isPeerWritable)(tmp_1.tmpdir)
220
- ? 'shared-with-other-users'
221
- : 'os-temp-root',
222
- },
223
- {
224
- dir: nx_tmp_dir_1.NX_TMP_DIR,
225
- reason: (0, owned_private_dir_1.isPeerWritable)(nx_tmp_dir_1.NX_TMP_DIR)
226
- ? 'shared-with-other-users'
227
- : 'nx-managed',
228
- },
229
- { dir: nx_tmp_dir_1.NX_USER_TMP_DIR, reason: 'nx-managed' },
230
- { dir: defaultSocketRoot(), reason: 'nx-managed' },
231
- // Skipped on Windows, where the home tier is never offered.
232
- ...(!onWindows && nx_tmp_dir_1.NX_HOME_TMP_DIR
233
- ? [
234
- { dir: nx_tmp_dir_1.NX_HOME_TMP_DIR, reason: 'nx-managed' },
235
- { dir: homeSocketRoot(), reason: 'nx-managed' },
236
- ]
237
- : []),
238
- { dir: native_file_cache_location_1.NATIVE_CACHE_ROOT, reason: 'nx-managed' },
239
- ];
240
- }
241
- /**
242
- * The configured socket dir, normalized. `resolve` strips a trailing slash,
243
- * which would otherwise defeat the `O_NOFOLLOW` guard downstream — this is the
244
- * one socket path built from user input rather than by `join`.
245
- *
246
- * `||` rather than `??`: an empty value means unset. An empty string survives
247
- * `??`, and `resolve('')` is the working directory — which `removeSocketDir`
248
- * then deletes recursively. `NX_SOCKET_DIR=` with no value is ordinary in a
249
- * .env file or a compose environment list.
250
- */
251
- function configuredSocketDir() {
252
- const dir = process.env.NX_SOCKET_DIR || process.env.NX_DAEMON_SOCKET_DIR;
253
- return dir ? (0, path_1.resolve)(dir) : undefined;
254
- }
255
- function socketDirName(root) {
256
- const hasher = (0, crypto_1.createHash)('sha256');
257
- hasher.update(workspace_root_1.workspaceRoot.toLowerCase());
258
- hasher.update(String(process.pid));
259
- const unique = hasher.digest('hex').substring(0, 20);
260
- return (0, path_1.join)(root, unique);
261
- }
262
- function pluginSocketDirName(root) {
263
- // Short so the socket file name still fits under assertValidSocketPath's limit.
264
- const hash = (0, crypto_1.createHash)('sha256')
265
- .update(workspace_root_1.workspaceRoot.toLowerCase())
266
- .digest('hex')
267
- .substring(0, 8);
268
- return (0, path_1.join)(root, hash);
269
- }
270
- /**
271
- * A socket dir under the first usable root, falling back to the workspace data
272
- * dir when no root can be established. Either way it is locked to the current
273
- * user.
274
- */
275
- function socketDirUnderFirstUsableRoot(leafFor) {
276
- const configuredDir = configuredSocketDir();
277
- if (configuredDir !== undefined) {
278
- return createOwnerOnlySocketDir(configuredDir, false);
279
- }
280
- // Cleared here too: the no-tier exit below returns without entering
281
- // createOwnerOnlySocketDir, and a stale value would have assertValidSocketPath
282
- // blame an NX_SOCKET_DIR the user no longer has set.
283
- socketDirFallbackCause = undefined;
284
- refusedConfiguredSocketDir = undefined;
285
- const refusals = [];
286
- const established = establishSocketRoot(refusals);
287
- if (established === undefined) {
288
- return fallBackToWorkspaceSocketDir(socketRootsUnavailable(refusals), undefined, refusals);
289
- }
290
- const dir = createOwnerOnlySocketDir(leafFor(established.root), true, refusals);
291
- // Only when the directory we actually got is the demoted tier: if
292
- // createOwnerOnlySocketDir fell back to the workspace, it recorded its own,
293
- // more specific cause and that one should survive.
294
- if (established.preferred !== undefined &&
295
- socketDirFallbackCause === undefined) {
296
- noteSocketRootDemotion(established.preferred, established.root, refusals);
297
- }
298
- return dir;
299
- }
300
- /**
301
- * Record a successful demotion to a later tier. Verbose, not warn: nothing
302
- * failed. The cause is still set because `assertValidSocketPath` keys its
303
- * "run with --verbose" block off it, and without it a later length failure
304
- * reads as though the user chose the path.
305
- *
306
- * `refusals` can name any of the three directories tier 0 establishes, so it is
307
- * not always `preferred`.
308
- */
309
- function noteSocketRootDemotion(preferred, used, refusals) {
310
- socketDirFallbackCause = new Error(`Nx could not establish its preferred socket root ${preferred}, so it used ${used}.`);
311
- // Lazily required for the import-cycle reason spelled out in
312
- // fallBackToWorkspaceSocketDir below.
313
- const { logger } = require('../utils/logger');
314
- logger.verbose(`Nx could not use the default socket directory ${preferred}. Using ${used} instead.` +
315
- (refusals.length
316
- ? ` ${refusals.map((r) => (0, owned_private_dir_1.describeRefusal)(r)).join('; ')}.`
317
- : ''));
318
- }
319
- function getSocketDir() {
320
- return socketDirUnderFirstUsableRoot(socketDirName);
321
- }
322
- /**
323
- * Plugin worker sockets get their own workspace-scoped directory rather than
324
- * sitting in the shared system temp dir, which cannot be locked down.
325
- */
326
- function getPluginSocketDir() {
327
- return socketDirUnderFirstUsableRoot(pluginSocketDirName);
328
- }
329
- let socketDirFallbackCause;
330
- let refusedConfiguredSocketDir;
331
101
  let warnedAboutWorkspaceFallback = false;
332
102
  let warnedAboutConfiguredSocketDir = false;
333
103
  /**
@@ -339,139 +109,157 @@ function resetSocketDirWarningsForTesting() {
339
109
  warnedAboutWorkspaceFallback = false;
340
110
  warnedAboutConfiguredSocketDir = false;
341
111
  }
342
- function getSocketDirFallbackCause() {
343
- return socketDirFallbackCause;
344
- }
345
112
  /**
346
- * The NX_SOCKET_DIR that was refused, if that is why we are in the fallback.
347
- * Reflects the most recent resolution only — both accessors are cleared at the
348
- * top of every `createOwnerOnlySocketDir` call, and the daemon and plugin socket
349
- * paths each drive one. Read it immediately after the call that produced the
350
- * path, which is what `assertValidSocketPath` does.
113
+ * Required lazily, and only where something is being reported. A static import
114
+ * closes a cycle — utils/logger reads `serverLogger` from daemon/logger while it
115
+ * is still evaluating, and daemon/logger imports this module — which throws
116
+ * whenever `isOnDaemon()` is true as this module loads. Production escapes it
117
+ * only because server.ts sets `global.NX_DAEMON` after its imports, so daemon
118
+ * boot rests on import order.
351
119
  */
352
- function getRefusedConfiguredSocketDir() {
353
- return refusedConfiguredSocketDir;
120
+ function lazyLogger() {
121
+ return require('../utils/logger')
122
+ .logger;
354
123
  }
355
124
  /**
356
- * @param dir the resolved socket directory to create and lock down.
357
- * @param usingDefaultRoot whether `dir` sits under the default root, in which
358
- * case Nx verifies the stable shared container and establishes the
359
- * current user's owner-only roots first.
360
- * @param priorRefusals why the tiers above this one were skipped. Ignored unless
361
- * `usingDefaultRoot`.
125
+ * Report one resolution and hand back the socket path. The native side decides
126
+ * where it is and why; every sentence about it is written here.
362
127
  */
363
- function createOwnerOnlySocketDir(dir, usingDefaultRoot, priorRefusals = []) {
364
- socketDirFallbackCause = undefined;
365
- refusedConfiguredSocketDir = undefined;
366
- // Outside the try so it is not swallowed by the fallback. Exact matches only,
367
- // so the per-user directories under those roots never trip it.
368
- const canonical = (0, owned_private_dir_1.canonicalDir)(dir);
369
- const unusable = dirsUnusableAsSocketDir().find((d) => canonical === (0, owned_private_dir_1.canonicalDir)(d.dir));
370
- if (unusable) {
371
- throw new InvalidSocketDirConfigured(dir, unusable.reason);
372
- }
373
- try {
374
- // A default root has already had its containment established by the tier it
375
- // came from; a configured one is the user's to create.
376
- if (!usingDefaultRoot) {
377
- (0, node_fs_1.mkdirSync)((0, path_1.dirname)(dir), { recursive: true });
378
- }
379
- // Separately from its parents: mkdirSync does not throw on a pre-planted
380
- // symlink, so creating and locking down in one step would adopt it.
381
- const created = (0, owned_private_dir_1.ensureOwnedPrivateDir)(dir);
382
- if (created.status === 'refused') {
383
- throw new owned_private_dir_1.DirectoryRefusedError(created.refusal);
384
- }
385
- return dir;
128
+ function adopt(details) {
129
+ if (details.invalidReason) {
130
+ throw new InvalidSocketDirConfigured(details.path, details.invalidReason);
386
131
  }
387
- catch (e) {
388
- const refusals = e instanceof owned_private_dir_1.DirectoryRefusedError
389
- ? [...priorRefusals, e.refusal]
390
- : priorRefusals;
391
- // Recoverable: fall back to the owner-controlled workspace data dir.
392
- if (usingDefaultRoot) {
393
- return fallBackToWorkspaceSocketDir(refusals.length ? socketRootsUnavailable(refusals) : e, dir, refusals);
394
- }
132
+ if (details.refusedConfiguredDir !== undefined) {
395
133
  // Never swap out a configured directory silently — the substitute is longer
396
134
  // and would resurface as a length complaint about a path the user never set.
397
135
  // Latched: a task-per-PseudoTerminal command resolves this once per task.
398
136
  if (!warnedAboutConfiguredSocketDir) {
399
137
  warnedAboutConfiguredSocketDir = true;
400
- console.warn(`Nx could not use the configured socket directory ${dir}: ${e instanceof Error ? e.message : e}\nFalling back to ${exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE}.`);
138
+ console.warn(`Nx could not use the configured socket directory ${details.refusedConfiguredDir}: ${details.refusalError}\nFalling back to ${details.path}.`);
139
+ }
140
+ return assertWithinBudget(details);
141
+ }
142
+ if (details.usedWorkspaceFallback) {
143
+ const logger = lazyLogger();
144
+ logger.verbose(`Nx could not use the default socket ${details.attemptedDir
145
+ ? `directory ${details.attemptedDir}`
146
+ : 'directories'}. Falling back to ${details.path}.`, fallbackCause(details));
147
+ // Warned rather than verbose: the workspace path grows with checkout depth,
148
+ // so this is where the 95-character budget is most likely to trip, and an
149
+ // allowlist scoped to Nx's usual roots no longer covers it.
150
+ //
151
+ // Latched once per process — neither socket-dir accessor is memoized and one
152
+ // CLI process resolves several, so without it a single command repeats this
153
+ // many times.
154
+ if (!warnedAboutWorkspaceFallback) {
155
+ warnedAboutWorkspaceFallback = true;
156
+ logger.warn([
157
+ `Nx could not use any of its usual socket directories and fell back to ${details.path}.`,
158
+ ...details.remedies,
159
+ (0, is_sandbox_1.isSandbox)()
160
+ ? // NX_HOME_TMP_DIR is undefined when there is no home directory,
161
+ // which is itself one reason this fallback is reached.
162
+ `A sandbox allowlist covering only ${[nx_tmp_dir_1.NX_TMP_DIR, nx_tmp_dir_1.NX_HOME_TMP_DIR]
163
+ .filter(Boolean)
164
+ .join(' or ')} does not cover this path.`
165
+ : undefined,
166
+ 'Run with --verbose to see why the others were rejected.',
167
+ ]
168
+ .filter(Boolean)
169
+ .join(' '));
401
170
  }
402
- // Tracked separately from socketDirFallbackCause so the length error stops
403
- // telling someone to shorten an NX_SOCKET_DIR that was refused for another
404
- // reason.
405
- refusedConfiguredSocketDir = dir;
406
- return establishWorkspaceSocketDir(e);
171
+ return assertWithinBudget(details);
407
172
  }
173
+ if (details.demotedFrom !== undefined) {
174
+ // Verbose, not warn: nothing failed.
175
+ lazyLogger().verbose(`Nx could not use the default socket directory ${details.demotedFrom}. Using ${details.path} instead.` +
176
+ (details.refusalDetails ? ` ${details.refusalDetails}.` : ''));
177
+ }
178
+ return assertWithinBudget(details);
408
179
  }
409
180
  /**
410
- * The refusals as one error. `AggregateError` because there genuinely are
411
- * several — one per root the chain tried — and its `errors` stay inspectable
412
- * rather than being flattened into the message that `--verbose` prints.
181
+ * Why this is not the preferred location, or `undefined` when it is. Kept as
182
+ * the error's `cause` so `--verbose` can show what the sentence summarizes.
413
183
  */
414
- function socketRootsUnavailable(refusals) {
415
- return new AggregateError(refusals.map((r) => new owned_private_dir_1.DirectoryRefusedError(r)), `Nx could not establish any of its default socket directories: ${refusals
416
- .map((r) => (0, owned_private_dir_1.describeRefusal)(r))
417
- .join('; ')}.`);
184
+ function fallbackCause(details) {
185
+ if (details.refusalError !== undefined) {
186
+ return new Error(details.refusalError);
187
+ }
188
+ if (details.usedWorkspaceFallback) {
189
+ return new Error(`Nx could not establish any of its default socket directories${details.refusalDetails ? `: ${details.refusalDetails}` : ''}.`);
190
+ }
191
+ if (details.demotedFrom !== undefined) {
192
+ return new Error(`Nx could not establish its preferred socket root ${details.demotedFrom}, so it used ${details.dir}.`);
193
+ }
194
+ return undefined;
418
195
  }
419
196
  /**
420
- * The last resort once no default root could be used. Retains the cause so
421
- * `assertValidSocketPath` can explain a length failure the user did not cause.
197
+ * The native side measures; the sentence is decided here, from the same
198
+ * resolution that produced the path. Which advice is correct turns on *why*
199
+ * the path is long: a user who already set `NX_SOCKET_DIR` and had it refused
200
+ * for an unrelated reason is not helped by being told to set a shorter one.
422
201
  */
423
- function fallBackToWorkspaceSocketDir(cause, attempted, refusals = []) {
424
- socketDirFallbackCause = cause;
425
- // Required lazily, and only on a path that is already failing. A static
426
- // import closes a cycle — utils/logger reads `serverLogger` from
427
- // daemon/logger while it is still evaluating, and daemon/logger imports this
428
- // module — which throws whenever `isOnDaemon()` is true as this module loads.
429
- // Production escapes it only because server.ts sets `global.NX_DAEMON` after
430
- // its imports, so daemon boot rests on import order.
431
- const { logger } = require('../utils/logger');
432
- logger.verbose(`Nx could not use the default socket ${attempted ? `directory ${attempted}` : 'directories'}. Falling back to ${exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE}.`, cause);
433
- // Warned rather than verbose: the workspace path grows with checkout depth, so
434
- // this is where the 95-character budget is most likely to trip, and an
435
- // allowlist scoped to Nx's usual roots no longer covers it.
436
- //
437
- // Latched once per process — neither socket-dir accessor is memoized and one
438
- // CLI process resolves several, so without it a single command repeats this
439
- // many times.
440
- if (!warnedAboutWorkspaceFallback) {
441
- warnedAboutWorkspaceFallback = true;
442
- logger.warn([
443
- `Nx could not use any of its usual socket directories and fell back to ${exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE}.`,
444
- ...new Set(refusals.map(owned_private_dir_1.remedyFor).filter(Boolean)),
445
- (0, is_sandbox_1.isSandbox)()
446
- ? // NX_HOME_TMP_DIR is undefined when there is no home directory,
447
- // which is itself one reason this fallback is reached.
448
- `A sandbox allowlist covering only ${[nx_tmp_dir_1.NX_TMP_DIR, nx_tmp_dir_1.NX_HOME_TMP_DIR]
449
- .filter(Boolean)
450
- .join(' or ')} does not cover this path.`
451
- : undefined,
452
- 'Run with --verbose to see why the others were rejected.',
453
- ]
454
- .filter(Boolean)
455
- .join(' '));
202
+ function assertWithinBudget(details) {
203
+ if (!details.tooLong) {
204
+ return details.path;
456
205
  }
457
- return establishWorkspaceSocketDir(cause);
206
+ const cause = fallbackCause(details);
207
+ throw new Error([
208
+ 'Attempted to open socket that exceeds the maximum socket length.',
209
+ // A refused NX_SOCKET_DIR also lands in the workspace, but no default
210
+ // directory was tried, and the line below already names the cause.
211
+ ...((details.usedWorkspaceFallback &&
212
+ details.refusedConfiguredDir === undefined) ||
213
+ details.demotedFrom !== undefined
214
+ ? [
215
+ `Nx fell back to ${details.dir} because the default socket directory could not be used.`,
216
+ 'Run the command with --verbose to see why the default directory was rejected.',
217
+ ]
218
+ : []),
219
+ '',
220
+ details.refusedConfiguredDir === undefined
221
+ ? `Set NX_SOCKET_DIR to a shorter path (e.g. ${process.platform === 'win32' ? '%TMP%/nx-tmp' : '/tmp/nx-tmp'}) to avoid this issue.`
222
+ : // Saying "set a shorter path" here would be advice they already
223
+ // followed: they set one, and it was refused for another reason.
224
+ `The directory set in NX_SOCKET_DIR (${details.refusedConfiguredDir}) could not be used — see the warning above — so Nx fell back to a longer path.\nPoint NX_SOCKET_DIR at a short directory your user owns.`,
225
+ ].join('\n'), cause === undefined ? undefined : { cause });
458
226
  }
459
- function establishWorkspaceSocketDir(cause) {
460
- (0, node_fs_1.mkdirSync)((0, path_1.dirname)(exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE), { recursive: true });
461
- // The fallback is only safe if it passes the same checks the primary did.
462
- const established = (0, owned_private_dir_1.ensureOwnedPrivateDir)(exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE);
463
- if (established.status === 'refused') {
464
- // With the remedy: this is the one refusal path with nowhere left to fall,
465
- // so it is where the user most needs it and the only one that cannot reach
466
- // the warning's copy.
467
- const remedy = (0, owned_private_dir_1.remedyFor)(established.refusal);
468
- throw new Error(`Nx could not establish a socket directory: ${(0, owned_private_dir_1.describeRefusal)(established.refusal)}.${remedy ? ` ${remedy}` : ''}`, { cause });
469
- }
470
- return exports.DAEMON_DIR_FOR_CURRENT_WORKSPACE;
227
+ /** The daemon's own socket. */
228
+ function getDaemonSocketPath() {
229
+ return adopt((0, native_1.resolveDaemonSocketPath)(workspace_root_1.workspaceRoot));
230
+ }
231
+ /** A forked task process's socket, in the daemon's per-run directory. */
232
+ function getForkedProcessSocketPath(id) {
233
+ return adopt((0, native_1.resolveForkedProcessSocketPath)(workspace_root_1.workspaceRoot, id));
234
+ }
235
+ /**
236
+ * Plugin worker sockets get their own workspace-scoped directory rather than
237
+ * sitting in the shared system temp dir, which cannot be locked down.
238
+ */
239
+ function getPluginSocketPath(id) {
240
+ return adopt((0, native_1.resolvePluginSocketPath)(workspace_root_1.workspaceRoot, id));
241
+ }
242
+ /**
243
+ * Where the Nx Console socket lives, for whoever binds or connects to it.
244
+ *
245
+ * Nx Console is the server here and Nx the client, so the extension listens
246
+ * where this says and Nx dials it.
247
+ *
248
+ * @param workspaceRootOverride the workspace to resolve for, when the caller is
249
+ * not itself running inside it (Nx Console runs in the editor's extension host)
250
+ * @param env the environment to read `NX_SOCKET_DIR` from, for callers that
251
+ * load a workspace `.env` into a copy rather than into `process.env`
252
+ */
253
+ function getNxConsoleSocketPath(workspaceRootOverride, env) {
254
+ return adopt((0, native_1.resolveNxConsoleSocketPath)(workspaceRootOverride ?? workspace_root_1.workspaceRoot, env));
471
255
  }
472
256
  function removeSocketDir() {
473
257
  try {
474
- (0, node_fs_1.rmSync)(getSocketDir(), { recursive: true, force: true });
258
+ // The directory, not the socket: the daemon's whole per-run directory goes.
259
+ (0, node_fs_1.rmSync)((0, native_1.resolveDaemonSocketPath)(workspace_root_1.workspaceRoot).dir, {
260
+ recursive: true,
261
+ force: true,
262
+ });
475
263
  }
476
264
  catch (e) { }
477
265
  }
@@ -115,7 +115,7 @@ export type { LifeCycle, TaskMetadata } from './tasks-runner/life-cycle';
115
115
  export { CompositeLifeCycle } from './tasks-runner/life-cycle';
116
116
  export { createRunManyDynamicOutputRenderer } from './tasks-runner/life-cycles/dynamic-run-many-terminal-output-life-cycle';
117
117
  export type { TaskStatus, TasksRunner } from './tasks-runner/tasks-runner';
118
- export type { DependsOnEntryLocation, NormalizedTargetDependencyConfig, } from './tasks-runner/utils';
118
+ export type { NormalizedTargetDependencyConfig } from './tasks-runner/utils';
119
119
  export { calculateReverseDeps, createTaskId, expandDependencyConfigSyntaxSugar, expandInitiatingTasksThroughNoop, expandWildcardTargetConfiguration, getCliPath, getCustomHasher, getDependencyConfigs, getExecutorForTask, getExecutorNameForTask, getOutputs, getPrintableCommandArgsForTask, getSerializedArgsForTask, getTargetConfigurationForTask, getUnparsedOverrideArgs, isCacheableTask, normalizeDependencyConfigDefinition, normalizeDependencyConfigProjects, normalizeTargetDependencyWithStringProjects, readProjectAndTargetFromTargetString, removeTasksFromTaskGraph, shouldStreamOutput, transformLegacyOutputs, unparse, validateOutputs, } from './tasks-runner/utils';
120
120
  export { getLastValueFromAsyncIterableIterator } from './utils/async-iterator';
121
121
  export { workspaceDataDirectory } from './utils/cache-directory';
@@ -251,6 +251,7 @@ Object.defineProperty(exports, "expandDependencyConfigSyntaxSugar", { enumerable
251
251
  Object.defineProperty(exports, "expandInitiatingTasksThroughNoop", { enumerable: true, get: function () { return utils_2.expandInitiatingTasksThroughNoop; } });
252
252
  Object.defineProperty(exports, "expandWildcardTargetConfiguration", { enumerable: true, get: function () { return utils_2.expandWildcardTargetConfiguration; } });
253
253
  Object.defineProperty(exports, "getCliPath", { enumerable: true, get: function () { return utils_2.getCliPath; } });
254
+ // TODO(v25): remove custom hasher support.
254
255
  Object.defineProperty(exports, "getCustomHasher", { enumerable: true, get: function () { return utils_2.getCustomHasher; } });
255
256
  Object.defineProperty(exports, "getDependencyConfigs", { enumerable: true, get: function () { return utils_2.getDependencyConfigs; } });
256
257
  Object.defineProperty(exports, "getExecutorForTask", { enumerable: true, get: function () { return utils_2.getExecutorForTask; } });
@@ -3,21 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hashArray = hashArray;
4
4
  exports.hashObject = hashObject;
5
5
  exports.hashFile = hashFile;
6
+ // Loaded on first use; a top-level import breaks unit tests in the Nx repo.
7
+ // Cached because each `require` call re-resolves the module.
8
+ let native;
9
+ function getNative() {
10
+ return (native ??= require('../native'));
11
+ }
6
12
  function hashArray(content) {
7
- // Import as needed. There is also an issue running unit tests in Nx repo if this is a top-level import.
8
- const { hashArray } = require('../native');
9
- return hashArray(content);
13
+ return getNative().hashArray(content);
10
14
  }
11
15
  function hashObject(obj) {
12
- const { hashArray } = require('../native');
13
16
  const parts = [];
14
17
  for (const key of Object.keys(obj ?? {}).sort()) {
15
18
  parts.push(key);
16
19
  parts.push(JSON.stringify(obj[key]));
17
20
  }
18
- return hashArray(parts);
21
+ return getNative().hashArray(parts);
19
22
  }
20
23
  function hashFile(filePath) {
21
- const { hashFile } = require('../native');
22
- return hashFile(filePath);
24
+ return getNative().hashFile(filePath);
23
25
  }
@@ -9,6 +9,7 @@ const native_1 = require("../native");
9
9
  const project_graph_1 = require("../project-graph/project-graph");
10
10
  const task_io_service_1 = require("../tasks-runner/task-io-service");
11
11
  const task_env_1 = require("../tasks-runner/task-env");
12
+ // TODO(v25): remove custom hasher support.
12
13
  const utils_1 = require("../tasks-runner/utils");
13
14
  const db_connection_1 = require("../utils/db-connection");
14
15
  const task_hasher_1 = require("./task-hasher");
@@ -32,6 +33,7 @@ async function hashTasksThatDoNotDependOnOutputsOfOtherTasks(hasher, projectGrap
32
33
  ? new Set((0, native_1.getUltracacheDeferredTaskIds)(ultracacheConfigurations, taskGraph))
33
34
  : null;
34
35
  const tasks = Object.values(taskGraph.tasks);
36
+ // TODO(v25): remove custom hasher support.
35
37
  const tasksWithHashers = await Promise.all(tasks.map(async (task) => {
36
38
  const customHasher = (0, utils_1.getCustomHasher)(task, projects);
37
39
  return { task, customHasher };
@@ -81,6 +83,7 @@ function readsDependencyOutputs(task, taskGraph, projectGraph, nxJson) {
81
83
  async function hashTask(hasher, projectGraph, taskGraph, task, env, taskDetails) {
82
84
  performance.mark('hashSingleTask:start');
83
85
  const projectsConfigurations = (0, project_graph_1.readProjectsConfigurationFromProjectGraph)(projectGraph);
86
+ // TODO(v25): remove custom hasher support.
84
87
  const customHasher = (0, utils_1.getCustomHasher)(task, projectsConfigurations.projects);
85
88
  const { value, details, inputs } = await (customHasher
86
89
  ? customHasher(task, {
@@ -126,6 +129,7 @@ async function hashTasks(hasher, projectGraph, taskGraph, perTaskEnvs, taskDetai
126
129
  const projectsConfigurations = (0, project_graph_1.readProjectsConfigurationFromProjectGraph)(projectGraph);
127
130
  const nxJson = (0, nx_json_1.readNxJson)();
128
131
  const tasks = (tasksToHashOverride ?? Object.values(taskGraph.tasks)).filter((task) => !task.hash);
132
+ // TODO(v25): remove custom hasher support.
129
133
  // Separate tasks with custom hashers from those without
130
134
  const tasksWithCustomHashers = [];
131
135
  const tasksWithoutCustomHashers = [];