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
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.
| # 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.
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.
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.
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.
| end | ||
| end | ||
|
|
||
| context "when a git src dependency doesn't have a valid package.json" do |
There was a problem hiding this comment.
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:
| expect(parsed_lockfile["dependencies"]["fetch-factory"]["version"]). | ||
| to eq("0.0.2") | ||
|
|
||
| context "with a corrupted npm lockfile (version missing)" do |
There was a problem hiding this comment.
No longer failing in npm 7, npm 6 spec is here
| end | ||
|
|
||
| # 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.
No longer fails for npm 7, tests have moved here:
| "--ignore-scripts", | ||
| "--package-lock-only" | ||
| ].join(" ") | ||
| SharedHelpers.run_shell_command(command) |
There was a problem hiding this comment.
Note: command gets escaped by shellwords https://github.com/dependabot/dependabot-core/blob/main/common/lib/dependabot/shared_helpers.rb#L270
| 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.
Note: npm 7 now exits with a full error log so we can't do starts_with? on anything meaningful
| end | ||
| end | ||
|
|
||
| def run_npm_7_subdependency_updater(lockfile_name:) |
There was a problem hiding this comment.
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.
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)
endAnd 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.
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:
Initialising the class with a single lockfile to be updated would make the class a lot simpler.
There was a problem hiding this comment.
Extracted it into a method but still passing around the content
jurre
left a comment
There was a problem hiding this comment.
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
lockfileVersionset in thepackage-lock.jsonfile. If this is set to2we used npm 7 otherwise fallback to npm 6.The native helpers to update dependencies shell out to
npm install pkg-namefor direct/top-level andnpm update pkg-namefor 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-awe go through and find any top-level git dependencies and lock them to the current resolved sha inpackage.jsonand then re-set the lockfile after updatepackages."".*dependenciesentries inrestore_locked_package_dependencieswhich 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.jsoneslint: ^1.0.0but we want to update to1.1.1without updating the package.json we runnpm install eslint@1.1.0which updates thepackages."".*dependenciesentry to1.1.0but we want it to match thepackage.jsonso reset it to^1.0.0after updategit_dependencies_to_lockgit dependenciesfromsyntax 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-onlyRelease plan
"\"lockfileVersion\": 2"with quotes?