-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Add support for npm 7 lockfiles #3011
Conversation
This adds a couple launch scripts to debug npm and yarn tests when running then using jest. The launch scripts complained about a missing config file so added this and changed the default env from jsdom to node as this is what we use. Changed `eslint-plugin-prettier` to `eslint-config-prettier` as this seems like the recommended extension now when using prettier with eslint: https://prettier.io/docs/en/integrating-with-linters.html This extracts these changes from the npm 7 branch: #3011
01ed498
to
144e747
Compare
7998d92
to
759836f
Compare
5017ca7
to
0dc71c8
Compare
npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb
Outdated
Show resolved
Hide resolved
15ffd14
to
3bd9de4
Compare
Extract all project based spec fixtures from npm7 pr to reduce diff: #3011 The file updater specs changes are copied but excluding the npm 7 specifc specs.
@@ -222,7 +232,7 @@ def handle_npm_updater_error(error, lockfile) | |||
# This happens if a new version has been published but npm is having | |||
# consistency issues and the version isn't fully available on all | |||
# queries | |||
if error_message.start_with?("No matching vers") && | |||
if error_message.include?("No matching vers") && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we match on the full string here? No matching version
def post_process_npm_lockfile(original_content, updated_content) | ||
updated_content = | ||
replace_project_metadata(updated_content, original_content) | ||
def post_process_npm_lockfile(original_content, updated_content, lockfile_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in pair-review: this might be a good candidate to extract out into its own class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah agreed, there's a bunch of pre/post-processing that's intertwined and would probably be a lot easier to grok if it was in one place.
|
||
context "when a git src dependency doesn't have a valid package.json" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was only removed from npm 7 as it takes absolutely ages and actually manages to do the update, the spec exists here for npm 6:
dependabot-core/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb
Line 79 in bfbc137
context "when a git src dependency doesn't have a valid package.json" do |
|
||
context "with a corrupted npm lockfile (version missing)" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer failing in npm 7, npm 6 spec is here
dependabot-core/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb
Line 64 in bfbc137
context "with a corrupted npm lockfile (version missing)" do |
|
||
# NOTE: This no longer raises in npm 7 | ||
context "when there is a private git dep we don't have access to" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer fails for npm 7, tests have moved here:
dependabot-core/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb
Lines 131 to 170 in bfbc137
# NOTE: This no longer raises in npm 7 | |
context "when there is a private git dep we don't have access to" do | |
let(:files) { project_dependency_files("npm6/github_dependency_private") } | |
let(:dependency_name) { "strict-uri-encode" } | |
let(:version) { "1.1.0" } | |
let(:requirements) { [] } | |
it "raises a helpful error" do | |
expect { updated_npm_lock_content }. | |
to raise_error(Dependabot::GitDependenciesNotReachable) do |error| | |
expect(error.dependency_urls). | |
to eq( | |
[ | |
"https://github.com/hmarr/dependabot-test-private-npm-package.git/" | |
] | |
) | |
end | |
end | |
end | |
end | |
describe "npm 7 specific" do | |
# NOTE: This used to raise in npm 6 | |
context "when there is a private git dep we don't have access to" do | |
let(:files) { project_dependency_files("npm7/github_dependency_private") } | |
let(:dependency_name) { "strict-uri-encode" } | |
let(:version) { "1.1.0" } | |
let(:requirements) { [] } | |
it "updates the dependency and leaves the private git dep alone" do | |
parsed_lockfile = JSON.parse(updated_npm_lock_content) | |
expect(parsed_lockfile.fetch("dependencies")["strict-uri-encode"]["version"]). | |
to eq("1.1.0") | |
expect(parsed_lockfile.fetch("dependencies")["bus-replacement-service"]["version"]). | |
to include("19c4dba3bfce7574e28f1df2138d47ab4cc665b3") | |
end | |
end | |
end |
"--ignore-scripts", | ||
"--package-lock-only" | ||
].join(" ") | ||
SharedHelpers.run_shell_command(command) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: command gets escaped by shellwords https://github.com/dependabot/dependabot-core/blob/main/common/lib/dependabot/shared_helpers.rb#L270
@@ -198,7 +309,7 @@ def handle_npm_updater_error(error, lockfile) | |||
# workspace project) | |||
sub_dep_local_path_error = "does not contain a package.json file" | |||
if error_message.match?(INVALID_PACKAGE) || | |||
error_message.start_with?("Invalid package name") || | |||
error_message.include?("Invalid package name") || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: npm 7 now exits with a full error log so we can't do starts_with? on anything meaningful
npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb
Outdated
Show resolved
Hide resolved
end | ||
end | ||
|
||
def run_npm_7_subdependency_updater(lockfile_name:) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mentioned while pairing: might want to extract a separate Npm7LockfileUpdater
in a follow-up PR.
# need to copy this from the manifest to the lockfile after the update | ||
# has finished. | ||
def restore_locked_package_dependencies(lockfile_name, lockfile_content) | ||
npm_version = Dependabot::NpmAndYarn::Helpers.npm_version(lockfile_content) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We check for the npm_version
6 times in this file, I know we talked about extracting an npm 7 updater, but maybe we can start with:
def npm7?
return @npm7 if defined?(@npm7)
@npm7 = Dependabot::NpmAndYarn::Helpers.npm_version(lockfile_content)
end
And using that everywhere? Might be a micro-optimization but I think it slightly clarifies the code and should be a pretty cheap change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this would be good but I think we need to refactor the lockfile updater a bit to get there as we currently pass around the lockfile name everywhere from here:
dependabot-core/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb
Line 26 in 720e663
def updated_lockfile_content(lockfile) |
Initialising the class with a single lockfile to be updated would make the class a lot simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracted it into a method but still passing around the content
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep iterating on the code, but functionally I feel pretty confident about this
Investigating python failures on main, seems unrelated to any recent changes |
Add support for updating npm 7 lockfiles alongside npm 6 based on the
lockfileVersion
set in thepackage-lock.json
file. If this is set to2
we used npm 7 otherwise fallback to npm 6.The native helpers to update dependencies shell out to
npm install pkg-name
for direct/top-level andnpm update pkg-name
for indirect/sub-dependencies.We made an initial attempt to use arborist programatically to do the update but abandoned this approach as we ran into a bug where arborist depends on pacote which re-runs what it thinks is the main npm cli command on failures to retry, which in our setup lead to an infinite loop of retries. The bug was reproduced here: https://github.com/feelepxyz/npm7-hang-investigation and discussed with the npm team who advised us to no use arborist programatically in this way (yet).
Review checklist/notable changes
starts_with?
on anything meaningfultop_level_dependency_update_not_required?
pkg-a
we go through and find any top-level git dependencies and lock them to the current resolved sha inpackage.json
and then re-set the lockfile after updatepackages."".*dependencies
entries inrestore_locked_package_dependencies
which should mirror what's in yourpackage.json
, we need to reset this after update otherwise we'll have both locked git deps and updated dependencies new requirements in there even though we didn't update thepackage.json
eslint: ^1.0.0
but we want to update to1.1.1
without updating the package.json we runnpm install eslint@1.1.0
which updates thepackages."".*dependencies
entry to1.1.0
but we want it to match thepackage.json
so reset it to^1.0.0
after updategit_dependencies_to_lock
git dependenciesfrom
syntax in lockfiles has changed to include the dependency name and also always seem to default tossh://
protocol when not explicitly fetching fromhttps://github..
for examplenpm i --package-lock-only
Release plan
"\"lockfileVersion\": 2"
with quotes?