Skip to content

fix: stop manifest search on devEngines package managers - #811

Open
michkot wants to merge 12 commits into
nodejs:mainfrom
michkot:fix/issue-779-devengines-lookup
Open

fix: stop manifest search on devEngines package managers#811
michkot wants to merge 12 commits into
nodejs:mainfrom
michkot:fix/issue-779-devengines-lookup

Conversation

@michkot

Copy link
Copy Markdown

fixes#779

Summary

This branch is intentionally a small stack, not a single behavioral change.

The main fix is the manifest-selection bug from #779: when scanning parent directories, loadSpec now stops at the nearest package.json that defines either packageManager or devEngines.packageManager. Before that, the scan only stopped for packageManager, so a closer manifest using only devEngines.packageManager could be skipped and a parent manifest could win instead.

The branch then adds two explicit follow-up behavior changes that became clearer while fixing the lookup:

  • top-level packageManager: "" and packageManager: null are now treated as defined but invalid values of the nearest manifest, so lookup stops there and reports an invalid "packageManager" instead of treating those values as absent and falling through to a parent package.json
  • Engine.findProjectSpec now honors non-error onFail for package-manager name mismatches when the selected project package manager comes only from devEngines.packageManager; in those cases, onFail: warn / ignore can fall back to the requested package manager instead of throwing

The branch also improves invalid "packageManager" errors so they mention which field in the selected package.json was used.

Decision table

The table below describes the outcome for the nearest selected manifest. It intentionally ignores transparent-command behavior, which can still choose fallback for separate reasons.

packageManagerdevEngines.packageManagerEffective onFailSelected-manifest resultEngine.findProjectSpec result
absentabsentn/acontinue scanning upwardif nothing else is found: NoSpec -> use fallback
validabsentn/astop and use packageManagerenforce that package manager
invalidabsentn/astop and report invalid packageManagerthrow invalid packageManager error
absentvaliderrorstop and use devEngines.packageManagerenforce that package manager; mismatch throws
absentvalidwarn / ignorestop and use devEngines.packageManagermismatch falls back to requested package manager
absentinvaliderrorstop and report invalid devEnginesthrow invalid devEngines.packageManager error
absentinvalidwarn / ignorestop at nearest manifest and return NoSpecuse fallback
validvalid and matchinganystop and use packageManagerenforce packageManager
validvalid but mismatchingerrorstop and report field mismatchthrow mismatch error
validvalid but mismatchingwarn / ignorestop and use packageManagerenforce packageManager
validinvaliderrorstop and report invalid devEnginesthrow invalid devEngines.packageManager error
validinvalidwarn / ignorestop and use packageManagerenforce packageManager

Tests

  • keep one CLI regression for the mixed-field case where a child devEngines.packageManager must win over a parent packageManager
  • add Engine.findProjectSpec coverage for:
    • nearest child devEngines.packageManager when the parent has no package-manager field
    • nearest child devEngines.packageManager over a parent packageManager
    • nearest invalid child devEngines.packageManager with onFail: warn, which must stop the search and use fallback rather than a parent packageManager
    • nearest child packageManager: ""
    • nearest child packageManager: null

Notes

  • This branch changes behavior for packageManager: "" and packageManager: null: they no longer behave like an absent field during upward lookup.
  • Follow-up suggestion: onFail should probably not affect structurally invalid devEngines.packageManager definitions, for example when name is not a string or version is not a valid semver range. Those look like malformed configuration rather than mismatch-policy choices, so they likely should always throw. The mismatch cases can still remain controlled by onFail.

Commit ordering

  • the first two commits are pure repros, so reviewers can inspect the failing scenarios before reading the fix
  • the next functional commits are ordered from root cause to follow-up semantics: first nearest-manifest selection, then explicit packageManager: "" | null handling, then the onFail: warn fallback behavior, then the error-path improvement
  • the CLI coverage reduction is kept as its own test-shaping commit so the single retained end-to-end repro stays obvious
  • the final docs and refactor commits are intentionally last, so reviewers can read behavioral changes without rename / comment / control-flow cleanup noise mixed into them

@michkot
michkotforce-pushed the fix/issue-779-devengines-lookup branch 2 times, most recently from a148873 to 355d22dCompareApril 18, 2026 04:51
@michkot
michkotforce-pushed the fix/issue-779-devengines-lookup branch from 355d22d to 1cc5eb0CompareApril 18, 2026 05:19
@michkot

Copy link
Copy Markdown
Author

@aduh95 May I ask you for a code review?

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Asymmetry between searching for closest package.json when packageManager is defined vs when devEngines.packageManager is defined after #643

1 participant

@michkot