From 357d63949e533dd2511c467396eeb07638f6505b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 20 Apr 2026 16:37:26 +0200 Subject: [PATCH] Fix lockfile dependency removal detection in PackageGraph The `otherGraph` variable in `listChangedPackages` was incorrectly created from `thisLockfile` instead of `otherLockfile`, making the merged dependency graph a duplicate of the current one. This meant that dependencies only present in the old lockfile were never added to the graph, so transitive removals could not be detected. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .changeset/fix-lockfile-removal-detection.md | 5 ++ .../src/monorepo/PackageGraph.test.ts | 47 +++++++++++++++++++ .../cli-node/src/monorepo/PackageGraph.ts | 2 +- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-lockfile-removal-detection.md diff --git a/.changeset/fix-lockfile-removal-detection.md b/.changeset/fix-lockfile-removal-detection.md new file mode 100644 index 0000000000..3f7d9541ae --- /dev/null +++ b/.changeset/fix-lockfile-removal-detection.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-node': patch +--- + +Fixed a bug in `PackageGraph.listChangedPackages` where removed dependencies were not detected during lockfile analysis. The dependency graph from the previous lockfile was not being merged, causing transitive dependency removals to be missed. diff --git a/packages/cli-node/src/monorepo/PackageGraph.test.ts b/packages/cli-node/src/monorepo/PackageGraph.test.ts index 51d8f7b7be..7ee41e939d 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.test.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.test.ts @@ -222,4 +222,51 @@ c-dep@^2: 'origin/master', ); }); + + it('detects packages affected by a removed dependency in the lockfile', async () => { + const graph = PackageGraph.fromPackages(testPackages); + + mockListChangedFiles.mockResolvedValueOnce(['yarn.lock']); + + // The old lockfile (at the ref) has b depending on b-dep + mockReadFileAtRef.mockResolvedValueOnce(` +a@^1: + version: "1.0.0" + +b@^1: + version: "1.0.0" + dependencies: + b-dep: ^1 + +b-dep@^1: + version: "1.0.0" + integrity: sha512-old + +c@^1: + version: "1.0.0" +`); + + // The current lockfile no longer has b-dep at all + jest.spyOn(Lockfile, 'load').mockResolvedValueOnce( + Lockfile.parse(` +a@^1: + version: "1.0.0" + +b@^1: + version: "1.0.0" + +c@^1: + version: "1.0.0" +`), + ); + + await expect( + graph + .listChangedPackages({ + ref: 'origin/master', + analyzeLockfile: true, + }) + .then(pkgs => pkgs.map(pkg => pkg.name)), + ).resolves.toEqual(['b']); + }); }); diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 9d1745c7c5..35fe943606 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -393,7 +393,7 @@ export class PackageGraph extends Map { // Merge the dependency graph from the other lockfile into this one in // order to be able to detect removals accurately. { - const otherGraph = thisLockfile.createSimplifiedDependencyGraph(); + const otherGraph = otherLockfile.createSimplifiedDependencyGraph(); for (const [name, dependencies] of otherGraph) { const node = graph.get(name); if (node) {