nx 23.3.0 → 24.0.0-beta.1

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 (114) hide show
  1. package/README.md +3 -6
  2. package/dist/nx-cloud-internals.d.ts +1 -0
  3. package/dist/nx-cloud-internals.js +7 -1
  4. package/dist/src/adapter/ngcli-adapter.js +1 -0
  5. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +0 -12
  6. package/dist/src/command-line/graph/graph.js +3 -5
  7. package/dist/src/command-line/init/command-object.js +0 -12
  8. package/dist/src/command-line/init/init-v2.js +0 -2
  9. package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +1 -1
  10. package/dist/src/command-line/migrate/agentic/close-agent-session.js +4 -3
  11. package/dist/src/command-line/migrate/agentic/master/spawn-master.js +76 -15
  12. package/dist/src/command-line/migrate/execute-migration.d.ts +2 -2
  13. package/dist/src/command-line/migrate/execute-migration.js +18 -10
  14. package/dist/src/command-line/migrate/migrate-commits.d.ts +4 -1
  15. package/dist/src/command-line/migrate/migrate-commits.js +10 -3
  16. package/dist/src/command-line/migrate/migrate.js +43 -6
  17. package/dist/src/command-line/migrate/run/agent-output.d.ts +1 -0
  18. package/dist/src/command-line/migrate/run/agent-output.js +5 -0
  19. package/dist/src/command-line/migrate/run/broker.d.ts +14 -5
  20. package/dist/src/command-line/migrate/run/broker.js +68 -20
  21. package/dist/src/command-line/migrate/run/index.d.ts +4 -3
  22. package/dist/src/command-line/migrate/run/index.js +8 -1
  23. package/dist/src/command-line/migrate/run/orchestrator.js +72 -13
  24. package/dist/src/command-line/migrate/run/runbook.js +19 -0
  25. package/dist/src/command-line/migrate/run/state-machine.js +4 -1
  26. package/dist/src/command-line/migrate/run/util.d.ts +3 -0
  27. package/dist/src/command-line/migrate/run/util.js +7 -1
  28. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +17 -12
  29. package/dist/src/command-line/release/utils/remote-release-clients/github.js +5 -6
  30. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +5 -6
  31. package/dist/src/command-line/run/executor-utils.js +1 -0
  32. package/dist/src/command-line/run/run-one.d.ts +20 -1
  33. package/dist/src/command-line/run/run-one.js +153 -5
  34. package/dist/src/command-line/show/show-target/info.js +1 -1
  35. package/dist/src/command-line/show/show-target/utils.js +1 -0
  36. package/dist/src/command-line/watch/command-object.js +3 -22
  37. package/dist/src/command-line/watch/watch.d.ts +0 -7
  38. package/dist/src/command-line/watch/watch.js +1 -1
  39. package/dist/src/config/misc-interfaces.d.ts +12 -0
  40. package/dist/src/config/task-graph.d.ts +1 -1
  41. package/dist/src/config/workspace-json-project-json.d.ts +65 -0
  42. package/dist/src/core/graph/main.js +1 -1
  43. package/dist/src/daemon/client/client.d.ts +9 -0
  44. package/dist/src/daemon/client/client.js +89 -37
  45. package/dist/src/daemon/server/shutdown-utils.js +0 -3
  46. package/dist/src/daemon/socket-utils.d.ts +9 -4
  47. package/dist/src/daemon/socket-utils.js +12 -49
  48. package/dist/src/daemon/tmp-dir.d.ts +19 -14
  49. package/dist/src/daemon/tmp-dir.js +142 -354
  50. package/dist/src/devkit-exports.d.ts +1 -1
  51. package/dist/src/devkit-internals.d.ts +1 -1
  52. package/dist/src/devkit-internals.js +1 -0
  53. package/dist/src/hasher/check-task-files.js +4 -4
  54. package/dist/src/hasher/file-hasher.js +9 -7
  55. package/dist/src/hasher/hash-task.js +4 -0
  56. package/dist/src/hasher/native-task-hasher-impl.js +3 -1
  57. package/dist/src/migrations/update-24-0-0/find-custom-hashers.d.ts +3 -0
  58. package/dist/src/migrations/update-24-0-0/find-custom-hashers.js +100 -0
  59. package/dist/src/migrations/update-24-0-0/find-custom-hashers.md +63 -0
  60. package/dist/src/migrations/update-24-0-0/move-custom-hashers-to-inputs.md +145 -0
  61. package/dist/src/native/index.d.ts +180 -12
  62. package/dist/src/native/native-bindings.js +8 -0
  63. package/dist/src/native/nx.wasi-browser.js +1 -0
  64. package/dist/src/native/nx.wasi.cjs +1 -0
  65. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  66. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  67. package/dist/src/nx-cloud/update-manager.d.ts +7 -0
  68. package/dist/src/nx-cloud/update-manager.js +237 -50
  69. package/dist/src/project-graph/affected/affected-tasks.js +1 -0
  70. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +7 -0
  71. package/dist/src/tasks-runner/create-task-graph.d.ts +8 -7
  72. package/dist/src/tasks-runner/create-task-graph.js +47 -26
  73. package/dist/src/tasks-runner/init-tasks-runner.js +13 -0
  74. package/dist/src/tasks-runner/life-cycle.d.ts +4 -1
  75. package/dist/src/tasks-runner/life-cycle.js +7 -0
  76. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +5 -2
  77. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +13 -7
  78. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +4 -3
  79. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +9 -2
  80. package/dist/src/tasks-runner/readiness/probes.d.ts +9 -0
  81. package/dist/src/tasks-runner/readiness/probes.js +62 -0
  82. package/dist/src/tasks-runner/readiness/ready-when.d.ts +38 -0
  83. package/dist/src/tasks-runner/readiness/ready-when.js +190 -0
  84. package/dist/src/tasks-runner/run-command.js +5 -4
  85. package/dist/src/tasks-runner/running-tasks/node-child-process.js +5 -4
  86. package/dist/src/tasks-runner/task-env.d.ts +10 -0
  87. package/dist/src/tasks-runner/task-env.js +26 -5
  88. package/dist/src/tasks-runner/task-graph-utils.d.ts +9 -6
  89. package/dist/src/tasks-runner/task-graph-utils.js +17 -12
  90. package/dist/src/tasks-runner/task-orchestrator.d.ts +37 -8
  91. package/dist/src/tasks-runner/task-orchestrator.js +460 -76
  92. package/dist/src/tasks-runner/tasks-schedule.d.ts +18 -1
  93. package/dist/src/tasks-runner/tasks-schedule.js +64 -6
  94. package/dist/src/tasks-runner/utils.d.ts +20 -5
  95. package/dist/src/tasks-runner/utils.js +70 -35
  96. package/dist/src/ultracache/overrides.js +4 -0
  97. package/dist/src/utils/child-process.js +1 -0
  98. package/dist/src/utils/git-utils.d.ts +6 -1
  99. package/dist/src/utils/git-utils.js +26 -5
  100. package/dist/src/utils/owned-private-dir.d.ts +1 -17
  101. package/dist/src/utils/owned-private-dir.js +10 -62
  102. package/dist/src/utils/perf-hooks.d.ts +1 -0
  103. package/dist/src/utils/plugins/core-plugins.js +1 -1
  104. package/dist/src/utils/spawn-without-terminal.d.ts +27 -0
  105. package/dist/src/utils/spawn-without-terminal.js +110 -0
  106. package/dist/src/utils/string-similarity.d.ts +38 -0
  107. package/dist/src/utils/string-similarity.js +108 -0
  108. package/migrations.json +7 -0
  109. package/package.json +11 -20
  110. package/readme-template.md +3 -3
  111. package/schemas/nx-schema.json +70 -0
  112. package/schemas/project-schema.json +67 -0
  113. package/dist/src/tasks-runner/legacy-depends-on-warning.d.ts +0 -18
  114. package/dist/src/tasks-runner/legacy-depends-on-warning.js +0 -109
@@ -2,14 +2,22 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DirectoryRefusedError = void 0;
4
4
  exports.describeRefusal = describeRefusal;
5
- exports.remedyFor = remedyFor;
6
5
  exports.canonicalDir = canonicalDir;
7
6
  exports.isSafeSharedRoot = isSafeSharedRoot;
8
- exports.isPeerWritable = isPeerWritable;
9
7
  exports.ensureSafeSharedRoot = ensureSafeSharedRoot;
10
8
  exports.isOwnedRealDirectory = isOwnedRealDirectory;
11
9
  exports.getUserSegment = getUserSegment;
12
10
  exports.ensureOwnedPrivateDir = ensureOwnedPrivateDir;
11
+ /**
12
+ * Directory guards for Nx's runtime state: create a directory only when it is
13
+ * ours and unreachable by other users on this machine.
14
+ *
15
+ * `native/utils/owned_dir.rs` implements the same rules and must change with
16
+ * this file. This copy exists because the native binding loader has to place
17
+ * and lock down the `.node` before Rust can be called, and `cache-directory.ts`,
18
+ * `git-utils.ts` and `nx-tmp-dir.ts` share it. Node builtins only, for the
19
+ * loader's sake.
20
+ */
13
21
  const node_fs_1 = require("node:fs");
14
22
  const node_os_1 = require("node:os");
15
23
  const node_path_1 = require("node:path");
@@ -56,42 +64,6 @@ function describeRefusal(r) {
56
64
  }
57
65
  }
58
66
  }
59
- /** Single-quoted for a shell, so a path with a space or quote survives a paste. */
60
- const shellQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
61
- /** What the user can do about a refusal, or `undefined` when there is nothing. */
62
- function remedyFor(r) {
63
- switch (r.kind) {
64
- case 'not-a-directory':
65
- return r.symlink
66
- ? `${r.dir} is a symlink where Nx expects a directory. If you did not create it, treat it as hostile: remove the link itself (not what it points at) and run the command again.`
67
- : undefined;
68
- case 'foreign-owner':
69
- return `${r.dir} belongs to another user on this machine, so Nx cannot keep its own directory there. Set NX_SOCKET_DIR to a short directory your user owns, or move it aside — which you can do yourself if you own the directory it sits in, and otherwise needs an administrator.`;
70
- case 'not-tightenable':
71
- // Both producers reach here having already established that the directory
72
- // is ours, so `chmod` is the user's to run. Names no cause: one producer
73
- // is a mount that discards the mode, the other is any `chmod` failure.
74
- return `${r.dir} is reachable by other users (mode ${asMode(r.mode)}) and Nx could not restrict it. Run \`chmod 0700 ${shellQuote(r.dir)}\` and try again; if the mode does not stick, set NX_SOCKET_DIR to a short directory on a filesystem that keeps POSIX permissions.`;
75
- case 'foreign-shared-container':
76
- // Unreachable today: `isSafeSharedRoot` denies with this kind only when
77
- // `stats.uid !== 0`, so a root-owned container never reaches here.
78
- if (r.uid === 0) {
79
- return undefined;
80
- }
81
- const q = shellQuote(r.dir);
82
- return `${r.dir} belongs to another user on this machine, so Nx cannot keep a private directory beneath it. Ask an administrator to hand it to root with \`sudo chown root ${q} && sudo chmod 1777 ${q}\`; every user can then keep their own directory under it.`;
83
- case 'not-created':
84
- case 'not-inspectable':
85
- case 'peer-writable-not-sticky':
86
- return undefined;
87
- default: {
88
- // Matches `describeRefusal`'s arm, so a new kind cannot ship with a
89
- // sentence and no advice — which is how `not-tightenable` went unadvised.
90
- const unhandled = r;
91
- return undefined;
92
- }
93
- }
94
- }
95
67
  /** One refusal as an `Error`, so several can travel in an `AggregateError`. */
96
68
  class DirectoryRefusedError extends Error {
97
69
  constructor(refusal) {
@@ -207,30 +179,6 @@ function isSafeSharedRoot(dir) {
207
179
  return deny({ kind: 'not-inspectable', dir, code: e?.code });
208
180
  }
209
181
  }
210
- /**
211
- * Whether other users on this machine can write into `dir`. Gates whether a
212
- * refusal message may cite other users, so it must not over-report: `false` on
213
- * Windows, where libuv synthesizes `st_mode` from the READONLY attribute and
214
- * reports an ordinary per-account directory as `0666`, and `false` on a path
215
- * that cannot be inspected.
216
- *
217
- * `statSync`, not `lstatSync`: the question is about the directory that will be
218
- * used, not the link pointing at it. Link modes mislead in both directions —
219
- * Linux creates symlinks `0777`, macOS applies the umask. Nothing here decides
220
- * whether a path is accepted, so following the link costs nothing the guards
221
- * do not already re-check.
222
- */
223
- function isPeerWritable(dir) {
224
- if (process.platform === 'win32') {
225
- return false;
226
- }
227
- try {
228
- return !!((0, node_fs_1.statSync)(dir).mode & 0o022);
229
- }
230
- catch {
231
- return false;
232
- }
233
- }
234
182
  /**
235
183
  * Create a shared container as sticky + world-writable if it does not exist,
236
184
  * and report whether the resulting path is safe for the current user.
@@ -1,3 +1,4 @@
1
+ // tsc doesn't emit hand-written .d.ts files; assets.json copies this one into dist.
1
2
  import type { EventParameters } from '../analytics/analytics';
2
3
 
3
4
  export interface TrackedDetail extends EventParameters {
@@ -80,7 +80,7 @@ exports.CORE_PLUGINS = [
80
80
  },
81
81
  {
82
82
  name: '@nx/remix',
83
- capabilities: 'executors,generators',
83
+ capabilities: 'generators',
84
84
  },
85
85
  {
86
86
  name: '@nx/rollup',
@@ -0,0 +1,27 @@
1
+ import { type ChildProcess, type SpawnOptions } from 'child_process';
2
+ /**
3
+ * Runs `command` in a shell that cannot reach the terminal nx shares with
4
+ * another program, so a prompt fails instead of waiting for keys that program
5
+ * reads. On POSIX the shell leads a new session with no controlling terminal,
6
+ * and loses every environment variable that names nx's terminal. Windows
7
+ * spawns it as usual, and with no stdio inherited it gets no console, so the
8
+ * terminal's Ctrl+C misses it there too. Whoever shares the terminal forwards
9
+ * signals with `signalCommandsWithoutTerminal`, and an nx that exits first
10
+ * kills each command whose output is still open.
11
+ */
12
+ export declare function spawnWithoutTerminal(command: string, options: Omit<SpawnOptions, 'shell' | 'detached'>): ChildProcess;
13
+ /**
14
+ * Sends `signal` to every command started by `spawnWithoutTerminal` whose
15
+ * output is still open: to its process group on POSIX, and on a SIGKILL also to
16
+ * the processes still descending from its shell, since a parent killed outright
17
+ * cannot stop the ones it started in groups of their own. Windows has no
18
+ * process groups, so there those descendants are terminated, whatever the
19
+ * signal.
20
+ */
21
+ export declare function signalCommandsWithoutTerminal(signal: NodeJS.Signals): void;
22
+ /**
23
+ * `env` without the variables whose value is the path of one of `terminals`
24
+ * (device ids). A process outside the new session, such as gpg-agent reading
25
+ * `GPG_TTY`, would otherwise open that terminal itself.
26
+ */
27
+ export declare function withoutTerminalPaths(env: NodeJS.ProcessEnv, terminals: ReadonlySet<number>): NodeJS.ProcessEnv;
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.spawnWithoutTerminal = spawnWithoutTerminal;
4
+ exports.signalCommandsWithoutTerminal = signalCommandsWithoutTerminal;
5
+ exports.withoutTerminalPaths = withoutTerminalPaths;
6
+ const child_process_1 = require("child_process");
7
+ const fs_1 = require("fs");
8
+ const tty_1 = require("tty");
9
+ // The commands spawned below whose output is still open, by pid. On POSIX each
10
+ // one leads its own process group.
11
+ const commands = new Map();
12
+ /**
13
+ * Runs `command` in a shell that cannot reach the terminal nx shares with
14
+ * another program, so a prompt fails instead of waiting for keys that program
15
+ * reads. On POSIX the shell leads a new session with no controlling terminal,
16
+ * and loses every environment variable that names nx's terminal. Windows
17
+ * spawns it as usual, and with no stdio inherited it gets no console, so the
18
+ * terminal's Ctrl+C misses it there too. Whoever shares the terminal forwards
19
+ * signals with `signalCommandsWithoutTerminal`, and an nx that exits first
20
+ * kills each command whose output is still open.
21
+ */
22
+ function spawnWithoutTerminal(command, options) {
23
+ const child = (0, child_process_1.spawn)(command, {
24
+ ...options,
25
+ shell: true,
26
+ windowsHide: true,
27
+ detached: process.platform !== 'win32',
28
+ ...(process.platform !== 'win32' && {
29
+ env: withoutTerminalPaths(options.env ?? process.env, stdioTerminals()),
30
+ }),
31
+ });
32
+ const pid = child.pid;
33
+ if (pid !== undefined) {
34
+ if (commands.size === 0)
35
+ process.on('exit', killCommands);
36
+ commands.set(pid, child);
37
+ child.once('close', () => {
38
+ commands.delete(pid);
39
+ if (commands.size === 0)
40
+ process.removeListener('exit', killCommands);
41
+ });
42
+ }
43
+ return child;
44
+ }
45
+ /**
46
+ * Sends `signal` to every command started by `spawnWithoutTerminal` whose
47
+ * output is still open: to its process group on POSIX, and on a SIGKILL also to
48
+ * the processes still descending from its shell, since a parent killed outright
49
+ * cannot stop the ones it started in groups of their own. Windows has no
50
+ * process groups, so there those descendants are terminated, whatever the
51
+ * signal.
52
+ */
53
+ function signalCommandsWithoutTerminal(signal) {
54
+ for (const [pid, child] of commands) {
55
+ // Tree first: killing the group reparents its members' children.
56
+ if (process.platform === 'win32' || signal === 'SIGKILL') {
57
+ killTree(child, pid, signal);
58
+ }
59
+ if (process.platform !== 'win32') {
60
+ try {
61
+ process.kill(-pid, signal);
62
+ }
63
+ catch {
64
+ // The command already ended; its `close` is on the way.
65
+ }
66
+ }
67
+ }
68
+ }
69
+ // Only while the shell lives: once it has exited, its pid can be reused.
70
+ function killTree(child, pid, signal) {
71
+ if (child.exitCode !== null || child.signalCode !== null)
72
+ return;
73
+ try {
74
+ const { killProcessTree } = require('../native');
75
+ killProcessTree(pid, signal);
76
+ }
77
+ catch {
78
+ // No native killer in this build; on POSIX the group signal still goes out.
79
+ }
80
+ }
81
+ function killCommands() {
82
+ signalCommandsWithoutTerminal('SIGKILL');
83
+ }
84
+ function stdioTerminals() {
85
+ const devices = new Set();
86
+ for (const fd of [0, 1, 2]) {
87
+ if ((0, tty_1.isatty)(fd))
88
+ devices.add((0, fs_1.fstatSync)(fd).rdev);
89
+ }
90
+ return devices;
91
+ }
92
+ /**
93
+ * `env` without the variables whose value is the path of one of `terminals`
94
+ * (device ids). A process outside the new session, such as gpg-agent reading
95
+ * `GPG_TTY`, would otherwise open that terminal itself.
96
+ */
97
+ function withoutTerminalPaths(env, terminals) {
98
+ return Object.fromEntries(Object.entries(env).filter(([, value]) => !namesTerminal(value, terminals)));
99
+ }
100
+ function namesTerminal(value, terminals) {
101
+ if (!value?.startsWith('/dev/'))
102
+ return false;
103
+ try {
104
+ const stat = (0, fs_1.statSync)(value);
105
+ return stat.isCharacterDevice() && terminals.has(stat.rdev);
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Computes the Levenshtein edit distance between two strings, i.e. the minimum
3
+ * number of single-character insertions, deletions, or substitutions required
4
+ * to turn `a` into `b`.
5
+ */
6
+ export declare function levenshteinDistance(a: string, b: string): number;
7
+ /**
8
+ * Ranks every candidate by how closely it resembles `input`, from closest to
9
+ * furthest, tie-broken alphabetically so the order is deterministic. Each entry
10
+ * keeps its computed distance so callers can reuse it (e.g. to both pick the
11
+ * closest match and order a list) without recomputing.
12
+ */
13
+ export declare function rankByDistance(input: string, candidates: readonly string[]): {
14
+ candidate: string;
15
+ distance: number;
16
+ }[];
17
+ /**
18
+ * Whether a candidate at edit `distance` from `input` is close enough to be a
19
+ * useful suggestion. Exposed so callers that already have a distance (e.g. from
20
+ * `rankByDistance`) can gate on it without recomputing.
21
+ */
22
+ export declare function isWithinSuggestionThreshold(input: string, candidate: string, distance: number): boolean;
23
+ /**
24
+ * Ranks the candidates that are similar enough to `input` to be worth
25
+ * suggesting, closest first (ties broken alphabetically). Each entry keeps its
26
+ * distance so callers can compare suggestions found for different forms of the
27
+ * same input without recomputing.
28
+ */
29
+ export declare function rankSuggestions(input: string, candidates: readonly string[]): {
30
+ candidate: string;
31
+ distance: number;
32
+ }[];
33
+ /**
34
+ * Returns up to `limit` candidates that most closely resemble `input`, sorted
35
+ * from closest to furthest. Candidates that are not similar enough to be a
36
+ * useful suggestion are excluded.
37
+ */
38
+ export declare function findClosestMatches(input: string, candidates: readonly string[], limit?: number): string[];
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.levenshteinDistance = levenshteinDistance;
4
+ exports.rankByDistance = rankByDistance;
5
+ exports.isWithinSuggestionThreshold = isWithinSuggestionThreshold;
6
+ exports.rankSuggestions = rankSuggestions;
7
+ exports.findClosestMatches = findClosestMatches;
8
+ /**
9
+ * Computes the Levenshtein edit distance between two strings, i.e. the minimum
10
+ * number of single-character insertions, deletions, or substitutions required
11
+ * to turn `a` into `b`.
12
+ */
13
+ function levenshteinDistance(a, b) {
14
+ if (a === b) {
15
+ return 0;
16
+ }
17
+ if (a.length === 0) {
18
+ return b.length;
19
+ }
20
+ if (b.length === 0) {
21
+ return a.length;
22
+ }
23
+ let previousRow = Array.from({ length: b.length + 1 }, (_, i) => i);
24
+ let currentRow = new Array(b.length + 1);
25
+ for (let i = 0; i < a.length; i++) {
26
+ currentRow[0] = i + 1;
27
+ for (let j = 0; j < b.length; j++) {
28
+ const cost = a[i] === b[j] ? 0 : 1;
29
+ currentRow[j + 1] = Math.min(currentRow[j] + 1, // insertion
30
+ previousRow[j + 1] + 1, // deletion
31
+ previousRow[j] + cost // substitution
32
+ );
33
+ }
34
+ [previousRow, currentRow] = [currentRow, previousRow];
35
+ }
36
+ return previousRow[b.length];
37
+ }
38
+ /**
39
+ * Ranks every candidate by how closely it resembles `input`, from closest to
40
+ * furthest, tie-broken alphabetically so the order is deterministic. Each entry
41
+ * keeps its computed distance so callers can reuse it (e.g. to both pick the
42
+ * closest match and order a list) without recomputing.
43
+ */
44
+ function rankByDistance(input, candidates) {
45
+ return candidates
46
+ .map((candidate) => ({
47
+ candidate,
48
+ distance: levenshteinDistance(input, candidate),
49
+ }))
50
+ .sort((a, b) => a.distance - b.distance || a.candidate.localeCompare(b.candidate));
51
+ }
52
+ /**
53
+ * The number of edits tolerated before `candidate` stops being a useful
54
+ * suggestion for `input`.
55
+ *
56
+ * The tolerance scales with length so longer words allow for more typos, but it
57
+ * scales with the length of the part that actually *differs* — the shared
58
+ * prefix and suffix are stripped first. Scaling on the raw length lets text the
59
+ * user typed correctly buy a bigger budget for the part they got wrong, which
60
+ * matters because these inputs are joined `project:target[:configuration]`
61
+ * specifiers: `zzz:build` and `api:build` are 3 edits apart, but the shared
62
+ * `:build` inflates the raw-length budget to 4 and a project name with no
63
+ * character in common gets suggested.
64
+ *
65
+ * Stripping a common prefix and suffix does not change the Levenshtein
66
+ * distance, so a caller's precomputed `distance` remains directly comparable.
67
+ */
68
+ function suggestionThreshold(input, candidate) {
69
+ const shortest = Math.min(input.length, candidate.length);
70
+ let prefix = 0;
71
+ while (prefix < shortest && input[prefix] === candidate[prefix]) {
72
+ prefix++;
73
+ }
74
+ let suffix = 0;
75
+ while (suffix < shortest - prefix &&
76
+ input[input.length - 1 - suffix] ===
77
+ candidate[candidate.length - 1 - suffix]) {
78
+ suffix++;
79
+ }
80
+ return Math.max(2, Math.ceil((input.length - prefix - suffix) * 0.4));
81
+ }
82
+ /**
83
+ * Whether a candidate at edit `distance` from `input` is close enough to be a
84
+ * useful suggestion. Exposed so callers that already have a distance (e.g. from
85
+ * `rankByDistance`) can gate on it without recomputing.
86
+ */
87
+ function isWithinSuggestionThreshold(input, candidate, distance) {
88
+ return distance <= suggestionThreshold(input, candidate);
89
+ }
90
+ /**
91
+ * Ranks the candidates that are similar enough to `input` to be worth
92
+ * suggesting, closest first (ties broken alphabetically). Each entry keeps its
93
+ * distance so callers can compare suggestions found for different forms of the
94
+ * same input without recomputing.
95
+ */
96
+ function rankSuggestions(input, candidates) {
97
+ return rankByDistance(input, candidates).filter(({ candidate, distance }) => isWithinSuggestionThreshold(input, candidate, distance));
98
+ }
99
+ /**
100
+ * Returns up to `limit` candidates that most closely resemble `input`, sorted
101
+ * from closest to furthest. Candidates that are not similar enough to be a
102
+ * useful suggestion are excluded.
103
+ */
104
+ function findClosestMatches(input, candidates, limit = 3) {
105
+ return rankSuggestions(input, candidates)
106
+ .slice(0, limit)
107
+ .map(({ candidate }) => candidate);
108
+ }
package/migrations.json CHANGED
@@ -190,6 +190,13 @@
190
190
  "description": "Sets \"cache\" on some executor-based target defaults whose caching was previously enabled by a target name default, maintaining compatibility with how cacheability resolved before Nx 23. Keys the migration cannot decide are left alone and keep working through a deprecated runtime fallback.",
191
191
  "implementation": "./dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults",
192
192
  "documentation": "./dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md"
193
+ },
194
+ "24-0-0-move-custom-hashers-to-inputs": {
195
+ "version": "24.0.0-beta.0",
196
+ "description": "Finds local executors that declare a custom hasher, which is deprecated and will be removed in Nx 25. Rewriting a hasher as target inputs means reading what its code hashes, so the rewrite is driven by an AI prompt; the generator only reports each hasher.",
197
+ "implementation": "./dist/src/migrations/update-24-0-0/find-custom-hashers",
198
+ "prompt": "./dist/src/migrations/update-24-0-0/move-custom-hashers-to-inputs.md",
199
+ "documentation": "./dist/src/migrations/update-24-0-0/find-custom-hashers.md"
193
200
  }
194
201
  }
195
202
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx",
3
- "version": "23.3.0",
3
+ "version": "24.0.0-beta.1",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
@@ -66,7 +66,6 @@
66
66
  "bl": "4.1.0",
67
67
  "brace-expansion": "5.0.12",
68
68
  "buffer": "5.7.1",
69
- "bundle-name": "4.1.0",
70
69
  "chalk": "4.1.2",
71
70
  "cli-cursor": "3.1.0",
72
71
  "cli-spinners": "2.6.1",
@@ -74,10 +73,7 @@
74
73
  "clone": "1.0.4",
75
74
  "color-convert": "2.0.1",
76
75
  "color-name": "1.1.4",
77
- "default-browser": "5.2.1",
78
- "default-browser-id": "5.0.0",
79
76
  "defaults": "1.0.4",
80
- "define-lazy-prop": "3.0.0",
81
77
  "dotenv": "16.4.7",
82
78
  "dotenv-expand": "12.0.3",
83
79
  "ejs": "5.0.1",
@@ -95,12 +91,9 @@
95
91
  "ieee754": "1.2.1",
96
92
  "ignore": "7.0.6",
97
93
  "inherits": "2.0.4",
98
- "is-docker": "3.0.0",
99
94
  "is-fullwidth-code-point": "3.0.0",
100
- "is-inside-container": "1.0.0",
101
95
  "is-interactive": "1.0.0",
102
96
  "is-unicode-supported": "0.1.0",
103
- "is-wsl": "3.1.0",
104
97
  "isexe": "2.0.0",
105
98
  "json5": "2.2.3",
106
99
  "jsonc-parser": "3.3.1",
@@ -112,7 +105,6 @@
112
105
  "npm-run-path": "4.0.1",
113
106
  "once": "1.4.0",
114
107
  "onetime": "5.1.2",
115
- "open": "10.1.0",
116
108
  "ora": "5.4.1",
117
109
  "path-key": "3.1.1",
118
110
  "picocolors": "1.1.1",
@@ -120,7 +112,6 @@
120
112
  "require-directory": "2.1.1",
121
113
  "resolve.exports": "2.0.3",
122
114
  "restore-cursor": "3.1.0",
123
- "run-applescript": "7.0.0",
124
115
  "safe-buffer": "5.2.1",
125
116
  "semver": "7.8.5",
126
117
  "signal-exit": "3.0.7",
@@ -159,16 +150,16 @@
159
150
  }
160
151
  },
161
152
  "optionalDependencies": {
162
- "@nx/nx-darwin-arm64": "23.3.0",
163
- "@nx/nx-darwin-x64": "23.3.0",
164
- "@nx/nx-freebsd-x64": "23.3.0",
165
- "@nx/nx-linux-arm-gnueabihf": "23.3.0",
166
- "@nx/nx-linux-arm64-gnu": "23.3.0",
167
- "@nx/nx-linux-arm64-musl": "23.3.0",
168
- "@nx/nx-linux-x64-gnu": "23.3.0",
169
- "@nx/nx-linux-x64-musl": "23.3.0",
170
- "@nx/nx-win32-arm64-msvc": "23.3.0",
171
- "@nx/nx-win32-x64-msvc": "23.3.0"
153
+ "@nx/nx-darwin-arm64": "24.0.0-beta.1",
154
+ "@nx/nx-darwin-x64": "24.0.0-beta.1",
155
+ "@nx/nx-freebsd-x64": "24.0.0-beta.1",
156
+ "@nx/nx-linux-arm-gnueabihf": "24.0.0-beta.1",
157
+ "@nx/nx-linux-arm64-gnu": "24.0.0-beta.1",
158
+ "@nx/nx-linux-arm64-musl": "24.0.0-beta.1",
159
+ "@nx/nx-linux-x64-gnu": "24.0.0-beta.1",
160
+ "@nx/nx-linux-x64-musl": "24.0.0-beta.1",
161
+ "@nx/nx-win32-arm64-msvc": "24.0.0-beta.1",
162
+ "@nx/nx-win32-x64-msvc": "24.0.0-beta.1"
172
163
  },
173
164
  "nx-migrations": {
174
165
  "migrations": "./migrations.json",
@@ -1,7 +1,7 @@
1
- <p style="text-align: center;">
1
+ <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-dark.svg">
4
- <img alt="Nx - Smart Repos · Fast Builds" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-light.svg" width="100%">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo-light.svg">
4
+ <img alt="Nx Logo" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.svg" width="140">
5
5
  </picture>
6
6
  </p>
7
7
 
@@ -615,6 +615,59 @@
615
615
  }
616
616
  },
617
617
  "definitions": {
618
+ "readyWhen": {
619
+ "description": "How Nx detects that this continuous target is ready. Dependents opt in with \"waitFor\": \"ready\" on their dependsOn entry. Strings may use the {workspaceRoot}, {projectRoot}, {projectName} and {options.*} placeholders, resolved against the options the task runs with.",
620
+ "type": "object",
621
+ "properties": {
622
+ "url": {
623
+ "type": "string",
624
+ "description": "Ready when an HTTP or HTTPS GET gets a status from 200 to 403. A redirect counts as ready and is not followed"
625
+ },
626
+ "port": {
627
+ "type": ["integer", "string"],
628
+ "minimum": 1,
629
+ "maximum": 65535,
630
+ "description": "Ready when a TCP connection to this port succeeds. A string must resolve to a port number through a placeholder such as {options.port}"
631
+ },
632
+ "host": {
633
+ "type": "string",
634
+ "description": "Host to connect to with port. By default 127.0.0.1 and ::1 are both tried"
635
+ },
636
+ "command": {
637
+ "type": "string",
638
+ "description": "Ready when this command, run through the shell at the workspace root, exits with code 0"
639
+ },
640
+ "logMatches": {
641
+ "description": "Ready when every one of these substrings has appeared in the task's output",
642
+ "oneOf": [
643
+ { "type": "string" },
644
+ {
645
+ "type": "array",
646
+ "items": { "type": "string" },
647
+ "minItems": 1
648
+ }
649
+ ]
650
+ },
651
+ "timeout": {
652
+ "type": "integer",
653
+ "minimum": 1,
654
+ "default": 60000,
655
+ "description": "Milliseconds to wait for readiness before failing dependents that wait for it"
656
+ },
657
+ "interval": {
658
+ "type": "integer",
659
+ "minimum": 1,
660
+ "description": "Milliseconds between probe attempts. By default attempts back off from 100 to 1000 milliseconds. Unused by logMatches"
661
+ }
662
+ },
663
+ "oneOf": [
664
+ { "required": ["url"] },
665
+ { "required": ["port"] },
666
+ { "required": ["command"] },
667
+ { "required": ["logMatches"] }
668
+ ],
669
+ "additionalProperties": false
670
+ },
618
671
  "ownersPattern": {
619
672
  "type": "object",
620
673
  "properties": {
@@ -912,6 +965,9 @@
912
965
  "continuous": {
913
966
  "$ref": "#/definitions/targetDefaultsConfig/properties/continuous"
914
967
  },
968
+ "readyWhen": {
969
+ "$ref": "#/definitions/targetDefaultsConfig/properties/readyWhen"
970
+ },
915
971
  "parallelism": {
916
972
  "$ref": "#/definitions/targetDefaultsConfig/properties/parallelism"
917
973
  },
@@ -975,6 +1031,9 @@
975
1031
  "default": false,
976
1032
  "description": "Whether this target runs continuously until stopped"
977
1033
  },
1034
+ "readyWhen": {
1035
+ "$ref": "#/definitions/readyWhen"
1036
+ },
978
1037
  "parallelism": {
979
1038
  "type": "boolean",
980
1039
  "default": true,
@@ -1021,6 +1080,17 @@
1021
1080
  "description": "Configuration for params handling.",
1022
1081
  "enum": ["ignore", "forward"],
1023
1082
  "default": "ignore"
1083
+ },
1084
+ "inputs": {
1085
+ "type": "boolean",
1086
+ "description": "Set to false to keep a continuous dependency's inputs out of this target's hash and affected calculation. Has no effect on dependencies that are not continuous.",
1087
+ "default": true
1088
+ },
1089
+ "waitFor": {
1090
+ "type": "string",
1091
+ "description": "For a continuous dependency, whether to wait until it has started or until it is ready per its readyWhen. Ignored when the dependency is not continuous or has no readyWhen.",
1092
+ "enum": ["started", "ready"],
1093
+ "default": "started"
1024
1094
  }
1025
1095
  },
1026
1096
  "oneOf": [