-
+
Nx New Year Tips
@@ -477,7 +477,7 @@ export default function NewYear(): JSX.Element {
className="py-18 mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"
>
-
+
{shownTips.map((tip) => (
+
diff --git a/nx-dev/ui-markdoc/src/lib/tags/cards.component.tsx b/nx-dev/ui-markdoc/src/lib/tags/cards.component.tsx
index db6f9d2fe8..f7f6811f41 100644
--- a/nx-dev/ui-markdoc/src/lib/tags/cards.component.tsx
+++ b/nx-dev/ui-markdoc/src/lib/tags/cards.component.tsx
@@ -198,7 +198,7 @@ export function Card({
) : null}
{/*HOVER ICON*/}
-
+
diff --git a/packages/nx/src/command-line/graph/graph.ts b/packages/nx/src/command-line/graph/graph.ts
index 6343e8b566..ef9cd17712 100644
--- a/packages/nx/src/command-line/graph/graph.ts
+++ b/packages/nx/src/command-line/graph/graph.ts
@@ -36,6 +36,7 @@ import { pruneExternalNodes } from '../../project-graph/operators';
import {
createProjectGraphAndSourceMapsAsync,
createProjectGraphAsync,
+ handleProjectGraphError,
} from '../../project-graph/project-graph';
import {
createTaskGraph,
@@ -48,21 +49,35 @@ import { HashPlanner, transferProjectGraph } from '../../native';
import { transformProjectGraphForRust } from '../../native/transform-objects';
import { getAffectedGraphNodes } from '../affected/affected';
import { readFileMapCache } from '../../project-graph/nx-deps-cache';
+import { Hash, getNamedInputs } from '../../hasher/task-hasher';
import { ConfigurationSourceMaps } from '../../project-graph/utils/project-configuration-utils';
-import { filterUsingGlobPatterns } from '../../hasher/task-hasher';
import { createTaskHasher } from '../../hasher/create-task-hasher';
+import { filterUsingGlobPatterns } from '../../hasher/task-hasher';
+import { ProjectGraphError } from '../../project-graph/error-types';
+
+export interface GraphError {
+ message: string;
+ stack: string;
+ cause: unknown;
+ name: string;
+ pluginName: string;
+ fileName?: string;
+}
+
export interface ProjectGraphClientResponse {
hash: string;
projects: ProjectGraphProjectNode[];
dependencies: Record;
- fileMap: ProjectFileMap;
+ fileMap?: ProjectFileMap;
layout: { appsDir: string; libsDir: string };
affected: string[];
focus: string;
groupByFolder: boolean;
exclude: string[];
+ isPartial: boolean;
+ errors?: GraphError[];
}
export interface TaskGraphClientResponse {
@@ -273,10 +288,30 @@ export async function generateGraph(
? args.targets[0]
: args.targets;
- const { projectGraph: rawGraph, sourceMaps } =
- await createProjectGraphAndSourceMapsAsync({
- exitOnError: true,
- });
+ let rawGraph: ProjectGraph;
+ let sourceMaps: ConfigurationSourceMaps;
+ let isPartial = false;
+ try {
+ const projectGraphAndSourceMaps =
+ await createProjectGraphAndSourceMapsAsync({
+ exitOnError: false,
+ });
+ rawGraph = projectGraphAndSourceMaps.projectGraph;
+ sourceMaps = projectGraphAndSourceMaps.sourceMaps;
+ } catch (e) {
+ if (e instanceof ProjectGraphError) {
+ output.warn({
+ title: 'Failed to process project graph. Showing partial graph.',
+ });
+ rawGraph = e.getPartialProjectGraph();
+ sourceMaps = e.getPartialSourcemaps();
+
+ isPartial = true;
+ }
+ if (!rawGraph) {
+ handleProjectGraphError({ exitOnError: true }, e);
+ }
+ }
let prunedGraph = pruneExternalNodes(rawGraph);
const projects = Object.values(
@@ -632,6 +667,8 @@ let currentProjectGraphClientResponse: ProjectGraphClientResponse = {
focus: null,
groupByFolder: false,
exclude: [],
+ isPartial: false,
+ errors: [],
};
let currentSourceMapsClientResponse: ConfigurationSourceMaps = {};
@@ -649,7 +686,11 @@ function debounce(fn: (...args) => void, time: number) {
function createFileWatcher() {
return daemonClient.registerFileWatcher(
- { watchProjects: 'all', includeGlobalWorkspaceFiles: true },
+ {
+ watchProjects: 'all',
+ includeGlobalWorkspaceFiles: true,
+ allowPartialGraph: true,
+ },
debounce(async (error, changes) => {
if (error === 'closed') {
output.error({ title: `Watch error: Daemon closed the connection` });
@@ -687,11 +728,39 @@ async function createProjectGraphAndSourceMapClientResponse(
}> {
performance.mark('project graph watch calculation:start');
- const { projectGraph, sourceMaps } =
- await createProjectGraphAndSourceMapsAsync({ exitOnError: true });
+ let projectGraph: ProjectGraph;
+ let sourceMaps: ConfigurationSourceMaps;
+ let isPartial = false;
+ let errors: GraphError[] | undefined;
+ try {
+ const projectGraphAndSourceMaps =
+ await createProjectGraphAndSourceMapsAsync({ exitOnError: false });
+ projectGraph = projectGraphAndSourceMaps.projectGraph;
+ sourceMaps = projectGraphAndSourceMaps.sourceMaps;
+ } catch (e) {
+ if (e instanceof ProjectGraphError) {
+ projectGraph = e.getPartialProjectGraph();
+ sourceMaps = e.getPartialSourcemaps();
+ errors = e.getErrors().map((e) => ({
+ message: e.message,
+ stack: e.stack,
+ cause: e.cause,
+ name: e.name,
+ pluginName: (e as any).pluginName,
+ fileName:
+ (e as any).file ?? (e.cause as any)?.errors?.[0]?.location?.file,
+ }));
+ isPartial = true;
+ }
+
+ if (!projectGraph) {
+ handleProjectGraphError({ exitOnError: true }, e);
+ }
+ }
let graph = pruneExternalNodes(projectGraph);
- let fileMap = readFileMapCache().fileMap.projectFileMap;
+ let fileMap: ProjectFileMap | undefined =
+ readFileMapCache()?.fileMap.projectFileMap;
performance.mark('project graph watch calculation:end');
performance.mark('project graph response generation:start');
@@ -700,7 +769,9 @@ async function createProjectGraphAndSourceMapClientResponse(
const dependencies = graph.dependencies;
const hasher = createHash('sha256');
- hasher.update(JSON.stringify({ layout, projects, dependencies, sourceMaps }));
+ hasher.update(
+ JSON.stringify({ layout, projects, dependencies, sourceMaps, errors })
+ );
const hash = hasher.digest('hex');
@@ -727,6 +798,8 @@ async function createProjectGraphAndSourceMapClientResponse(
dependencies,
affected,
fileMap,
+ isPartial,
+ errors,
},
sourceMapResponse: sourceMaps,
};
@@ -736,12 +809,15 @@ async function createTaskGraphClientResponse(
pruneExternal: boolean = false
): Promise {
let graph: ProjectGraph;
+ try {
+ graph = await createProjectGraphAsync({ exitOnError: false });
+ } catch (e) {
+ if (e instanceof ProjectGraphError) {
+ graph = e.getPartialProjectGraph();
+ }
+ }
if (pruneExternal) {
- graph = pruneExternalNodes(
- await createProjectGraphAsync({ exitOnError: true })
- );
- } else {
- graph = await createProjectGraphAsync({ exitOnError: true });
+ graph = pruneExternalNodes(graph);
}
const nxJson = readNxJson();
diff --git a/packages/nx/src/daemon/client/client.ts b/packages/nx/src/daemon/client/client.ts
index 3bd55fdf96..a4ebf81faa 100644
--- a/packages/nx/src/daemon/client/client.ts
+++ b/packages/nx/src/daemon/client/client.ts
@@ -179,6 +179,7 @@ export class DaemonClient {
watchProjects: string[] | 'all';
includeGlobalWorkspaceFiles?: boolean;
includeDependentProjects?: boolean;
+ allowPartialGraph?: boolean;
},
callback: (
error: Error | null | 'closed',
@@ -188,7 +189,15 @@ export class DaemonClient {
} | null
) => void
): Promise {
- await this.getProjectGraphAndSourceMaps();
+ try {
+ await this.getProjectGraphAndSourceMaps();
+ } catch (e) {
+ if (config.allowPartialGraph && e instanceof ProjectGraphError) {
+ // we are fine with partial graph
+ } else {
+ throw e;
+ }
+ }
let messenger: DaemonSocketMessenger | undefined;
await this.queue.sendToQueue(() => {
diff --git a/packages/nx/src/daemon/server/file-watching/file-watcher-sockets.ts b/packages/nx/src/daemon/server/file-watching/file-watcher-sockets.ts
index bc1f64fde4..453010d913 100644
--- a/packages/nx/src/daemon/server/file-watching/file-watcher-sockets.ts
+++ b/packages/nx/src/daemon/server/file-watching/file-watcher-sockets.ts
@@ -1,6 +1,5 @@
import { Socket } from 'net';
import { findMatchingProjects } from '../../../utils/find-matching-projects';
-import { ProjectGraph } from '../../../config/project-graph';
import { findAllProjectNodeDependencies } from '../../../utils/project-graph-utils';
import { PromisedBasedQueue } from '../../../utils/promised-based-queue';
import { currentProjectGraph } from '../project-graph-incremental-recomputation';
diff --git a/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts b/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts
index 1680bb1c35..61120a66e6 100644
--- a/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts
+++ b/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts
@@ -261,7 +261,7 @@ async function processFilesAndCreateAndSerializeProjectGraph(
const errors = [...(projectConfigurationsError?.errors ?? [])];
if (g.error) {
- if (isAggregateProjectGraphError(g.error)) {
+ if (isAggregateProjectGraphError(g.error) && g.error.errors?.length) {
errors.push(...g.error.errors);
} else {
return {
diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts
index 9dfc44fcbc..51d0989504 100644
--- a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts
+++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts
@@ -4,6 +4,7 @@ import { ProjectConfiguration } from '../../../config/workspace-json-project-jso
import { toProjectName } from '../../../config/workspaces';
import { readJsonFile } from '../../../utils/fileutils';
import { NxPluginV2 } from '../../../project-graph/plugins';
+import { CreateNodesError } from '../../../project-graph/error-types';
export const ProjectJsonProjectsPlugin: NxPluginV2 = {
name: 'nx/core/project-json',
@@ -13,6 +14,7 @@ export const ProjectJsonProjectsPlugin: NxPluginV2 = {
const json = readJsonFile(
join(workspaceRoot, file)
);
+
const project = buildProjectFromProjectJson(json, file);
return {
projects: {
diff --git a/packages/nx/src/project-graph/project-graph.ts b/packages/nx/src/project-graph/project-graph.ts
index b6d221025d..05e2086d5a 100644
--- a/packages/nx/src/project-graph/project-graph.ts
+++ b/packages/nx/src/project-graph/project-graph.ts
@@ -171,7 +171,7 @@ export async function buildProjectGraphAndSourceMapsWithoutDaemon() {
}
}
-function handleProjectGraphError(opts: { exitOnError: boolean }, e) {
+export function handleProjectGraphError(opts: { exitOnError: boolean }, e) {
if (opts.exitOnError) {
const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true';
if (e instanceof ProjectGraphError) {