nx 22.7.1 → 22.7.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 (51) hide show
  1. package/dist/bin/nx.d.ts +1 -0
  2. package/dist/bin/nx.js +3 -0
  3. package/dist/src/ai/clone-ai-config-repo.js +20 -3
  4. package/dist/src/analytics/analytics.js +10 -1
  5. package/dist/src/command-line/format/format.js +15 -5
  6. package/dist/src/command-line/graph/graph.js +0 -1
  7. package/dist/src/command-line/release/config/use-legacy-versioning.d.ts +2 -0
  8. package/dist/src/command-line/release/config/use-legacy-versioning.js +8 -0
  9. package/dist/src/command-line/report/report.js +0 -1
  10. package/dist/src/config/misc-interfaces.d.ts +6 -0
  11. package/dist/src/config/schema-utils.js +2 -1
  12. package/dist/src/core/graph/main.js +1 -1
  13. package/dist/src/core/graph/styles.css +1 -1
  14. package/dist/src/core/graph/styles.js +1 -1
  15. package/dist/src/daemon/server/latest-nx.js +2 -0
  16. package/dist/src/daemon/server/start.d.ts +1 -1
  17. package/dist/src/daemon/server/start.js +2 -0
  18. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  19. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  20. package/dist/src/plugins/js/lock-file/npm-parser.js +37 -19
  21. package/dist/src/plugins/js/lock-file/pnpm-parser.js +51 -4
  22. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +12 -4
  23. package/dist/src/plugins/js/utils/typescript.d.ts +7 -0
  24. package/dist/src/plugins/js/utils/typescript.js +39 -0
  25. package/dist/src/project-graph/plugins/isolation/plugin-worker.d.ts +1 -0
  26. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +2 -0
  27. package/dist/src/project-graph/plugins/resolve-plugin.d.ts +7 -4
  28. package/dist/src/project-graph/plugins/resolve-plugin.js +152 -33
  29. package/dist/src/project-graph/plugins/utils.js +13 -7
  30. package/dist/src/tasks-runner/life-cycles/task-history-life-cycle-old.js +13 -2
  31. package/dist/src/tasks-runner/life-cycles/task-history-life-cycle.js +16 -5
  32. package/dist/src/tasks-runner/life-cycles/tui-summary-life-cycle.js +11 -2
  33. package/dist/src/tasks-runner/run-command.js +8 -1
  34. package/dist/src/tasks-runner/task-orchestrator.js +20 -4
  35. package/dist/src/tasks-runner/tasks-schedule.js +3 -3
  36. package/dist/src/utils/ab-testing.js +12 -0
  37. package/dist/src/utils/compile-cache.d.ts +24 -0
  38. package/dist/src/utils/compile-cache.js +49 -0
  39. package/dist/src/utils/enable-compile-cache.d.ts +1 -0
  40. package/dist/src/utils/enable-compile-cache.js +7 -0
  41. package/dist/src/utils/has-nx-js-plugin.d.ts +9 -0
  42. package/dist/src/utils/has-nx-js-plugin.js +24 -0
  43. package/dist/src/utils/logger.d.ts +12 -1
  44. package/dist/src/utils/logger.js +57 -36
  45. package/dist/src/utils/nx-key.d.ts +0 -1
  46. package/dist/src/utils/nx-key.js +20 -23
  47. package/dist/src/utils/output.d.ts +3 -2
  48. package/dist/src/utils/output.js +29 -28
  49. package/dist/src/utils/package-json.js +2 -13
  50. package/dist/src/utils/perf-logging.js +3 -1
  51. package/package.json +22 -15
@@ -6,28 +6,59 @@ exports.getPluginPathAndName = getPluginPathAndName;
6
6
  const tslib_1 = require("tslib");
7
7
  const path = tslib_1.__importStar(require("node:path"));
8
8
  const node_fs_1 = require("node:fs");
9
+ const resolve_exports_1 = require("resolve.exports");
9
10
  const packages_1 = require("../../plugins/js/utils/packages");
11
+ const typescript_1 = require("../../plugins/js/utils/typescript");
10
12
  const fileutils_1 = require("../../utils/fileutils");
11
13
  const logger_1 = require("../../utils/logger");
12
14
  const path_1 = require("../../utils/path");
13
15
  const workspace_root_1 = require("../../utils/workspace-root");
14
16
  const find_project_for_path_1 = require("../utils/find-project-for-path");
15
17
  const retrieve_workspace_files_1 = require("../utils/retrieve-workspace-files");
18
+ const TS_SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.cts', '.mts']);
16
19
  let projectsWithoutInference;
17
20
  let projectsWithoutInferencePromise = null;
18
21
  async function resolveNxPlugin(moduleName, root, paths) {
19
- try {
20
- require.resolve(moduleName, { paths });
21
- }
22
- catch {
23
- // If a plugin cannot be resolved, we will need projects to resolve it
24
- projectsWithoutInferencePromise ??=
25
- (0, retrieve_workspace_files_1.retrieveProjectConfigurationsWithoutPluginInference)(root);
26
- projectsWithoutInference ??= await projectsWithoutInferencePromise;
22
+ // Default plugins (see `getDefaultPlugins` in `get-plugins.ts`) are passed
23
+ // as absolute file paths to compiled bundles inside `nx` itself; they are
24
+ // never workspace-local. Skip the project load entirely for them to avoid
25
+ // recursing through `retrieveProjectConfigurationsWithoutPluginInference`,
26
+ // which itself triggers default-plugin loading.
27
+ if (!path.isAbsolute(moduleName)) {
28
+ let resolvedFromNode;
29
+ try {
30
+ resolvedFromNode = require.resolve(moduleName, { paths });
31
+ }
32
+ catch { }
33
+ // Load projects if Node couldn't resolve (so the local fallback can run)
34
+ // OR if Node resolved to a workspace-internal path (a symlinked workspace
35
+ // package whose source-first lookup should win over the symlinked dist).
36
+ if (!resolvedFromNode ||
37
+ isWorkspaceLocalResolution(resolvedFromNode, root)) {
38
+ projectsWithoutInferencePromise ??=
39
+ (0, retrieve_workspace_files_1.retrieveProjectConfigurationsWithoutPluginInference)(root);
40
+ projectsWithoutInference ??= await projectsWithoutInferencePromise;
41
+ }
27
42
  }
28
43
  const { pluginPath, name, shouldRegisterTSTranspiler } = getPluginPathAndName(moduleName, paths, projectsWithoutInference, root);
29
44
  return { pluginPath, name, shouldRegisterTSTranspiler };
30
45
  }
46
+ /**
47
+ * Distinguishes a symlinked workspace package (where `require.resolve`
48
+ * follows the package-manager symlink into the workspace source tree) from
49
+ * a truly-installed dependency under `node_modules/`. The former needs the
50
+ * source-first lookup to bypass the dist that Node would otherwise return.
51
+ */
52
+ function isWorkspaceLocalResolution(resolvedPath, root) {
53
+ const normalizedRoot = path.normalize(root);
54
+ const normalizedPath = path.normalize(resolvedPath);
55
+ return (normalizedPath.startsWith(normalizedRoot + path.sep) &&
56
+ !normalizedPath.includes(path.sep + 'node_modules' + path.sep));
57
+ }
58
+ function isPackageResolutionError(e) {
59
+ const code = e.code;
60
+ return (code === 'MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED');
61
+ }
31
62
  function readPluginMainFromProjectConfiguration(plugin) {
32
63
  const { main } = Object.values(plugin.targets).find((x) => [
33
64
  '@nx/js:tsc',
@@ -46,31 +77,44 @@ function resolveLocalNxPlugin(importPath, projects, root = workspace_root_1.work
46
77
  }
47
78
  function getPluginPathAndName(moduleName, paths, projects, root) {
48
79
  let pluginPath;
49
- let shouldRegisterTSTranspiler = false;
50
- try {
51
- pluginPath = require.resolve(moduleName, {
52
- paths,
53
- });
54
- const extension = path.extname(pluginPath);
55
- shouldRegisterTSTranspiler = extension === '.ts';
80
+ // Resolve local workspace plugins from source first so the workspace's
81
+ // `customConditions`/`development` exports condition wins over the built
82
+ // `dist` artifact that Node's resolver would otherwise pick up via the
83
+ // `default` condition (Node ignores TypeScript custom conditions). Skipped
84
+ // when `projects` weren't loaded — the caller already determined that the
85
+ // import isn't a workspace package.
86
+ const localPlugin = projects
87
+ ? resolveLocalNxPlugin(moduleName, projects, root)
88
+ : null;
89
+ if (localPlugin) {
90
+ pluginPath = tryResolveLocalPluginFromSource(moduleName, localPlugin, root);
91
+ if (!pluginPath && getSubpathOfLocalPackage(moduleName, localPlugin)) {
92
+ throwUnresolvableLocalPluginError(moduleName, localPlugin, root);
93
+ }
56
94
  }
57
- catch (e) {
58
- if (e.code === 'MODULE_NOT_FOUND') {
59
- const plugin = resolveLocalNxPlugin(moduleName, projects, root);
60
- if (plugin) {
61
- shouldRegisterTSTranspiler = true;
62
- const main = readPluginMainFromProjectConfiguration(plugin.projectConfig);
63
- pluginPath = main ? path.join(root, main) : plugin.path;
95
+ if (!pluginPath) {
96
+ try {
97
+ pluginPath = require.resolve(moduleName, { paths });
98
+ }
99
+ catch (e) {
100
+ if (localPlugin && isPackageResolutionError(e)) {
101
+ throwUnresolvableLocalPluginError(moduleName, localPlugin, root);
64
102
  }
65
- else {
66
- logger_1.logger.error(`Plugin listed in \`nx.json\` not found: ${moduleName}`);
103
+ if (e.code !== 'MODULE_NOT_FOUND') {
67
104
  throw e;
68
105
  }
69
- }
70
- else {
106
+ if (localPlugin) {
107
+ throwUnresolvableLocalPluginError(moduleName, localPlugin, root);
108
+ }
109
+ logger_1.logger.error(`Plugin listed in \`nx.json\` not found: ${moduleName}`);
71
110
  throw e;
72
111
  }
73
112
  }
113
+ const ext = path.extname(pluginPath);
114
+ // Directory paths fall through to Node's `package.json` `main` resolution
115
+ // which may land on a TS file; only opt out of TS transpiler registration
116
+ // when the resolved path is unambiguously JS.
117
+ const shouldRegisterTSTranspiler = ext === '' || TS_SOURCE_EXTENSIONS.has(ext);
74
118
  const packageJsonPath = path.join(pluginPath, 'package.json');
75
119
  const { name } = !['.ts', '.js'].some((x) => path.extname(moduleName) === x) && // Not trying to point to a ts or js file
76
120
  (0, node_fs_1.existsSync)(packageJsonPath) // plugin has a package.json
@@ -78,12 +122,84 @@ function getPluginPathAndName(moduleName, paths, projects, root) {
78
122
  : { name: moduleName };
79
123
  return { pluginPath, name, shouldRegisterTSTranspiler };
80
124
  }
125
+ function getSubpathOfLocalPackage(moduleName, plugin) {
126
+ const packageName = plugin.projectConfig.metadata?.js?.packageName;
127
+ if (!packageName || !moduleName.startsWith(packageName + '/')) {
128
+ return null;
129
+ }
130
+ return '.' + moduleName.slice(packageName.length);
131
+ }
132
+ function tryResolveLocalPluginFromSource(moduleName, plugin, root) {
133
+ if (plugin.resolvedFile) {
134
+ return plugin.resolvedFile;
135
+ }
136
+ const subpath = getSubpathOfLocalPackage(moduleName, plugin);
137
+ if (subpath) {
138
+ return resolveSubpathFromExports(plugin.projectConfig, plugin.path, subpath, root);
139
+ }
140
+ const main = readPluginMainFromProjectConfiguration(plugin.projectConfig);
141
+ return main ? path.join(root, main) : null;
142
+ }
143
+ function throwUnresolvableLocalPluginError(moduleName, plugin, root) {
144
+ const subpath = getSubpathOfLocalPackage(moduleName, plugin);
145
+ const packageName = plugin.projectConfig.metadata?.js?.packageName;
146
+ if (subpath) {
147
+ throw new Error(`Unable to resolve local plugin "${moduleName}". The import targets ` +
148
+ `the subpath "${subpath}" of the local package "${packageName}", but ` +
149
+ `the package's "exports" map has no resolvable entry for "${subpath}", ` +
150
+ `or none of the matched paths exist on disk. Check the "exports" field ` +
151
+ `in "${path.relative(root, path.join(plugin.path, 'package.json'))}" ` +
152
+ `and ensure the source file referenced by "${subpath}" exists.`);
153
+ }
154
+ throw new Error(`Unable to resolve local plugin "${moduleName}". The local package ` +
155
+ `"${packageName ?? moduleName}" does not declare a build target with ` +
156
+ `a "main" source path, and Node could not resolve it either.`);
157
+ }
158
+ function resolveSubpathFromExports(projectConfig, projectPath, subpath, root) {
159
+ const packageExports = projectConfig.metadata?.js?.packageExports;
160
+ if (!packageExports) {
161
+ return null;
162
+ }
163
+ const pkg = {
164
+ name: projectConfig.metadata.js.packageName,
165
+ exports: packageExports,
166
+ };
167
+ try {
168
+ const matches = (0, resolve_exports_1.resolve)(pkg, subpath, {
169
+ conditions: (0, typescript_1.getRootTsConfigResolveExportsConditions)(root),
170
+ });
171
+ if (!matches || !matches.length) {
172
+ return null;
173
+ }
174
+ for (const match of matches) {
175
+ const candidate = path.join(projectPath, match);
176
+ if ((0, node_fs_1.existsSync)(candidate)) {
177
+ return candidate;
178
+ }
179
+ }
180
+ }
181
+ catch (e) {
182
+ logger_1.logger.verbose(`Failed to resolve subpath "${subpath}" of local plugin via package.json exports`, e);
183
+ }
184
+ return null;
185
+ }
81
186
  function lookupLocalPlugin(importPath, projects, root = workspace_root_1.workspaceRoot) {
82
- const projectConfig = findNxProjectForImportPath(importPath, projects, root);
83
- if (!projectConfig) {
187
+ const match = findNxProjectForImportPath(importPath, projects, root);
188
+ if (!match) {
84
189
  return null;
85
190
  }
86
- return { path: path.join(root, projectConfig.root), projectConfig };
191
+ let resolvedFile;
192
+ if (match.tsPathFile) {
193
+ const candidate = path.join(root, match.tsPathFile);
194
+ if (path.extname(candidate) && (0, node_fs_1.existsSync)(candidate)) {
195
+ resolvedFile = candidate;
196
+ }
197
+ }
198
+ return {
199
+ path: path.join(root, match.projectConfig.root),
200
+ projectConfig: match.projectConfig,
201
+ resolvedFile,
202
+ };
87
203
  }
88
204
  let packageEntryPointsToProjectMap;
89
205
  let wildcardEntryPointsToProjectMap;
@@ -101,7 +217,10 @@ function findNxProjectForImportPath(importPath, projects, root = workspace_root_
101
217
  for (const tsConfigPath of possibleTsPaths) {
102
218
  const nxProject = (0, find_project_for_path_1.findProjectForPath)(tsConfigPath, projectRootMappings);
103
219
  if (nxProject) {
104
- return projectNameMap.get(nxProject);
220
+ return {
221
+ projectConfig: projectNameMap.get(nxProject),
222
+ tsPathFile: tsConfigPath,
223
+ };
105
224
  }
106
225
  }
107
226
  }
@@ -112,14 +231,14 @@ function findNxProjectForImportPath(importPath, projects, root = workspace_root_
112
231
  } = (0, packages_1.getWorkspacePackagesMetadata)(projects));
113
232
  }
114
233
  if (packageEntryPointsToProjectMap[importPath]) {
115
- return packageEntryPointsToProjectMap[importPath];
234
+ return { projectConfig: packageEntryPointsToProjectMap[importPath] };
116
235
  }
117
236
  const project = (0, packages_1.matchImportToWildcardEntryPointsToProjectMap)(wildcardEntryPointsToProjectMap, importPath);
118
237
  if (project) {
119
- return project;
238
+ return { projectConfig: project };
120
239
  }
121
240
  logger_1.logger.verbose('Unable to find local plugin', possibleTsPaths, projectRootMappings);
122
- throw new Error('Unable to resolve local plugin with import path ' + importPath);
241
+ return null;
123
242
  }
124
243
  let tsconfigPaths;
125
244
  function readTsConfigPaths(root = workspace_root_1.workspaceRoot) {
@@ -3,22 +3,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createNodesFromFiles = createNodesFromFiles;
4
4
  const error_types_1 = require("../error-types");
5
5
  async function createNodesFromFiles(createNodes, configFiles, options, context) {
6
- const results = [];
7
- const errors = [];
8
- await Promise.all(configFiles.map(async (file, idx) => {
6
+ const settled = await Promise.all(configFiles.map(async (file, idx) => {
9
7
  try {
10
8
  const value = await createNodes(file, options, {
11
9
  ...context,
12
10
  configFiles,
13
11
  }, idx);
14
- if (value) {
15
- results.push([file, value]);
16
- }
12
+ return value ? { kind: 'value', file, value } : { kind: 'empty' };
17
13
  }
18
14
  catch (e) {
19
- errors.push([file, e]);
15
+ return { kind: 'error', file, error: e };
20
16
  }
21
17
  }));
18
+ const results = [];
19
+ const errors = [];
20
+ for (const entry of settled) {
21
+ if (entry.kind === 'value') {
22
+ results.push([entry.file, entry.value]);
23
+ }
24
+ else if (entry.kind === 'error') {
25
+ errors.push([entry.file, entry.error]);
26
+ }
27
+ }
22
28
  if (errors.length > 0) {
23
29
  throw new error_types_1.AggregateCreateNodesError(errors, results);
24
30
  }
@@ -61,11 +61,22 @@ class LegacyTaskHistoryLifeCycle {
61
61
  }
62
62
  printFlakyTasksMessage() {
63
63
  if (this.flakyTasks?.length > 0) {
64
+ const MAX_VISIBLE_FLAKY = 5;
65
+ const visibleFlaky = this.flakyTasks.length > MAX_VISIBLE_FLAKY + 1
66
+ ? this.flakyTasks.slice(0, MAX_VISIBLE_FLAKY)
67
+ : this.flakyTasks;
68
+ const hiddenCount = this.flakyTasks.length - visibleFlaky.length;
69
+ const flakyRows = visibleFlaky.map((t) => ` ${t}`);
70
+ if (hiddenCount > 0) {
71
+ flakyRows.push(` ${hiddenCount} more...`);
72
+ }
64
73
  output_1.output.warn({
65
- title: `Nx detected ${this.flakyTasks.length === 1 ? 'a flaky task' : ' flaky tasks'}`,
74
+ title: `Nx detected ${this.flakyTasks.length === 1
75
+ ? 'a flaky task'
76
+ : `${this.flakyTasks.length} flaky tasks`}`,
66
77
  bodyLines: [
67
78
  ,
68
- ...this.flakyTasks.map((t) => ` ${t}`),
79
+ ...flakyRows,
69
80
  ...((0, nx_cloud_utils_1.isNxCloudUsed)((0, nx_json_1.readNxJson)())
70
81
  ? []
71
82
  : [
@@ -79,14 +79,25 @@ class TaskHistoryLifeCycle {
79
79
  }
80
80
  printFlakyTasksMessage() {
81
81
  if (this.flakyTasks?.length > 0) {
82
+ const MAX_VISIBLE_FLAKY = 5;
83
+ const visibleFlaky = this.flakyTasks.length > MAX_VISIBLE_FLAKY + 1
84
+ ? this.flakyTasks.slice(0, MAX_VISIBLE_FLAKY)
85
+ : this.flakyTasks;
86
+ const hiddenCount = this.flakyTasks.length - visibleFlaky.length;
87
+ const flakyRows = visibleFlaky.map((hash) => {
88
+ const taskRun = this.taskRuns.get(hash);
89
+ return ` ${(0, serialize_target_1.serializeTarget)(taskRun.target.project, taskRun.target.target, taskRun.target.configuration)}`;
90
+ });
91
+ if (hiddenCount > 0) {
92
+ flakyRows.push(` ${hiddenCount} more...`);
93
+ }
82
94
  output_1.output.warn({
83
- title: `Nx detected ${this.flakyTasks.length === 1 ? 'a flaky task' : ' flaky tasks'}`,
95
+ title: `Nx detected ${this.flakyTasks.length === 1
96
+ ? 'a flaky task'
97
+ : `${this.flakyTasks.length} flaky tasks`}`,
84
98
  bodyLines: [
85
99
  ,
86
- ...this.flakyTasks.map((hash) => {
87
- const taskRun = this.taskRuns.get(hash);
88
- return ` ${(0, serialize_target_1.serializeTarget)(taskRun.target.project, taskRun.target.target, taskRun.target.configuration)}`;
89
- }),
100
+ ...flakyRows,
90
101
  ...((0, nx_cloud_utils_1.isNxCloudUsed)((0, nx_json_1.readNxJson)())
91
102
  ? []
92
103
  : [
@@ -67,6 +67,12 @@ function getTuiTerminalSummaryLifeCycle({ projectNames, tasks, taskGraph, args,
67
67
  displayStoppedTasks.add(taskId);
68
68
  inProgressTasks.delete(taskId);
69
69
  }
70
+ else if (taskStatus === 2 /* NativeTaskStatus.Skipped */) {
71
+ // Skipped tasks don't get an endTasks() call; clear them here so the run
72
+ // summary doesn't treat them as still-in-progress and report Cancelled.
73
+ tasksToTaskStatus[taskId] = 'skipped';
74
+ inProgressTasks.delete(taskId);
75
+ }
70
76
  };
71
77
  lifeCycle.endTasks = (taskResults) => {
72
78
  for (const { task, status, terminalOutput } of taskResults) {
@@ -132,10 +138,13 @@ function getTuiTerminalSummaryLifeCycle({ projectNames, tasks, taskGraph, args,
132
138
  (0, task_history_life_cycle_1.getTasksHistoryLifeCycle)().printFlakyTasksMessage();
133
139
  };
134
140
  const printRunOneSummary = ({ failure, cancelled, }) => {
135
- // Prints task outputs in the order they were completed
136
- // above the summary, since run-one should print all task results.
141
+ // Print task outputs in completion order above the summary.
137
142
  for (const taskId of taskIdsInTheOrderTheyStart) {
138
143
  const taskStatus = tasksToTaskStatus[taskId];
144
+ // Skipped tasks never ran; don't print a misleading `> nx run` header.
145
+ if (taskStatus === 'skipped') {
146
+ continue;
147
+ }
139
148
  const terminalOutput = getTerminalOutput(taskId);
140
149
  output_1.output.logCommandOutput(taskId, taskStatus, terminalOutput);
141
150
  }
@@ -22,6 +22,7 @@ const project_graph_1 = require("../project-graph/project-graph");
22
22
  const handle_errors_1 = require("../utils/handle-errors");
23
23
  const is_ci_1 = require("../utils/is-ci");
24
24
  const nx_cloud_utils_1 = require("../utils/nx-cloud-utils");
25
+ const logger_1 = require("../utils/logger");
25
26
  const nx_key_1 = require("../utils/nx-key");
26
27
  const output_1 = require("../utils/output");
27
28
  const sync_generators_1 = require("../utils/sync-generators");
@@ -324,6 +325,10 @@ async function runCommand(projectsToRun, currentProjectGraph, { nxJson }, nxArgs
324
325
  return status;
325
326
  }
326
327
  async function runCommandForTasks(projectsToRun, currentProjectGraph, { nxJson }, nxArgs, overrides, initiatingProject, extraTargetDependencies, extraOptions) {
328
+ // Kick off the license lookup in the background so it overlaps with task
329
+ // execution. The log itself is deferred to the print site below so it
330
+ // never lands in the middle of task output.
331
+ const nxKeyPromise = (0, nx_key_1.getNxKeyInformation)().catch(() => null);
327
332
  const projectNames = projectsToRun.map((t) => t.name);
328
333
  const projectNameSet = new Set(projectNames);
329
334
  const { projectGraph, taskGraph } = await ensureWorkspaceIsInSyncAndGetGraphs(currentProjectGraph, nxJson, projectNames, nxArgs, overrides, extraTargetDependencies, extraOptions);
@@ -348,7 +353,9 @@ async function runCommandForTasks(projectsToRun, currentProjectGraph, { nxJson }
348
353
  printSummary();
349
354
  }
350
355
  await printConfigureAiAgentsDisclaimer();
351
- await (0, nx_key_1.printNxKey)();
356
+ const nxKey = await nxKeyPromise;
357
+ if (nxKey)
358
+ logger_1.logger.log((0, nx_key_1.createNxKeyLicenseeInformation)(nxKey));
352
359
  return {
353
360
  taskResults,
354
361
  completed: didCommandComplete(tasks, taskResults),
@@ -29,6 +29,14 @@ const task_env_1 = require("./task-env");
29
29
  const tasks_schedule_1 = require("./tasks-schedule");
30
30
  const utils_1 = require("./utils");
31
31
  const shared_running_task_1 = require("./running-tasks/shared-running-task");
32
+ /**
33
+ * Resolve a batch executor's per-task result to a TaskStatus. Prefers an
34
+ * explicit `status` from the executor; falls back to the `success` boolean
35
+ * for executors that pre-date the `status` field.
36
+ */
37
+ function resolveBatchTaskStatus(result) {
38
+ return result.status ?? (result.success ? 'success' : 'failure');
39
+ }
32
40
  class TaskOrchestrator {
33
41
  // endregion internal state
34
42
  constructor(hasher, initiatingProject, initiatingTasks, projectGraph, taskGraph, nxJson, options, bail, daemon, outputStyle, taskGraphForHashing = taskGraph) {
@@ -498,11 +506,18 @@ class TaskOrchestrator {
498
506
  // Heavy operations (caching, scheduling, complete) happen at batch-end in postRunSteps
499
507
  batchProcess.onTaskResults((taskId, result) => {
500
508
  const task = this.taskGraph.tasks[taskId];
501
- const status = result.success ? 'success' : 'failure';
502
- this.options.lifeCycle.printTaskTerminalOutput(task, status, result.terminalOutput ?? '');
509
+ const status = resolveBatchTaskStatus(result);
510
+ // Append before print so printTaskTerminalOutput finds the PTY already
511
+ // populated and no-ops; reversing the order writes terminalOutput twice.
503
512
  if (result.terminalOutput) {
504
513
  this.options.lifeCycle.appendTaskOutput(taskId, result.terminalOutput, false);
505
514
  }
515
+ // Skipped tasks didn't run, so they have no terminal output and don't
516
+ // need a per-task PTY — calling printTaskTerminalOutput would otherwise
517
+ // allocate one just to write a cursor-hide escape.
518
+ if (status !== 'skipped') {
519
+ this.options.lifeCycle.printTaskTerminalOutput(task, status, result.terminalOutput ?? '');
520
+ }
506
521
  task.startTime = result.startTime;
507
522
  task.endTime = result.endTime;
508
523
  if (result.startTime && result.endTime) {
@@ -516,10 +531,11 @@ class TaskOrchestrator {
516
531
  const task = this.taskGraph.tasks[taskId];
517
532
  task.startTime = result.startTime;
518
533
  task.endTime = result.endTime;
534
+ const status = resolveBatchTaskStatus(result);
519
535
  return {
520
- code: result.success ? 0 : 1,
536
+ code: status === 'success' ? 0 : 1,
521
537
  task,
522
- status: (result.success ? 'success' : 'failure'),
538
+ status,
523
539
  terminalOutput: result.terminalOutput,
524
540
  };
525
541
  });
@@ -223,9 +223,9 @@ class TasksSchedule {
223
223
  }
224
224
  }
225
225
  canBatchTaskBeScheduled(task, batchTaskGraph) {
226
- // task self needs to have parallelism true
226
+ // task self needs to support parallelism (undefined defaults to parallel)
227
227
  // all deps have either completed or belong to the same batch
228
- return (task.parallelism === true &&
228
+ return (task.parallelism !== false &&
229
229
  this.taskGraph.dependencies[task.id].every((id) => this.completedTasks.has(id) || !!batchTaskGraph?.tasks[id]));
230
230
  }
231
231
  canBeScheduled(taskId) {
@@ -248,7 +248,7 @@ class TasksSchedule {
248
248
  }
249
249
  else {
250
250
  // if all running tasks support parallelism, can only schedule task with parallelism
251
- return this.taskGraph.tasks[taskId].parallelism === true;
251
+ return this.taskGraph.tasks[taskId].parallelism !== false;
252
252
  }
253
253
  }
254
254
  getEstimatedTaskTimings() {
@@ -21,6 +21,18 @@ const messageOptions = {
21
21
  ],
22
22
  footer: '\nFree for small teams. Remote caching and task distribution. 2-minute setup: https://nx.dev/nx-cloud',
23
23
  },
24
+ {
25
+ code: 'cloud-self-healing-remote-cache',
26
+ message: `Would you like to enable AI-powered Self-Healing CI and Remote Caching?`,
27
+ initial: 0,
28
+ choices: [
29
+ { value: 'yes', name: 'Yes' },
30
+ { value: 'skip', name: 'Skip for now' },
31
+ { value: 'never', name: pc.dim("No, don't ask again") },
32
+ ],
33
+ footer: '\nLearn about it at https://nx.dev/nx-cloud',
34
+ hint: `\n(it's free and can be disabled any time)`,
35
+ },
24
36
  ],
25
37
  setupViewLogs: [
26
38
  {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Enables V8's on-disk bytecode cache for the current process.
3
+ *
4
+ * Calls `module.enableCompileCache()` with no arguments and lets Node pick
5
+ * the location, which means Node's standard env vars work transparently:
6
+ * - `NODE_COMPILE_CACHE=<dir>` — override the cache directory.
7
+ * - `NODE_DISABLE_COMPILE_CACHE=1` — disable entirely.
8
+ *
9
+ * The default location lives under the OS temp dir keyed by V8 version, so
10
+ * the cache is shared across workspaces and self-invalidates on Node
11
+ * upgrades — no nx-specific cleanup needed.
12
+ *
13
+ * Called at the entry point of every long-lived nx process (main CLI,
14
+ * daemon, plugin workers) via `enable-compile-cache.ts`, which side-effects
15
+ * this on import.
16
+ *
17
+ * Set `NX_COMPILE_CACHE=false` to opt out without disabling the cache for
18
+ * non-nx Node processes the way `NODE_DISABLE_COMPILE_CACHE` would.
19
+ *
20
+ * No-op on Node versions without the `module.enableCompileCache` API.
21
+ * Errors are swallowed — the compile cache is a pure performance
22
+ * optimization and must never break the CLI.
23
+ */
24
+ export declare function enableCompileCache(...override: [unknown?]): boolean;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enableCompileCache = enableCompileCache;
4
+ const tslib_1 = require("tslib");
5
+ const nodeModule = tslib_1.__importStar(require("node:module"));
6
+ /**
7
+ * Enables V8's on-disk bytecode cache for the current process.
8
+ *
9
+ * Calls `module.enableCompileCache()` with no arguments and lets Node pick
10
+ * the location, which means Node's standard env vars work transparently:
11
+ * - `NODE_COMPILE_CACHE=<dir>` — override the cache directory.
12
+ * - `NODE_DISABLE_COMPILE_CACHE=1` — disable entirely.
13
+ *
14
+ * The default location lives under the OS temp dir keyed by V8 version, so
15
+ * the cache is shared across workspaces and self-invalidates on Node
16
+ * upgrades — no nx-specific cleanup needed.
17
+ *
18
+ * Called at the entry point of every long-lived nx process (main CLI,
19
+ * daemon, plugin workers) via `enable-compile-cache.ts`, which side-effects
20
+ * this on import.
21
+ *
22
+ * Set `NX_COMPILE_CACHE=false` to opt out without disabling the cache for
23
+ * non-nx Node processes the way `NODE_DISABLE_COMPILE_CACHE` would.
24
+ *
25
+ * No-op on Node versions without the `module.enableCompileCache` API.
26
+ * Errors are swallowed — the compile cache is a pure performance
27
+ * optimization and must never break the CLI.
28
+ */
29
+ function enableCompileCache(
30
+ // Test seam: production callers omit this. `unknown` (rather than
31
+ // `EnableCompileCacheFn | undefined`) lets tests pass a non-function to
32
+ // simulate pre-22.8 Node where `module.enableCompileCache` is missing. We
33
+ // read `arguments.length` so callers can *explicitly* pass `undefined`.
34
+ ...override) {
35
+ if (process.env.NX_COMPILE_CACHE === 'false')
36
+ return false;
37
+ const impl = override.length === 0
38
+ ? nodeModule.enableCompileCache
39
+ : override[0];
40
+ if (typeof impl !== 'function')
41
+ return false;
42
+ try {
43
+ impl();
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // Side-effect-only module: import this as the *first* import in any nx
4
+ // process entry point so V8's compile cache is enabled before the rest of
5
+ // the import chain (which is what we actually want to cache) starts loading.
6
+ const compile_cache_1 = require("./compile-cache");
7
+ (0, compile_cache_1.enableCompileCache)();
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Checks if `@nx/js` is installed by attempting to resolve its `package.json`.
3
+ *
4
+ * Lives in its own module so unit tests can `jest.doMock` it without having to
5
+ * intercept Node's resolver at the global level. The previous in-`package-json`
6
+ * definition was unreachable to mocks because callers in the same module
7
+ * referenced it via the local lexical binding rather than `module.exports`.
8
+ */
9
+ export declare function hasNxJsPlugin(projectRoot: string, workspaceRoot: string): boolean;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasNxJsPlugin = hasNxJsPlugin;
4
+ const installation_directory_1 = require("./installation-directory");
5
+ /**
6
+ * Checks if `@nx/js` is installed by attempting to resolve its `package.json`.
7
+ *
8
+ * Lives in its own module so unit tests can `jest.doMock` it without having to
9
+ * intercept Node's resolver at the global level. The previous in-`package-json`
10
+ * definition was unreachable to mocks because callers in the same module
11
+ * referenced it via the local lexical binding rather than `module.exports`.
12
+ */
13
+ function hasNxJsPlugin(projectRoot, workspaceRoot) {
14
+ try {
15
+ // nx-ignore-next-line
16
+ require.resolve('@nx/js/package.json', {
17
+ paths: [projectRoot, ...(0, installation_directory_1.getNxRequirePaths)(workspaceRoot), __dirname],
18
+ });
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
@@ -1,7 +1,17 @@
1
1
  export declare const NX_PREFIX: string;
2
2
  export declare const NX_ERROR: string;
3
+ type LogDriver = Pick<Console, 'warn' | 'error' | 'info' | 'log' | 'debug'>;
4
+ export declare function createLogger(driver: LogDriver): {
5
+ warn: (...v: any[]) => void;
6
+ error: (s: any) => void;
7
+ info: (s: any) => void;
8
+ log: (...s: any[]) => void;
9
+ debug: (...s: any[]) => void;
10
+ fatal: (...s: any[]) => void;
11
+ verbose: (...s: any[]) => void;
12
+ };
3
13
  export declare const logger: {
4
- warn: (s: any) => void;
14
+ warn: (...v: any[]) => void;
5
15
  error: (s: any) => void;
6
16
  info: (s: any) => void;
7
17
  log: (...s: any[]) => void;
@@ -10,3 +20,4 @@ export declare const logger: {
10
20
  verbose: (...s: any[]) => void;
11
21
  };
12
22
  export declare function stripIndent(str: string): string;
23
+ export {};