fix(check): honor package-lock.json when installing from package_file - #2829
Conversation
The lockfile copied into the tool staging directory was being ignored because npm install ran with --no-package-lock, which tells npm to skip reading lockfiles entirely. Linters were installed at the latest versions matching the package.json semver ranges instead of the locked versions, contradicting the documented behavior. Drop --no-package-lock when a lockfile was staged (package_file set with no package_filters), and keep it otherwise since a filtered package.json would conflict with the full lockfile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.
Tip: disable this comment in your organization's Code Review settings.
There was a problem hiding this comment.
Pull request overview
Fixes qlty check Node linter installs to actually respect a staged package-lock.json when package_file is used (and package_filters are not), aligning behavior with the documented lockfile semantics.
Changes:
- Conditionally drops
npm install --no-package-lockwhen a lockfile is staged so npm will honor locked versions. - Adds unit tests covering the “lockfile staged” case and the “package_filters ignores lockfiles” case.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Coverage Impact - ubuntu-latest This PR will not change total coverage. Modified Files with Diff Coverage (2)
🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
|
Coverage Impact - macos-15 ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (2)
🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
The initial tool installation (npm install <tool>@<version>) generates a package-lock.json in the staging directory, so checking for lock file existence wrongly dropped --no-package-lock for repositories without a lock file. This surfaced in the stylelint plugin test, where the leftover lock pinned stylelint to the exact plugin version instead of re-resolving the merged package.json. Track whether update_package_json actually copied the user's lock file and key the install command off that instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The Plugin Tests failure ( Fixed in 60938c8 by tracking whether 🤖 Generated with Claude Code |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
noahd1
left a comment
There was a problem hiding this comment.
Approving - but please evaluate the recently added GitHub Co-Pilot comment. May be worth addressing, I can't tell.
Its an pre-existing and unrelated code cleanup, but might as well. |
) ## Summary PHP counterpart to #2829: `composer.lock` was not honored when installing PHP linters with `package_file`, contradicting the [documented behavior](https://docs.qlty.sh/cli/linter-extensions#lock-files) ("When using `package_file`, Qlty respects the locked versions for reliability"). ## Problem Since #1658, `update_composer_json` copies the repo's `composer.lock` into the tool staging directory (when no `package_filters` are configured), but the package file was then installed with `composer update --no-interaction --ignore-platform-reqs` — and `composer update` re-resolves dependencies and rewrites the lock, ignoring the locked versions entirely. **Repro:** a repo with `"squizlabs/php_codesniffer": "^3.8"` in `composer.json`, a lockfile pinning `3.8.0`, and `package_file = "composer.json"` in `qlty.toml` installed phpcs **3.13.5** instead of the locked **3.8.0**. ## Fix When the repository's `composer.lock` was staged, attempt `composer install` (which installs exactly what the lock records). If it fails — a stale lock, a missing package, a constraint mismatch, anything composer's lock validation rejects — fall back to `composer update` with a warning, restoring the previous behavior at the cost of one fast-failing composer run. One wrinkle: qlty collapses `require-dev` into `require` in the staged composer.json, but `composer install` validates required prod packages against the lock's prod section — so a tool locked under `packages-dev` (the common case for linters) would hard-error with "Required package ... is not present in the lock file". The staged lock therefore gets the same treatment: `packages-dev` entries are collapsed into `packages`. ## Testing - New unit tests: lock staged → `composer install`; install failure → falls back to `composer update`; `package_filters` set → lock not staged, `composer update` only; dev packages collapsed in the staged lock - Verified end-to-end: locked phpcs 3.8.0 installs (including when locked under `require-dev`); a stale lock (constraint `^3.9` vs locked `3.8.0`) falls back to update and succeeds; without a lockfile, behavior is unchanged - phpstan, php-codesniffer, and php-cs-fixer plugin test suites pass locally - Full qlty-check test suite passes (249 tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Automated PR for release 0.639.0. Review (and edit, if needed) the changelog entry below, then merge this pull request to publish the release. ## Draft release notes ### Fixed - Honor `package-lock.json` when installing node-based linters from `package_file`, so the locked versions are respected instead of npm resolving the latest versions matching the `package.json` semver ranges (#2829) - Honor `composer.lock` when installing PHP linters from `package_file`, so the locked versions are respected instead of `composer update` re-resolving to the latest matching versions (#2830) Co-authored-by: qlty-releases[bot] <181762136+qlty-releases[bot]@users.noreply.github.com>

Summary
Fixes a user-reported bug where
package-lock.jsonwas not honored when installing node-based linters withpackage_file, contradicting the documented behavior ("When usingpackage_file, Qlty respects the locked versions for reliability").Problem
Since #1658,
update_package_jsoncopies the repo'spackage-lock.jsoninto the tool staging directory (whenpackage_fileis set and nopackage_filtersare configured). However,package_file_installthen runsnpm install --force --no-package-lock— and npm's--no-package-lockflag makes npm ignore lockfiles during install. The flag predates the lockfile-copy feature and was never removed, so the copied lockfile had no effect and npm resolved the latest versions matching thepackage.jsonsemver ranges.Repro: a repo with
"eslint": "^8.0.0"inpackage.json, a lockfile pinning eslint to8.0.0, andpackage_file = "package.json"inqlty.tomlinstalled eslint 8.57.1 instead of the locked 8.0.0.Fix
Drop
--no-package-lockfrom the install command when a lockfile was staged. The flag is kept when no lockfile is present, and in thepackage_filterscase, where no lockfile is copied since the filteredpackage.jsonwould conflict with the full lockfile (also matches the documented behavior: "When usingpackage_filewithpackage_filters, the lock files are ignored").Testing
package_filterswith lockfile → lockfile not staged, flag kept8.0.0🤖 Generated with Claude Code