Skip to content

Commit

Permalink
Support NPM 7 and up and include NodeJS 17 in the Docker image (NPM 8)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhuitl committed May 1, 2023
1 parent 6a6ce91 commit 420c544
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 27 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ RUN apt -q update && apt install -y \
RUN add-apt-repository ppa:git-core/ppa && \
apt -q update && apt install -y git && rm -rf /var/lib/apt/lists/*

# nodejs seems to be required for the one of the gems
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
# install nodejs
RUN curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
apt -q update && apt install -y nodejs && rm -rf /var/lib/apt/lists/*

# install yarn
Expand Down
1 change: 1 addition & 0 deletions features/features/package_managers/npm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
LicenseFinder::TestingDSL::NpmProject.create
node_developer.run_license_finder
expect(node_developer).to be_seeing_line 'http-server, 0.11.1, MIT'
expect(node_developer).to be_seeing_line 'entities, 4.4.0, "Simplified BSD"'
end
end
2 changes: 1 addition & 1 deletion features/support/testing_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def install

class NpmProject < Project
def add_dep
add_to_file('package.json', '{"dependencies" : {"http-server": "0.11.1"}}')
add_to_file('package.json', '{"dependencies" : {"http-server": "0.11.1", "entities": "4.4.0"}}')
end

def install
Expand Down
15 changes: 14 additions & 1 deletion lib/license_finder/package_managers/npm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def prepare
private

def npm_json
command = "#{package_management_command} list --json --long#{production_flag}"
command = "#{package_management_command} list --json --long#{all_flag}#{production_flag}"
command += " #{@npm_options}" unless @npm_options.nil?
stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
# we can try and continue if we got an exit status 1 - unmet peer dependency
Expand All @@ -53,5 +53,18 @@ def production_flag

@ignored_groups.include?('devDependencies') ? ' --production' : ''
end

def all_flag
npm_version >= 7 ? ' --all' : ''
end

def npm_version
command = "#{package_management_command} -v"
stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?

version = stdout.split('.').map(&:to_i)
version[0]
end
end
end
36 changes: 27 additions & 9 deletions lib/license_finder/packages/npm_package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ def packages_from_json(npm_json, package_path)
def flattened_dependencies(npm_json, existing_packages = {})
identifier = Identifier.from_hash npm_json
if existing_packages[identifier].nil?
existing_packages[identifier] = NpmPackage.new(npm_json) if identifier
existing_packages[identifier] = package_for_dependency(npm_json) if identifier
npm_json.fetch('dependencies', {}).values.map do |d|
flattened_dependencies(d, existing_packages)
end
else
duplicate_package = NpmPackage.new(npm_json)
duplicate_package = package_for_dependency(npm_json)
unless existing_packages[identifier].dependencies.include?(duplicate_package.dependencies)
existing_packages[identifier].dependencies |= duplicate_package.dependencies
npm_json.fetch('dependencies', {}).values.map do |d|
Expand All @@ -39,6 +39,23 @@ def flattened_dependencies(npm_json, existing_packages = {})
existing_packages
end

# Read the dependency's package.json file in order to get details like the license, authors,
# and so on. In NPM versions < 7, this information was included in the output of `npm list`.
# In later versions, it no longer is, and has to be read from the package.json file instead.
def package_for_dependency(npm_json)
package_path = npm_json['path']
package_json_path = Pathname.new(package_path).join('package.json') unless package_path.nil?

if package_json_path.nil? || !package_json_path.exist?
# Ancient NPM versions did not have the "path" field. Resort to the old way of gathering
# the details, expecting them to be contained in the output of `npm list`.
NpmPackage.new(npm_json)
else
package_json = JSON.parse(package_json_path.read, max_nesting: false)
NpmPackage.new(npm_json, package_json)
end
end

def populate_groups(package_json)
package_json.groups.each do |group|
group.package_names.each do |package_name|
Expand All @@ -64,16 +81,17 @@ def populate_child_groups(dependency, packages, populated_ids = [])
end
end

def initialize(npm_json)
@json = npm_json
def initialize(npm_json, package_json = npm_json)
@npm_json = npm_json
@json = package_json
@identifier = Identifier.from_hash(npm_json)
@dependencies = deps_from_json
super(@identifier.name,
@identifier.version,
description: npm_json['description'],
homepage: npm_json['homepage'],
description: package_json['description'],
homepage: package_json['homepage'],
authors: author_names,
spec_licenses: Package.license_names_from_standard_spec(npm_json),
spec_licenses: Package.license_names_from_standard_spec(package_json),
install_path: npm_json['path'],
children: @dependencies.map(&:name))
end
Expand Down Expand Up @@ -125,7 +143,7 @@ def package_url
private

def deps_from_json
@json.fetch('dependencies', {}).values.map { |dep| Identifier.from_hash(dep) }.compact
@npm_json.fetch('dependencies', {}).values.map { |dep| Identifier.from_hash(dep) }.compact
end

class Identifier
Expand All @@ -139,7 +157,7 @@ def initialize(name, version)
def self.from_hash(hash)
name = hash['name']
version = hash['version']
return nil if name.nil? || version.nil?
return nil if name.nil? || name.empty? || version.nil? || version.empty?

Identifier.new(name, version)
end
Expand Down
65 changes: 51 additions & 14 deletions spec/lib/license_finder/package_managers/npm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ module LicenseFinder
end
end

def common_before
NPM.instance_variable_set(:@modules, nil)
FileUtils.mkdir_p(Dir.tmpdir)
FileUtils.mkdir_p(root)
File.write(File.join(root, 'package.json'), package_json)
end

describe '.prepare' do
include FakeFS::SpecHelpers
before do
NPM.instance_variable_set(:@modules, nil)
FileUtils.mkdir_p(Dir.tmpdir)
FileUtils.mkdir_p(root)
File.write(File.join(root, 'package.json'), package_json)
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long')
common_before
allow(SharedHelpers::Cmd).to receive(:run).with('npm -v')
.and_return(['7.0.0', '', cmd_success])
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long --all')
.and_return([dependency_json, '', cmd_success])
end

Expand All @@ -60,11 +66,10 @@ module LicenseFinder
describe '.current_packages' do
include FakeFS::SpecHelpers
before do
NPM.instance_variable_set(:@modules, nil)
FileUtils.mkdir_p(Dir.tmpdir)
FileUtils.mkdir_p(root)
File.write(File.join(root, 'package.json'), package_json)
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long')
common_before
allow(SharedHelpers::Cmd).to receive(:run).with('npm -v')
.and_return(['7.0.0', '', cmd_success])
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long --all')
.and_return([dependency_json, '', cmd_success])
end

Expand Down Expand Up @@ -101,12 +106,12 @@ module LicenseFinder
end

it 'fails when command fails' do
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long').and_return ['', 'error', cmd_fail_random_status]
expect { npm.current_packages }.to raise_error("Command 'npm list --json --long' failed to execute: error")
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long --all').and_return ['', 'error', cmd_fail_random_status]
expect { npm.current_packages }.to raise_error("Command 'npm list --json --long --all' failed to execute: error")
end

it 'continues when command fails with exitstatus 1' do
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long').and_return ['{}', 'error', cmd_fail_unmet_status]
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long --all').and_return ['{}', 'error', cmd_fail_unmet_status]
expect { npm.current_packages }.not_to raise_error
end

Expand All @@ -118,7 +123,7 @@ module LicenseFinder
context 'ignored_groups contains devDependencies' do
let(:npm) { NPM.new project_path: Pathname.new(root), ignored_groups: 'devDependencies' }
it 'should include a production flag' do
expect(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long --production')
expect(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long --all --production')
.and_return([dependency_json, '', cmd_success])
npm.current_packages
end
Expand Down Expand Up @@ -234,5 +239,37 @@ module LicenseFinder
end
end
end

describe '.call_to_old_npm_version' do
include FakeFS::SpecHelpers
before do
common_before
allow(SharedHelpers::Cmd).to receive(:run).with('npm -v')
.and_return(['6.0.0', '', cmd_success])
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long')
.and_return([dependency_json, '', cmd_success])
end

it 'does not include --all' do
expect(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long')
npm.current_packages
end
end

describe '.call_to_new_npm_version' do
include FakeFS::SpecHelpers
before do
common_before
allow(SharedHelpers::Cmd).to receive(:run).with('npm -v')
.and_return(['7.0.0', '', cmd_success])
allow(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long --all')
.and_return([dependency_json, '', cmd_success])
end

it 'includes --all' do
expect(SharedHelpers::Cmd).to receive(:run).with('npm list --json --long --all')
npm.current_packages
end
end
end
end

0 comments on commit 420c544

Please sign in to comment.