Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix npm_and_yarn engine detection #11392

Merged
merged 20 commits into from
Jan 28, 2025
Merged

Fix npm_and_yarn engine detection #11392

merged 20 commits into from
Jan 28, 2025

Conversation

ntkme
Copy link
Contributor

@ntkme ntkme commented Jan 23, 2025

What are you trying to accomplish?

Detect npm engine version is broken without a lockfile even if the version is specified in package.json in some cases:

  • { "packageManager": "[email protected]" } works
  • { "engines": { "npm": "10.0.0" } } works
  • { "engines": { "npm": ">=10" } } is broken with message No version requirement found for npm

Anything you want to highlight for special attention from reviewers?

The root cause is that while trying to setup the engine, delete_if in ruby was used. delete_if modifies the input array in place, and effectively removes entries from @engines unless they match a very specific format.

The side effect of removing valid entries from @engines breaks the version detection from engines at a later time.

How will you know you've accomplished your goal?

I don't know for sure as I don't know how to test this end to end. I just manually walked through the code and found that the side effect seems to be the problem here.

detected_version = detect_version(name)
# if we have a detected version, we check if it is deprecated or unsupported
if detected_version
package_manager = package_manager_class.new(
detected_version: detected_version.to_s
)
return package_manager if package_manager.deprecated? || package_manager.unsupported?
end
installed_version = installed_version(name)
Dependabot.logger.info("Installed version for #{name}: #{installed_version}")
package_manager_requirement = find_engine_constraints_as_requirement(name)

At line 312, the call to detect_version calls VersionSelector and removes engines entries not matching a specific format due to side effect using delete_if:

 detected_version = detect_version(name)

At line 325, because the engines entries got removed, it fails to detect the constraints:

 package_manager_requirement = find_engine_constraints_as_requirement(name) 

Checklist

  • I have run the complete test suite to ensure all tests and linters pass.
  • I have thoroughly tested my code changes to ensure they work as expected, including adding additional tests for new functionality.
  • I have written clear and descriptive commit messages.
  • I have provided a detailed description of the changes in the pull request, including the problem it addresses, how it fixes the problem, and any relevant details about the implementation.
  • I have ensured that the code is well-documented and easy to understand.

@ntkme ntkme requested a review from a team as a code owner January 23, 2025 16:48
@ntkme
Copy link
Contributor Author

ntkme commented Jan 23, 2025

cc @kbukum1 @sachin-sandhu

@abdulapopoola
Copy link
Member

Thanks @ntkme ! Can you please add a test please?

@ntkme ntkme force-pushed the fix-npm branch 2 times, most recently from 424692b to b1b823e Compare January 23, 2025 20:16
@ntkme
Copy link
Contributor Author

ntkme commented Jan 23, 2025

@abdulapopoola Test added.

For reference, the added test fails on the main branch with:

  1) Dependabot::NpmAndYarn::PackageManagerHelper#find_engine_constraints_as_requirement when the engines field contains valid constraints when package manager lockfile does not exist returns a requirement for npm with the correct constraints
     Failure/Error: expect(requirement).to be_a(Dependabot::NpmAndYarn::Requirement)
       expected nil to be a kind of Dependabot::NpmAndYarn::Requirement

It passes with the fix in this PR.

@ntkme ntkme mentioned this pull request Jan 23, 2025
1 task
@ntkme ntkme force-pushed the fix-npm branch 2 times, most recently from 7f1f8ae to 1d1c8b2 Compare January 23, 2025 20:55
@kbukum1
Copy link
Contributor

kbukum1 commented Jan 23, 2025

@ntkme ,

Sorry for let reply. Checking it now.

@kbukum1 kbukum1 self-assigned this Jan 23, 2025
@kbukum1
Copy link
Contributor

kbukum1 commented Jan 23, 2025

Thank you, @ntkme, for identifying the issue and proposing a solution. I’ll be making further updates to enhance the stability of the solution. Once it’s ready, I’ll let you know.

CC: @abdulapopoola

@jkowalleck
Copy link

jkowalleck commented Jan 25, 2025

{ "engines": { "npm": ">=10" } } is broken with message No version requirement found for npm

does this PR also fix the following cases?

  • { "engines": { "npm": "^7" } } -- not tested
  • { "engines": { "npm": "^6.2 || ^7" } } -- not tested
  • { "engines": { "npm": "6 - 11" } } -- causes Dependabot detected the following npm requirement for your project: ''.

@ntkme
Copy link
Contributor Author

ntkme commented Jan 26, 2025

@jkowalleck As you’ve noticed, there are some version range syntax that are currently not parsable, but supporting those additional syntax is out of my scope of this PR, which is just fixing the side effect of delete_if.

In my opinion additional range syntax can be a separate feature request, but I will leave it to @kbukum1 to decide what to do with other unsupported npm version range syntax.

@kbukum1
Copy link
Contributor

kbukum1 commented Jan 27, 2025

@jkowalleck As you’ve noticed, there are some version range syntaxes that are currently not parsable. Supporting those additional syntaxes is out of the scope of this PR, which focuses on fixing the side effect of delete_if.

In my opinion, supporting additional range syntaxes can be addressed as a separate feature request, but I will leave it to @kbukum1 to decide how to handle other unsupported npm version range syntaxes.

Thank you for the information, @ntkme.

@jkowalleck, yes, I am currently working on this and plan to include semver specification validation as part of this PR. I believe it’s better to address all related issues together in one PR to ensure a comprehensive fix.

CC: @abdulapopoola

@kbukum1
Copy link
Contributor

kbukum1 commented Jan 27, 2025

{ "engines": { "npm": ">=10" } } is broken with message No version requirement found for npm

Does this PR also fix the following cases?

  • { "engines": { "npm": "^7" } } -- not tested
  • { "engines": { "npm": "^6.2 || ^7" } } -- not tested
  • { "engines": { "npm": "6 - 11" } } -- causes Dependabot detected the following npm requirement for your project: ''.

Hi @jkowalleck, just to clarify: the fix I am working on will handle all valid [semver specifications](https://semver.org/#ranges) for version ranges. However, it will not include support for { "engines": { "npm": "6 - 11" } }, as that syntax does not conform to the semver standard.

If there's anything specific you believe is missing or should be addressed, please let me know!

CC: @abdulapopoola

@broksonic21
Copy link

As a user, any chance we can get this merged in and you work out further improvements later? We have multiple repositories not getting any security or dependabot updates due to this bug (assuming it fixes #11234 (comment), which I believe it does)

@github-actions github-actions bot added L: elixir:hex Elixir packages via hex L: go:modules Golang modules L: dotnet:nuget NuGet packages via nuget or dotnet labels Jan 27, 2025
@kbukum1 kbukum1 requested a review from sachin-sandhu January 27, 2025 20:39
@kbukum1
Copy link
Contributor

kbukum1 commented Jan 27, 2025

Hi @ntkme, @jkowalleck, @broksonic21,

I’ve made changes to address most of the issues related to checking engine constraints and retrieving versions from constraints. Please let me know if you see any issue that is not covered related to that?

@ntkme, I’ve retained your changes, particularly the spec modifications, and integrated them with the new implementation. If the new changes are accepted successfully, they should align with the solution you initially proposed.

Looking forward to your feedback!

CC: @abdulapopoola

@kbukum1
Copy link
Contributor

kbukum1 commented Jan 27, 2025

Review Tip: We’ve implemented a more comprehensive method for extracting versions from constraints, ensuring that the version is obtained only when it fits the requirements and does not risk issues such as version deprecation. This approach focuses on providing accurate version detection without causing unintended behavior.

Note: We return versions from constraints only when we can confidently determine the version falls within the same major version or is an exact match. For example, constraints like ^6, ^6.5, or ^6.7 clearly indicate that the package manager’s major version is 6, so we accept these versions. However, for less specific constraints such as >=6, we do not select a version. This avoids potential deprecation issues, as choosing version 6 might cause problems if newer major versions like 7, 8, or 9 are also supported.

CC: @sachin-sandhu , @abdulapopoola

@bcomnes
Copy link

bcomnes commented Jan 27, 2025

Will this fix these types of errors that started showing up last week on tons of my node module repos?

Screenshot 2025-01-27 at 10 41 23 AM

@kbukum1
Copy link
Contributor

kbukum1 commented Jan 27, 2025

Will this fix these types of errors that started showing up last week on tons of my node module repos?

Screenshot 2025-01-27 at 10 41 23 AM

@bcomnes,

This change improves our ability to detect the version from the engines field. If your npm version is specified as higher than version 6 (e.g., { "npm": "^7" }) or if your package-lock.json has a lockfileVersion higher than "1", you should not encounter this error.

To provide a more accurate answer, I would need additional details about your configuration, such as the engines field, the packageManager field in your package.json, or the lockfileVersion in your package-lock.json.

Tagging @amazimbe, who may have more insight into the specific scenarios where the unsupported error is raised.

@bcomnes
Copy link

bcomnes commented Jan 27, 2025

In my case, these would be for packages that don't specify an engines field or have a committed lockfile in the repo, a scenario not uncommon in module codebases. This arrangement has been working in dependabot for years prior to whatever change was deployed last week.

EDIT: Related issue #11373

It's really important you restore previous functionality as whatever was changed last week effectively disabled dependeabot on thousands (at least!) of modules.

sachin-sandhu
sachin-sandhu previously approved these changes Jan 27, 2025
Copy link
Contributor

@sachin-sandhu sachin-sandhu left a comment

Choose a reason for hiding this comment

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

Approving after the discussion.

@amazimbe
Copy link
Contributor

Will this fix these types of errors that started showing up last week on tons of my node module repos?
Screenshot 2025-01-27 at 10 41 23 AM

@bcomnes,

This change improves our ability to detect the version from the engines field. If your npm version is specified as higher than version 6 (e.g., { "npm": "^7" }) or if your package-lock.json has a lockfileVersion higher than "1", you should not encounter this error.

To provide a more accurate answer, I would need additional details about your configuration, such as the engines field, the packageManager field in your package.json, or the lockfileVersion in your package-lock.json.

Tagging @amazimbe, who may have more insight into the specific scenarios where the unsupported error is raised.

Thanks both. The unsupported error is only raised when we detect npm 6 as the version. https://github.com/dependabot/dependabot-core/blob/main/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb#L61 . if the npm lockfile has no content or it's version is >=2, the detected version will be npm8. Otherwise, we return npm 6.

@bcomnes
Copy link

bcomnes commented Jan 27, 2025

In some cases, the package.json engines field also defined "npm": ">=10" but received the same unsupported error starting last week.

It seems like if no npm version is specified anywhere, or the engine parsing/resolution fails for whatever reason, you should default to whatever version ships with node lts with a warning, rather than defaulting to not working.

randhircs
randhircs previously approved these changes Jan 27, 2025
Copy link
Member

@randhircs randhircs left a comment

Choose a reason for hiding this comment

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

To me looks good. Please proceed.

else
Dependabot.logger.warn("Unrecognized constraint format for #{name}: #{constraint}")
constraint
end
Copy link
Contributor

@kbukum1 kbukum1 Jan 27, 2025

Choose a reason for hiding this comment

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

To ensure a safe deployment, a feature flag has been added.

@kbukum1 kbukum1 requested review from randhircs and kbukum1 January 28, 2025 00:00
Copy link
Contributor

@sachin-sandhu sachin-sandhu left a comment

Choose a reason for hiding this comment

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

LGTM

@kbukum1 kbukum1 merged commit e28f22d into dependabot:main Jan 28, 2025
54 checks passed
@ntkme ntkme deleted the fix-npm branch January 28, 2025 01:05
@kbukum1
Copy link
Contributor

kbukum1 commented Jan 28, 2025

Hi @ntkme,

The feature flag for this change is now enabled. I’d greatly appreciate your feedback on whether everything is working as intended or if you encounter any issues. If the approach proves problematic, I may disable the feature flag and address any concerns.

Thank you for your time and support!

@shazron
Copy link

shazron commented Jan 28, 2025

Can you clarify? All of my repos have this issue.

Dependabot updates were working before this issue, which I assume they passed before because they used whatever npm version came with the node version and that npm version was acceptable.

I believe I am the typical case on Github (no package.json engines.npm property, no package-lock.json lockfile, no package.json packageManager property).

All my repos have their package.json engines.node property as >=18, which would imply the included npm is >6 since node-18 ships with at least npm-9.

A search on Github reveals 801 PRs are affected which may reflect the typical case I described.

Before I apply my fixes - from what I gathered from the previous comments and other issues, for package.json:

These types of engines.npm version specifiers (version can be 7,8,9,10,11 currently) will make the npm check pass because they are unambiguous:

  • ^7
  • ~7
  • 7

These types of engines.npm version specifiers (version can be 7,8,9,10,11 currently) will make the npm check fail because they are ambiguous:

  • the property is missing
  • >=7
  • >7

Similarly for the package.json packageManager property, (version can be 7,8,9,10,11 currently) this will make the npm check pass if the version is unambiguous:

  • npm@7
  • npm@^7
  • npm@~7

Are my assumptions correct?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
L: dotnet:nuget NuGet packages via nuget or dotnet L: elixir:hex Elixir packages via hex L: go:modules Golang modules L: javascript L: ruby:bundler RubyGems via bundler
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.