Class: Bibliothecary::Parsers::NPM
- Inherits:
-
Object
- Object
- Bibliothecary::Parsers::NPM
- Includes:
- Analyser
- Defined in:
- lib/bibliothecary/parsers/npm.rb
Constant Summary collapse
- PACKAGE_LOCK_JSON_MAX_DEPTH =
Max depth to recurse into the “dependencies” property of package-lock.json
10
Class Method Summary collapse
- .lockfile_preference_order(file_infos) ⇒ Object
- .mapping ⇒ Object
-
.parse_ls(file_contents, options: {}) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument.
-
.parse_manifest(file_contents, options: {}) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument.
-
.parse_package_lock(file_contents, options: {}) ⇒ Object
(also: parse_shrinkwrap)
rubocop:disable Lint/UnusedMethodArgument.
- .parse_package_lock_deps_recursively(dependencies, depth = 1) ⇒ Object
- .parse_package_lock_v1(manifest) ⇒ Object
- .parse_package_lock_v2(manifest) ⇒ Object
-
.parse_yarn_lock(file_contents, options: {}) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument.
Methods included from Analyser
create_analysis, create_error_analysis, included
Class Method Details
.lockfile_preference_order(file_infos) ⇒ Object
157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/bibliothecary/parsers/npm.rb', line 157 def self.lockfile_preference_order(file_infos) files = file_infos.each_with_object({}) do |file_info, obj| obj[File.basename(file_info.full_path)] = file_info end if files["npm-shrinkwrap.json"] [files["npm-shrinkwrap.json"]] + files.values.reject { |fi| File.basename(fi.full_path) == "npm-shrinkwrap.json" } else files.values end end |
.mapping ⇒ Object
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/bibliothecary/parsers/npm.rb', line 11 def self.mapping { match_filename("package.json") => { kind: "manifest", parser: :parse_manifest, }, match_filename("npm-shrinkwrap.json") => { kind: "lockfile", parser: :parse_shrinkwrap, }, match_filename("yarn.lock") => { kind: "lockfile", parser: :parse_yarn_lock, }, match_filename("package-lock.json") => { kind: "lockfile", parser: :parse_package_lock, }, match_filename("npm-ls.json") => { kind: "lockfile", parser: :parse_ls, }, } end |
.parse_ls(file_contents, options: {}) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument
151 152 153 154 155 |
# File 'lib/bibliothecary/parsers/npm.rb', line 151 def self.parse_ls(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument manifest = JSON.parse(file_contents) transform_tree_to_array(manifest.fetch("dependencies", {})) end |
.parse_manifest(file_contents, options: {}) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/bibliothecary/parsers/npm.rb', line 101 def self.parse_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument manifest = JSON.parse(file_contents) raise "appears to be a lockfile rather than manifest format" if manifest.key?("lockfileVersion") dependencies = manifest.fetch("dependencies", []) .reject { |name, _requirement| name.start_with?("//") } # Omit comment keys. They are valid in package.json: https://groups.google.com/g/nodejs/c/NmL7jdeuw0M/m/yTqI05DRQrIJ .map do |name, requirement| Dependency.new( name: name, requirement: requirement, type: "runtime", local: requirement.start_with?("file:") ) end dependencies += manifest.fetch("devDependencies", []) .reject { |name, _requirement| name.start_with?("//") } # Omit comment keys. They are valid in package.json: https://groups.google.com/g/nodejs/c/NmL7jdeuw0M/m/yTqI05DRQrIJ .map do |name, requirement| Dependency.new( name: name, requirement: requirement, type: "development", local: requirement.start_with?("file:") ) end dependencies end |
.parse_package_lock(file_contents, options: {}) ⇒ Object Also known as: parse_shrinkwrap
rubocop:disable Lint/UnusedMethodArgument
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/bibliothecary/parsers/npm.rb', line 40 def self.parse_package_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument manifest = JSON.parse(file_contents) # https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#lockfileversion if manifest["lockfileVersion"].to_i <= 1 # lockfileVersion 1 uses the "dependencies" object parse_package_lock_v1(manifest) else # lockfileVersion 2 has backwards-compatability by including both "packages" and the legacy "dependencies" object # lockfileVersion 3 has no backwards-compatibility and only includes the "packages" object parse_package_lock_v2(manifest) end end |
.parse_package_lock_deps_recursively(dependencies, depth = 1) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/bibliothecary/parsers/npm.rb', line 82 def self.parse_package_lock_deps_recursively(dependencies, depth=1) dependencies.flat_map do |name, requirement| type = requirement.fetch("dev", false) ? "development" : "runtime" version = requirement.key?("from") ? requirement["from"][/#(?:semver:)?v?(.*)/, 1] : nil version ||= requirement["version"].split("#").last child_dependencies = if depth >= PACKAGE_LOCK_JSON_MAX_DEPTH [] else parse_package_lock_deps_recursively(requirement.fetch("dependencies", []), depth + 1) end [Dependency.new( name: name, requirement: version, type: type, )] + child_dependencies end end |
.parse_package_lock_v1(manifest) ⇒ Object
58 59 60 |
# File 'lib/bibliothecary/parsers/npm.rb', line 58 def self.parse_package_lock_v1(manifest) parse_package_lock_deps_recursively(manifest.fetch("dependencies", [])) end |
.parse_package_lock_v2(manifest) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/bibliothecary/parsers/npm.rb', line 62 def self.parse_package_lock_v2(manifest) # "packages" is a flat object where each key is the installed location of the dep, e.g. node_modules/foo/node_modules/bar. manifest .fetch("packages") # there are a couple of scenarios where a package's name won't start with node_modules # 1. name == "", this is the lockfile's package itself # 2. when a package is a local path dependency, it will appear in package-lock.json twice. # * One occurrence has the node_modules/ prefix in the name (which we keep) # * The other occurrence's name is the path to the local dependency (which has less information, and is duplicative, so we discard) .select { |name, _dep| name.start_with?("node_modules") } .map do |name, dep| Dependency.new( name: name.split("node_modules/").last, requirement: dep["version"], type: dep.fetch("dev", false) || dep.fetch("devOptional", false) ? "development" : "runtime", local: dep.fetch("link", false), ) end end |
.parse_yarn_lock(file_contents, options: {}) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/bibliothecary/parsers/npm.rb', line 130 def self.parse_yarn_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument response = Typhoeus.post("#{Bibliothecary.configuration.yarn_parser_host}/parse", body: file_contents) raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.yarn_parser_host}/parse", response.response_code) unless response.success? json = JSON.parse(response.body, symbolize_names: true) json .uniq .reject do |dep| dep[:requirement]&.include?("workspace") && dep[:version].include?("use.local") end .map do |dep| Dependency.new( name: dep[:name], requirement: dep[:version], type: dep[:type], local: dep[:requirement]&.start_with?("file:"), ) end end |