lib/licensed/sources/npm.rb in licensed-2.14.4 vs lib/licensed/sources/npm.rb in licensed-2.15.0

- old
+ new

@@ -2,10 +2,29 @@ require "json" module Licensed module Sources class NPM < Source + class Dependency < ::Licensed::Dependency + # override license_metadata to pull homepage and summary information + # from a packages package.json file, if it exists + # this accounts for the lack of this information in npm 7's `npm list` output + def license_metadata + data = super + return data if !data["homepage"].to_s.empty? && !data["summary"].to_s.empty? + + package_json_path = File.join(path, "package.json") + return data unless File.exist?(package_json_path) + + package_json = JSON.parse(File.read(package_json_path)) + data["homepage"] = package_json["homepage"] + data["summary"] = package_json["description"] + + data + end + end + def self.type "npm" end def enabled? @@ -48,32 +67,61 @@ # package name to it's metadata def recursive_dependencies(dependencies, result = {}) dependencies.each do |name, dependency| next if dependency["peerMissing"] next if yarn_lock_present && dependency["missing"] + dependency["name"] = name (result[name] ||= []) << dependency recursive_dependencies(dependency["dependencies"] || {}, result) end result end # Returns parsed package metadata returned from `npm list` def package_metadata return @package_metadata if defined?(@package_metadata) + @package_metadata = JSON.parse(package_metadata_command) + rescue JSON::ParserError => e + message = "Licensed was unable to parse the output from 'npm list'. JSON Error: #{e.message}" + npm_error = package_metadata_error + message = "#{message}. npm Error: #{npm_error}" if npm_error + raise Licensed::Sources::Source::Error, message + end - @package_metadata = begin - JSON.parse(package_metadata_command) - rescue JSON::ParserError => e - raise Licensed::Sources::Source::Error, - "Licensed was unable to parse the output from 'npm list'. Please run 'npm list --json --long' and check for errors. Error: #{e.message}" - end + # Returns an error, if one exists, from running `npm list` to get package metadata + def package_metadata_error + Licensed::Shell.execute("npm", "list", *package_metadata_args) + return "" + rescue Licensed::Shell::Error => e + return e.message end # Returns the output from running `npm list` to get package metadata def package_metadata_command args = %w(--json --long) - args << "--production" unless include_non_production? + args.concat(package_metadata_args) + Licensed::Shell.execute("npm", "list", *args, allow_failure: true) + end + + # Returns an array of arguments that should be used for all `npm list` + # calls, regardless of how the output is formatted + def package_metadata_args + args = [] + args << "--production" unless include_non_production? + + # on npm 7+, the --all argument is necessary to evaluate the project's + # full dependency tree + args << "--all" if npm_version >= Gem::Version.new("7.0.0") + + return args + end + + # Returns the currently installed version of npm as a Gem::Version object + def npm_version + @npm_version ||= begin + Gem::Version.new(Licensed::Shell.execute("npm", "-v").strip) + end end # Returns true if a yarn.lock file exists in the current directory def yarn_lock_present @yarn_lock_present ||= File.exist?(config.pwd.join("yarn.lock"))