diff --git a/bun/lib/dependabot/bun/metadata_finder.rb b/bun/lib/dependabot/bun/metadata_finder.rb index 497f4053395..72b12bbd7d2 100644 --- a/bun/lib/dependabot/bun/metadata_finder.rb +++ b/bun/lib/dependabot/bun/metadata_finder.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "excon" +require "cgi" require "sorbet-runtime" require "time" @@ -16,6 +17,13 @@ module Bun class MetadataFinder < Dependabot::MetadataFinders::Base extend T::Sig + # RFC 3986 unreserved characters safe in URI paths: A-Z, a-z, 0-9, ., _, -, ~ + # Use an explicit ASCII character class because Ruby's \w is encoding-aware + # and can match non-ASCII word characters under UTF-8. + # Characters outside this set require percent-encoding in npm releaser profile URLs. + CHARS_REQUIRING_ENCODING = T.let(/[^A-Za-z0-9._~-]/, Regexp) + private_constant :CHARS_REQUIRING_ENCODING + sig { override.returns(T.nilable(String)) } def homepage_url # Attempt to use version_listing first, as fetching the entire listing @@ -32,13 +40,27 @@ def maintainer_changes return unless npm_listing.dig("time", dependency.version) return if previous_releasers&.include?(npm_releaser) + # Safe: npm_releaser is non-nil after the guard clause above + encoded_releaser = encode_npm_releaser(T.must(npm_releaser)) "This version was pushed to npm by " \ - "[#{npm_releaser}](https://www.npmjs.com/~#{npm_releaser}), a new " \ + "[#{npm_releaser}](https://www.npmjs.com/~#{encoded_releaser}), a new " \ "releaser for #{dependency.name} since your current version." end private + # Encodes npm releaser names for safe inclusion in npmjs.com profile URLs. + # Optimization: Returns unmodified if all characters are RFC 3986 unreserved. + # Names with special characters (spaces, @, +, etc.) are percent-encoded. + sig { params(releaser: String).returns(String) } + def encode_npm_releaser(releaser) + # Early return for common case: most npm usernames contain only safe characters + return releaser unless releaser.match?(CHARS_REQUIRING_ENCODING) + + # CGI.escape uses + for spaces; convert to %20 for proper URL encoding + CGI.escape(releaser).gsub("+", "%20") + end + sig { override.returns(T.nilable(Dependabot::Source)) } def look_up_source return find_source_from_registry if new_source.nil? @@ -216,7 +238,13 @@ def dependency_url new_source&.fetch(:url) end - # Remove trailing slashes and escape spaces for proper URL formatting + # TODO: Remove URI::DEFAULT_PARSER.escape in favor of explicit space encoding (like npm_and_yarn). + # Currently, normalize_registry_url safely handles spaces for configured registries (new_source.nil?), + # but URI::DEFAULT_PARSER.escape remains here for the new_source case. This should be addressed in a + # separate concern when standardizing URL handling across all ecosystems. + # NOTE: URI::DEFAULT_PARSER.escape is deprecated and should be replaced with URI.encode_uri_component + # or a similar approach. + # URI::DEFAULT_PARSER.escape encodes many characters; then remove trailing slashes registry_url = URI::DEFAULT_PARSER.escape(registry_url)&.gsub(%r{/+$}, "") # NPM registries expect slashes to be escaped @@ -236,6 +264,8 @@ def configured_registry_from_credentials sig { params(registry: T.nilable(String)).returns(T.nilable(String)) } def normalize_registry_url(registry) return nil unless registry + + registry = registry.strip.gsub(/\s+/, "%20") return registry if registry.start_with?("http") "https://#{registry}" diff --git a/bun/spec/dependabot/bun/metadata_finder_spec.rb b/bun/spec/dependabot/bun/metadata_finder_spec.rb index 8cbef976b78..dcb7ab461bb 100644 --- a/bun/spec/dependabot/bun/metadata_finder_spec.rb +++ b/bun/spec/dependabot/bun/metadata_finder_spec.rb @@ -30,7 +30,7 @@ requirements: [ { file: "package.json", requirement: "^1.0", groups: [], source: nil } ], - package_manager: "npm_and_yarn" + package_manager: "bun" ) end @@ -72,7 +72,7 @@ ref: "master" } }], - package_manager: "npm_and_yarn" + package_manager: "bun" ) end @@ -298,7 +298,7 @@ url: "https://npm.fury.io/dependabot" } }], - package_manager: "npm_and_yarn" + package_manager: "bun" ) end @@ -395,7 +395,7 @@ } } ], - package_manager: "npm_and_yarn" + package_manager: "bun" ) end @@ -437,7 +437,7 @@ } } ], - package_manager: "npm_and_yarn" + package_manager: "bun" ) end @@ -513,7 +513,7 @@ groups: [], source: nil }], - package_manager: "npm_and_yarn" + package_manager: "bun" ) end @@ -525,6 +525,36 @@ ) end end + + context "when the maintainer name contains spaces" do + let(:dependency_name) { "npm-package-json-lint" } + let(:npm_url) { "https://registry.npmjs.org/npm-package-json-lint" } + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: "10.0.0", + previous_version: "9.0.0", + requirements: [{ + file: "package.json", + requirement: "^10.0", + groups: [], + source: nil + }], + package_manager: "bun" + ) + end + let(:npm_all_versions_response) do + fixture("npm_responses", "npm-package-json-lint.json") + end + + it "properly URL-encodes the maintainer name in the link" do + expect(maintainer_changes).to eq( + "This version was pushed to npm by " \ + "[GitHub Actions](https://www.npmjs.com/~GitHub%20Actions), a new releaser " \ + "for npm-package-json-lint since your current version." + ) + end + end end describe "#dependency_url" do @@ -538,7 +568,7 @@ requirements: [ { file: "package.json", requirement: "^1.0", groups: [], source: nil } ], - package_manager: "npm_and_yarn" + package_manager: "bun" ) end @@ -646,7 +676,7 @@ source: { type: "registry", url: "https://npm.fury.io/dependabot" } } ], - package_manager: "npm_and_yarn" + package_manager: "bun" ) end @@ -666,4 +696,156 @@ end end end + + describe "#encode_npm_releaser (private helper)" do + subject(:encoded) { finder.send(:encode_npm_releaser, releaser_name) } + + context "with safe characters only (no encoding needed)" do + let(:releaser_name) { "dougwilson" } + + it "returns the name unmodified" do + expect(encoded).to eq("dougwilson") + end + end + + context "with safe characters including dot, underscore, dash" do + let(:releaser_name) { "user.name_test-pkg" } + + it "returns the name unmodified" do + expect(encoded).to eq("user.name_test-pkg") + end + end + + context "with space character" do + let(:releaser_name) { "GitHub Actions" } + + it "encodes space as %20" do + expect(encoded).to eq("GitHub%20Actions") + end + end + + context "with @ symbol (common in email-like usernames)" do + let(:releaser_name) { "user@domain" } + + it "encodes @ as %40" do + expect(encoded).to eq("user%40domain") + end + end + + context "with + symbol" do + let(:releaser_name) { "user+admin" } + + it "encodes + as %2B" do + expect(encoded).to eq("user%2Badmin") + end + end + + context "with / symbol" do + let(:releaser_name) { "scope/user" } + + it "encodes / as %2F" do + expect(encoded).to eq("scope%2Fuser") + end + end + + context "with mixed special characters" do + let(:releaser_name) { "user@host+admin" } + + it "encodes all unsafe characters" do + expect(encoded).to eq("user%40host%2Badmin") + end + end + + context "with empty string" do + let(:releaser_name) { "" } + + it "returns empty string unchanged" do + expect(encoded).to eq("") + end + end + + context "with numeric-only name" do + let(:releaser_name) { "12345" } + + it "returns unchanged (digits are safe)" do + expect(encoded).to eq("12345") + end + end + end + + describe "#normalize_registry_url (private helper)" do + subject(:normalized) { finder.send(:normalize_registry_url, registry_url) } + + context "with nil input" do + let(:registry_url) { nil } + + it "returns nil" do + expect(normalized).to be_nil + end + end + + context "with registry URL without protocol" do + let(:registry_url) { "my.registry.com" } + + it "adds https:// prefix" do + expect(normalized).to eq("https://my.registry.com") + end + end + + context "with registry URL with https protocol" do + let(:registry_url) { "https://my.registry.com" } + + it "returns unchanged" do + expect(normalized).to eq("https://my.registry.com") + end + end + + context "with registry URL with http protocol" do + let(:registry_url) { "http://my.registry.com" } + + it "returns unchanged" do + expect(normalized).to eq("http://my.registry.com") + end + end + + context "with registry URL containing spaces" do + let(:registry_url) { "https://my registry.com" } + + it "encodes spaces as %20" do + expect(normalized).to eq("https://my%20registry.com") + end + end + + context "with registry URL containing multiple spaces" do + let(:registry_url) { "https://my registry com" } + + it "encodes consecutive spaces as single %20" do + expect(normalized).to eq("https://my%20registry%20com") + end + end + + context "with registry URL with leading/trailing whitespace" do + let(:registry_url) { " https://my.registry.com " } + + it "strips whitespace and returns URL unchanged" do + expect(normalized).to eq("https://my.registry.com") + end + end + + context "with registry URL with mixed whitespace (spaces and tabs)" do + let(:registry_url) { "https://my\t registry.com" } + + it "encodes consecutive whitespace as single %20" do + expect(normalized).to eq("https://my%20registry.com") + end + end + + context "with registry URL with nested spaces and no protocol" do + let(:registry_url) { "my registry.com" } + + it "encodes spaces and adds https://" do + expect(normalized).to eq("https://my%20registry.com") + end + end + end end diff --git a/bun/spec/fixtures/npm_responses/npm-package-json-lint.json b/bun/spec/fixtures/npm_responses/npm-package-json-lint.json new file mode 100644 index 00000000000..669e98913a2 --- /dev/null +++ b/bun/spec/fixtures/npm_responses/npm-package-json-lint.json @@ -0,0 +1,116 @@ +{ + "_attachments": {}, + "_id": "npm-package-json-lint", + "_rev": "142-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "bugs": { + "url": "https://github.com/ericcornelissen/npm-package-json-lint/issues" + }, + "contributors": [ + { + "email": "eric@example.com", + "name": "Eric Cornelissen" + } + ], + "description": "Configurable npm package.json linter", + "dist-tags": { + "latest": "10.0.0" + }, + "homepage": "https://github.com/ericcornelissen/npm-package-json-lint", + "keywords": [ + "lint", + "npm", + "package.json" + ], + "license": "MIT", + "maintainers": [ + { + "email": "github-actions@github.com", + "name": "GitHub Actions" + } + ], + "name": "npm-package-json-lint", + "readme": "# npm-package-json-lint\n\nA utility for linting npm package.json files.\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "https://github.com/ericcornelissen/npm-package-json-lint" + }, + "time": { + "created": "2020-01-01T00:00:00.000Z", + "modified": "2024-01-02T00:00:00.000Z", + "9.0.0": "2024-01-01T00:00:00.000Z", + "10.0.0": "2024-01-02T00:00:00.000Z" + }, + "versions": { + "9.0.0": { + "_from": ".", + "_id": "npm-package-json-lint@9.0.0", + "_npmUser": { + "email": "eric@example.com", + "name": "oldmaintainer" + }, + "_npmVersion": "11.10.0", + "_shasum": "8f14e45fceea167a5a36dedd4bea2543fba3442a", + "bugs": { + "url": "https://github.com/ericcornelissen/npm-package-json-lint/issues" + }, + "description": "Configurable npm package.json linter", + "dist": { + "shasum": "8f14e45fceea167a5a36dedd4bea2543fba3442a", + "tarball": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-9.0.0.tgz" + }, + "engines": { + "node": ">=14.0.0" + }, + "homepage": "https://github.com/ericcornelissen/npm-package-json-lint", + "keywords": [ + "lint", + "npm", + "package.json" + ], + "license": "MIT", + "main": "dist/index.js", + "name": "npm-package-json-lint", + "repository": { + "type": "git", + "url": "https://github.com/ericcornelissen/npm-package-json-lint" + }, + "version": "9.0.0" + }, + "10.0.0": { + "_from": ".", + "_id": "npm-package-json-lint@10.0.0", + "_npmUser": { + "email": "github-actions@github.com", + "name": "GitHub Actions" + }, + "_npmVersion": "11.11.0", + "_shasum": "df3c6c4ce8db6c0f5f5b92b0c1c1c0c0c0c0c0c0", + "bugs": { + "url": "https://github.com/ericcornelissen/npm-package-json-lint/issues" + }, + "description": "Configurable npm package.json linter", + "dist": { + "shasum": "df3c6c4ce8db6c0f5f5b92b0c1c1c0c0c0c0c0c0", + "tarball": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-10.0.0.tgz" + }, + "engines": { + "node": ">=14.0.0" + }, + "homepage": "https://github.com/ericcornelissen/npm-package-json-lint", + "keywords": [ + "lint", + "npm", + "package.json" + ], + "license": "MIT", + "main": "dist/index.js", + "name": "npm-package-json-lint", + "repository": { + "type": "git", + "url": "https://github.com/ericcornelissen/npm-package-json-lint" + }, + "version": "10.0.0" + } + } +} diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/metadata_finder.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/metadata_finder.rb index fb5cecfde24..e83818c9901 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/metadata_finder.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/metadata_finder.rb @@ -3,6 +3,7 @@ require "excon" require "time" +require "uri" require "sorbet-runtime" require "dependabot/metadata_finders" @@ -24,6 +25,13 @@ class MetadataFinder < Dependabot::MetadataFinders::Base T::Array[String] ) + # RFC 3986 unreserved characters safe in URI paths: A-Z, a-z, 0-9, ., _, -, ~ + # Use an explicit ASCII character class because Ruby's \w is encoding-aware + # and can match non-ASCII word characters under UTF-8. + # Characters outside this set require percent-encoding in npm releaser profile URLs. + CHARS_REQUIRING_ENCODING = T.let(/[^A-Za-z0-9._~-]/, Regexp) + private_constant :CHARS_REQUIRING_ENCODING + sig { override.returns(T.nilable(String)) } def homepage_url # Attempt to use version_listing first, as fetching the entire listing @@ -40,8 +48,9 @@ def maintainer_changes return unless npm_listing.dig("time", dependency.version) return if previous_releasers&.include?(npm_releaser) + encoded_releaser = encode_npm_releaser(T.must(npm_releaser)) "This version was pushed to npm by " \ - "[#{npm_releaser}](https://www.npmjs.com/~#{npm_releaser}), a new " \ + "[#{npm_releaser}](https://www.npmjs.com/~#{encoded_releaser}), a new " \ "releaser for #{dependency.name} since your current version." end @@ -97,6 +106,26 @@ def format_script_list(action, scripts) "#{action} #{script_names} #{noun}" end + # Encodes npm releaser names for safe inclusion in npmjs.com profile URLs. + # Optimization: Returns unmodified if all characters are RFC 3986 unreserved. + # Names with special characters (spaces, @, +, etc.) are percent-encoded. + sig { params(releaser: String).returns(String) } + def encode_npm_releaser(releaser) + # Early return for common case: most npm usernames contain only safe characters + return releaser unless releaser.match?(CHARS_REQUIRING_ENCODING) + + # RFC 3986 percent-encoding: unreserved characters stay, rest become %XX + # Note: We manually encode to use %20 for spaces (not +) as required for URL paths + releaser.bytes.map do |byte| + char = byte.chr + if char.match?(/[A-Za-z0-9._~\-]/) + char + else + format("%02X", byte).prepend("%") + end + end.join + end + sig { params(version: T.nilable(String)).returns(T::Hash[String, String]) } def install_scripts_for_version(version) return {} unless version @@ -301,8 +330,11 @@ def dependency_url new_source&.fetch(:url) end - # Remove trailing slashes and escape spaces for proper URL formatting - registry_url = URI::DEFAULT_PARSER.escape(registry_url)&.gsub(%r{/+$}, "") + # Encode spaces in registry URL for proper HTTP requests + # (configured_registry_from_credentials already encodes spaces via normalize_registry_url) + registry_url = registry_url&.gsub(" ", "%20") + # Remove trailing slashes to avoid double slashes when appending dependency name + registry_url = registry_url&.gsub(%r{/+$}, "") # NPM registries expect slashes to be escaped escaped_dependency_name = dependency.name.gsub("/", "%2F") @@ -328,6 +360,12 @@ def configured_registry_from_credentials sig { params(registry: T.nilable(String)).returns(T.nilable(String)) } def normalize_registry_url(registry) return nil unless registry + + # Encode whitespace characters (spaces, tabs, newlines) as %20. + # Note: This only handles spaces; other URL-unsafe characters (e.g., #, ?) are not encoded. + # However, this matches RegistryFinder's behavior exactly, so it's consistent and acceptable. + # If broader URL encoding is needed, that would be a separate concern for the entire codebase. + registry = registry.strip.gsub(/\s+/, "%20") return registry if registry.start_with?("http") "https://#{registry}" diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/metadata_finder_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/metadata_finder_spec.rb index 3a98970945c..d238b63fd19 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/metadata_finder_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/metadata_finder_spec.rb @@ -526,6 +526,112 @@ ) end end + + context "when the maintainer name contains spaces" do + let(:dependency_name) { "npm-package-json-lint" } + let(:npm_url) { "https://registry.npmjs.org/npm-package-json-lint" } + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: "10.0.0", + previous_version: "9.0.0", + requirements: [{ + file: "package.json", + requirement: "^10.0", + groups: [], + source: nil + }], + package_manager: "npm_and_yarn" + ) + end + let(:npm_all_versions_response) do + fixture("npm_responses", "npm-package-json-lint.json") + end + + it "properly URL-encodes the maintainer name in the link" do + expect(maintainer_changes).to eq( + "This version was pushed to npm by " \ + "[GitHub Actions](https://www.npmjs.com/~GitHub%20Actions), a new releaser " \ + "for npm-package-json-lint since your current version." + ) + end + end + end + + describe "#encode_npm_releaser (private helper)" do + subject(:encoded) { finder.send(:encode_npm_releaser, releaser_name) } + + context "with safe characters only (no encoding needed)" do + let(:releaser_name) { "dougwilson" } + + it "returns the name unmodified" do + expect(encoded).to eq("dougwilson") + end + end + + context "with safe characters including dot, underscore, dash" do + let(:releaser_name) { "user.name_test-pkg" } + + it "returns the name unmodified" do + expect(encoded).to eq("user.name_test-pkg") + end + end + + context "with space character" do + let(:releaser_name) { "GitHub Actions" } + + it "encodes space as %20" do + expect(encoded).to eq("GitHub%20Actions") + end + end + + context "with @ symbol (common in email-like usernames)" do + let(:releaser_name) { "user@domain" } + + it "encodes @ as %40" do + expect(encoded).to eq("user%40domain") + end + end + + context "with + symbol" do + let(:releaser_name) { "user+admin" } + + it "encodes + as %2B" do + expect(encoded).to eq("user%2Badmin") + end + end + + context "with / symbol" do + let(:releaser_name) { "scope/user" } + + it "encodes / as %2F" do + expect(encoded).to eq("scope%2Fuser") + end + end + + context "with mixed special characters" do + let(:releaser_name) { "user@host+admin" } + + it "encodes all unsafe characters" do + expect(encoded).to eq("user%40host%2Badmin") + end + end + + context "with empty string" do + let(:releaser_name) { "" } + + it "returns empty string unchanged" do + expect(encoded).to eq("") + end + end + + context "with numeric-only name" do + let(:releaser_name) { "12345" } + + it "returns unchanged (digits are safe)" do + expect(encoded).to eq("12345") + end + end end describe "#install_script_changes" do @@ -858,6 +964,22 @@ end end + context "with registry URL containing spaces" do + let(:credentials) do + [Dependabot::Credential.new( + { + "type" => "npm_registry", + "registry" => "http://example.com/registry with spaces", + "replaces-base" => true + } + )] + end + + it "percent-encodes spaces in the registry URL" do + expect(dependency_url).to eq("http://example.com/registry%20with%20spaces/etag") + end + end + context "with multiple credentials" do let(:credentials) do [ diff --git a/npm_and_yarn/spec/fixtures/npm_responses/npm-package-json-lint.json b/npm_and_yarn/spec/fixtures/npm_responses/npm-package-json-lint.json new file mode 100644 index 00000000000..9a651161d46 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/npm_responses/npm-package-json-lint.json @@ -0,0 +1,116 @@ +{ + "_attachments": {}, + "_id": "npm-package-json-lint", + "_rev": "142-abc1234567890", + "bugs": { + "url": "https://github.com/ericcornelissen/npm-package-json-lint/issues" + }, + "contributors": [ + { + "email": "eric@example.com", + "name": "Eric Cornelissen" + } + ], + "description": "Configurable npm package.json linter", + "dist-tags": { + "latest": "10.0.0" + }, + "homepage": "https://github.com/ericcornelissen/npm-package-json-lint", + "keywords": [ + "lint", + "npm", + "package.json" + ], + "license": "MIT", + "maintainers": [ + { + "email": "github-actions@github.com", + "name": "GitHub Actions" + } + ], + "name": "npm-package-json-lint", + "readme": "# npm-package-json-lint\n\nA utility for linting npm package.json files.\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "https://github.com/ericcornelissen/npm-package-json-lint" + }, + "time": { + "created": "2020-01-01T00:00:00.000Z", + "modified": "2024-01-02T00:00:00.000Z", + "9.0.0": "2024-01-01T00:00:00.000Z", + "10.0.0": "2024-01-02T00:00:00.000Z" + }, + "versions": { + "9.0.0": { + "_from": ".", + "_id": "npm-package-json-lint@9.0.0", + "_npmUser": { + "email": "eric@example.com", + "name": "oldmaintainer" + }, + "_npmVersion": "11.10.0", + "_shasum": "8f14e45fceea167a5a36dedd4bea2543fba3442a", + "bugs": { + "url": "https://github.com/ericcornelissen/npm-package-json-lint/issues" + }, + "description": "Configurable npm package.json linter", + "dist": { + "shasum": "8f14e45fceea167a5a36dedd4bea2543fba3442a", + "tarball": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-9.0.0.tgz" + }, + "engines": { + "node": ">=14.0.0" + }, + "homepage": "https://github.com/ericcornelissen/npm-package-json-lint", + "keywords": [ + "lint", + "npm", + "package.json" + ], + "license": "MIT", + "main": "dist/index.js", + "name": "npm-package-json-lint", + "repository": { + "type": "git", + "url": "https://github.com/ericcornelissen/npm-package-json-lint" + }, + "version": "9.0.0" + }, + "10.0.0": { + "_from": ".", + "_id": "npm-package-json-lint@10.0.0", + "_npmUser": { + "email": "github-actions@github.com", + "name": "GitHub Actions" + }, + "_npmVersion": "11.11.0", + "_shasum": "df3c6c4ce8db6c0f5f5b92b0c1c1c0c0c0c0c0c0", + "bugs": { + "url": "https://github.com/ericcornelissen/npm-package-json-lint/issues" + }, + "description": "Configurable npm package.json linter", + "dist": { + "shasum": "df3c6c4ce8db6c0f5f5b92b0c1c1c0c0c0c0c0c0", + "tarball": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-10.0.0.tgz" + }, + "engines": { + "node": ">=14.0.0" + }, + "homepage": "https://github.com/ericcornelissen/npm-package-json-lint", + "keywords": [ + "lint", + "npm", + "package.json" + ], + "license": "MIT", + "main": "dist/index.js", + "name": "npm-package-json-lint", + "repository": { + "type": "git", + "url": "https://github.com/ericcornelissen/npm-package-json-lint" + }, + "version": "10.0.0" + } + } +}