Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions packages/nx/src/project-graph/project-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,10 +416,36 @@ export async function createProjectGraphAndSourceMapsAsync(
}

export function preventRecursionInGraphConstruction() {
// preventRecursionInGraphConstruction -> callee -> ...
// slice removes preventRecursionInGraphConstruction and its caller,
// which is useful when using this function to detect recursion in buildProjectGraphAndSourceMapsWithoutDaemon
const stackframes = getCallSites().slice(2);
const allFrames = getCallSites();

// Find the first occurrence of buildProjectGraphAndSourceMapsWithoutDaemon in the call stack.
// This represents the current invocation and should be skipped for the recursion check.
const firstOccurrenceIndex = allFrames.findIndex(
(f) =>
f.getFunctionName() === buildProjectGraphAndSourceMapsWithoutDaemon.name
);

let stackframes: NodeJS.CallSite[];

if (firstOccurrenceIndex !== -1) {
// Skip the current invocation frame and any consecutive frames with the same function name.
// Some runtimes (e.g. Bun) include extra async frames for the same call, which would
// otherwise cause a false positive loop detection.
let startIndex = firstOccurrenceIndex + 1;
while (
startIndex < allFrames.length &&
allFrames[startIndex].getFunctionName() ===
buildProjectGraphAndSourceMapsWithoutDaemon.name
) {
startIndex++;
}
stackframes = allFrames.slice(startIndex);
} else {
// If buildProjectGraphAndSourceMapsWithoutDaemon is not in the stack (e.g., when called
// from daemon client), fall back to the original slice(2) behavior.
// preventRecursionInGraphConstruction -> callee -> ...
stackframes = allFrames.slice(2);
}

if (
stackframes.some((f) => {
Expand Down
Loading