Fix npm workspace package-lock.json entries incorrectly updated when requirement unchanged#14480
Fix npm workspace package-lock.json entries incorrectly updated when requirement unchanged#14480
Conversation
…irement unchanged When Dependabot updates a dependency in a workspace package (e.g., cli/package.json), it runs `npm install dep@newversion --workspace=cli --package-lock-only`. This causes npm to update `packages["cli"].dependencies.dep` in the lockfile to the exact installed version, even when the workspace's package.json requirement hasn't changed (e.g., versioning strategy is increase-if-necessary). Extend `restore_locked_package_dependencies` to also restore workspace package entries (packages["<workspace>"]) so they mirror the workspace package.json requirement rather than reflecting the exact npm-installed version. Add helper methods `workspace_package_files` and `workspace_lockfile_key`, and add a test assertion for the behavior. Co-authored-by: thavaahariharangit <164553783+thavaahariharangit@users.noreply.github.com>
…ace content Co-authored-by: thavaahariharangit <164553783+thavaahariharangit@users.noreply.github.com>
ba0aaa9 to
f8cc697
Compare
There was a problem hiding this comment.
Pull request overview
This PR prevents npm workspace package-lock.json metadata from being rewritten to a new range/version when the workspace package.json requirement was intentionally left unchanged (e.g., increase-if-necessary updates), aligning workspace lockfile entries back to the manifest requirements.
Changes:
- Extend lockfile “requirement restoration” logic from the root
packages[""]entry to workspacepackages["<workspace>"]entries. - Add targeted string-replacement helpers to scope replacements to a specific
packagessection (instead of globalgsub). - Expand
NpmLockfileUpdaterspecs and add new fixture projects covering multiple workspace/range scenarios.
Reviewed changes
Copilot reviewed 12 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb | Adds workspace-aware restoration of packages["<workspace>"].(dev)Dependencies requirements and introduces helper methods to scope lockfile substitutions. |
| npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb | Extends coverage to ensure workspace requirement ranges are preserved (and not restored when requirements were intentionally updated). |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_outdated_deps_requirement_changed/package.json | New workspace fixture root manifest for “requirement changed” scenario. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_outdated_deps_requirement_changed/package-lock.json | New fixture lockfile simulating npm overwriting workspace metadata. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_outdated_deps_requirement_changed/app/package.json | New fixture workspace manifest for “requirement changed” scenario. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_same_dep/package.json | New fixture root manifest for multi-workspace shared dependency scenario. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_same_dep/package-lock.json | New fixture lockfile for shared dependency across workspaces. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_same_dep/app-a/package.json | New fixture workspace manifest (app-a). |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_same_dep/app-b/package.json | New fixture workspace manifest (app-b). |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_different_ranges/package.json | New fixture root manifest for different ranges per workspace scenario. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_different_ranges/package-lock.json | New fixture lockfile for different ranges per workspace. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_different_ranges/app-a/package.json | New fixture workspace manifest (app-a). |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_different_ranges/app-b/package.json | New fixture workspace manifest (app-b). |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_dep_in_root_and_workspace/package.json | New fixture root manifest where dep exists in root + workspace with different ranges. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_dep_in_root_and_workspace/package-lock.json | New fixture lockfile for dep existing in both root + workspace. |
| npm_and_yarn/spec/fixtures/projects/npm8/workspace_dep_in_root_and_workspace/app/package.json | New fixture workspace manifest for root+workspace dep scenario. |
Files not reviewed (4)
- npm_and_yarn/spec/fixtures/projects/npm8/workspace_dep_in_root_and_workspace/package-lock.json: Language not supported
- npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_different_ranges/package-lock.json: Language not supported
- npm_and_yarn/spec/fixtures/projects/npm8/workspace_multiple_packages_same_dep/package-lock.json: Language not supported
- npm_and_yarn/spec/fixtures/projects/npm8/workspace_outdated_deps_requirement_changed/package-lock.json: Language not supported
npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb
Outdated
Show resolved
Hide resolved
|
@robaiken As discussed during standup, I’ve recreated the issue here. PR: https://github.com/thavaahariharangit/errorneous-lock-update-recreation/pull/2/changes Running the Dependabot CLI before and after this fix, I got below output (comparison given below), This confirms this fix resolves the issue: #14460
|
|
covered here: #14515 |

What are you trying to accomplish?
When updating a workspace dependency (e.g.,
cli/package.json) with a versioning strategy likeincrease-if-necessary, Dependabot runsnpm install dep@newversion --workspace=cli --package-lock-only. npm v11 writes the new version range intopackages["cli"].dependencies.depin the lockfile even thoughcli/package.jsonitself wasn't changed. This results in a PR that modifies the lockfile's workspace entry without a correspondingpackage.jsonchange. See npm/documentation#1914.Anything you want to highlight for special attention from reviewers?
The existing
restore_locked_package_dependenciesalready handles this forpackages[""](root) — it restores requirement strings that npm overwrites with the exact installed version. This PR extends the same logic to workspace package entries (packages["<workspace>"]).The
gsub-based string replacement is inherited from the existing root-package approach. It carries the same theoretical fragility (could affect multiple lockfile locations if the same dep+version string appears elsewhere), but in practice this is safe since workspace entries use dependency keys ("dep": "version") that don't appear innode_modules/*entries.New helpers:
workspace_package_files— non-rootpackage.jsonfiles associated with the current lockfileworkspace_lockfile_key— maps a workspace file to itspackages["<key>"]entry using relative path from lockfile directoryBefore fix — after running
npm install dep@0.0.2 --workspace=app, lockfile contains:After fix — restored to mirror
packages/app/package.json:How will you know you've accomplished your goal?
Extended the existing
workspace_outdated_deps_not_in_root_package_jsontest to assert thatpackages["bump-version-for-cron"]["devDependencies"]["@swc/core"]remains"^1.3.37"after updating@swc/corefrom1.3.40→1.3.44. Manually confirmed with npm v11.9.0 that without the fix npm changes"^0.0.1"to"^0.0.2"in the workspace lockfile entry.Checklist
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
api.launchpad.net/usr/bin/add-apt-repository add-apt-repository -y ppa:git-core/ppa grep rity.crt -q la/emSign_Root_CA_-_G1.crt e u3_amd64.deb(dns block)gitlab.com/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/_temp/ghcca-node/node/bin/corepack npm install babel-preset-php@gitlab:kornelski/babel-preset-php#5fbc24ccc37bd72052ce71ceae5b4934feb3ac19 --force --ignore-scripts --package-lock-only om/Graffino/Browcorepack m/_cacache/tmp/gnpm /usr/bin/basenaminstall git conf�� --global credential.helpe--package-lock-only ub.com/.insteadOf .com/ crt dabot-core/depennpm git(dns block)/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/_temp/ghcca-node/node/bin/corepack npm install babel-preset-php@gitlab:kornelski/babel-preset-php#5fbc24ccc37bd72052ce71ceae5b4934feb3ac19 --force --ignore-scripts --package-lock-only k/dependabot-corgit /usr/share/ca-ceconfig e --file /home/r--global sh -c e/common sed k/_temp/ghcca-node/node/bin/corepack elper '!/home/rugit /usr/share/ca-cerev-parse dabot-core/depen--show-prefix git(dns block)ports.ubuntu.com/usr/lib/apt/methods/http /usr/lib/apt/methods/http(dns block)If you need me to access, download, or install something from one of these locations, you can either:
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.