Skip to content

fix(pm): detect stale lockfile in ut install - #2797

Merged
fireairforce merged 3 commits into
nextfrom
fix/pm-install-stale-lockfile
Apr 16, 2026
Merged

fireairforce merged 3 commits into
nextfrom
fix/pm-install-stale-lockfile

Conversation

@elrrrrrrr

Copy link
Copy Markdown
Contributor

Summary

  • ut install was silently installing stale node_modules whenever package.json drifted from package-lock.json. Regression from refactor(pm): pipeline deps resolver #2576 — the old ensure_package_lock call (which ran is_pkg_lock_outdated) got replaced with a bare file-exists check in Self::install, so the freshness check never fired on the plain install path.
  • Put the freshness check back, but keep refactor(pm): pipeline deps resolver #2576's pipeline benefit: a stale lockfile now falls through to resolve_with_pipeline (concurrent resolve + download), not the serial ensure_package_lock path.
  • Corrupt lockfile / unreadable package.json during the freshness check is treated as stale — safer than installing from a file we can't validate.

Repro the bug (before this fix)

// package.json
{ "dependencies": { "abbrev": "^2.0.0", "ini": "^5.0.0" } }

// package-lock.json  (stale — empty root deps)
{ "lockfileVersion": 3, "packages": { "": { "dependencies": {} } } }

ut install finishes silently with an empty node_modules. User thinks all is well.

Test plan

  • cargo clippy -p utoo-pm --all-targets -- -D warnings --no-deps
  • cargo test -p utoo-pm (helper::lock suite green)
  • Local reproduction of the regression — observed the silent-stale-install behaviour pre-fix, then verified the same fixture now prints package-lock.json is outdated, regenerating, installs both packages, and rewrites the lockfile.
  • New e2e case Case 8.2: stale lockfile detection in e2e/utoo-pm.sh; the tracked e2e/pm/stale-lockfile/package-lock.json is intentionally stale so the test is self-contained

🤖 Generated with Claude Code

@elrrrrrrr
elrrrrrrr marked this pull request as draft April 16, 2026 12:18

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a mechanism to detect and regenerate stale package-lock.json files during installation, ensuring they remain synchronized with package.json. It includes a new E2E test case to verify this logic. Feedback highlights potential performance overhead due to redundant file parsing and suggests refactoring to allow skipping the check when the lockfile is known to be fresh.

Comment thread crates/pm/src/service/install.rs Resolved
elrrrrrrr and others added 3 commits April 16, 2026 21:52
`fs::try_exists(&lock_path)` check in Self::install. As a result any
`ut install` with an existing package-lock.json skipped the manifest
resolution entirely — even when package.json had been edited — silently
installing the stale tree from the lockfile.

Re-introduce the freshness check: when the lockfile exists we now still
run `is_pkg_lock_outdated`. Stale lockfiles (or freshness-check errors
— corrupt lockfiles etc.) fall through to the pipeline cold path so we
keep the concurrent resolve + download benefit #2576 was built for.

Regression guard lives in `e2e/pm/stale-lockfile/` — a fixture that
commits a package-lock.json whose root `dependencies` is empty while
package.json declares abbrev and ini. Case 8.2 in e2e/utoo-pm.sh
asserts:
  - `ut install` regenerates the lockfile
  - both packages end up in node_modules/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Drop duplicate `tracing::warn` in Self::install; `is_pkg_lock_outdated`
  already logs the specific mismatch reason (root deps changed, engines
  changed, …). Users now see one warn instead of two per stale detection.
- Collapse `lock_exists` + `lock_stale` into a single `use_fresh_lock`
  expression — removes the double-negation.
- Drop the committed stale `package-lock.json` fixture and the
  `git checkout --` dance in Case 8.2. The e2e now writes the stale
  lockfile inline via heredoc, which:
    * survives local edits / worktrees / stashed state
    * avoids the `git add -f` fight with .gitignore
    * keeps the fixture dir to just package.json (the declarative half)
- Assert root deps via `node` inspection of `packages[""].dependencies`
  instead of `grep "abbrev"`, which would match transitive
  `node_modules/abbrev` entries and hide a regression where only the
  tree — not the root declaration — got rewritten.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The freshness check was reading the root package.json three times per
`ut install` — once as DepsView, once inside find_workspaces (for the
"workspaces" field), once as EnginesView. Each workspace was also read
twice (once by find_workspaces, again as DepsView).

Load the root as full `PackageJson` via `get_or_load_package_json` so
subsequent reads on the same path hit the in-process cache (filled by
`update_package_json`'s write-through). Within this function, drop the
DepsView/EnginesView intermediates and read the fields directly off the
same `PackageJson`; workspaces come back pre-loaded on the
`find_workspaces` tuple so we don't re-load them either.

Cache coherence: `update_package_json` already calls `set_package_json`
write-through after every edit, and every `ut install` starts a fresh
process with a cold cache — so external edits (IDE, `git checkout`)
still see a disk read next run.

Tests: the original `test_is_pkg_lock_outdated` simulated a
"write file, re-check" flow that doesn't happen in real invocations
(every mutation goes via `update_package_json` + write-through). Split
it into five independent scenarios, each with its own TempDir so cache
keys don't collide, which is also the idiomatic "one test per case".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@elrrrrrrr
elrrrrrrr force-pushed the fix/pm-install-stale-lockfile branch from 2488786 to 33ca6e6 Compare April 16, 2026 14:10
@elrrrrrrr elrrrrrrr added the A-Pkg Manager Area: Package Manager label Apr 16, 2026
@elrrrrrrr
elrrrrrrr marked this pull request as ready for review April 16, 2026 14:18
@fireairforce
fireairforce merged commit cab4a3d into next Apr 16, 2026
27 checks passed
@fireairforce
fireairforce deleted the fix/pm-install-stale-lockfile branch April 16, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Pkg Manager Area: Package Manager

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants