Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bin/importmap verify compares vendored files with remotes #237

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,12 @@ module MyEngine
end
```

## Checking for outdated or vulnerable packages
## Checking for outdated, vulnerable or altered packages

Importmap for Rails provides two commands to check your pinned packages:
- `./bin/importmap outdated` checks the NPM registry for new versions
- `./bin/importmap audit` checks the NPM registry for known security issues
- `./bin/importmap verify` checks the vendored files against a fresh download

## License

Expand Down
24 changes: 24 additions & 0 deletions lib/importmap/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,30 @@ def audit
end
end

desc "verify", "Verify that vendored files are identical to the pinned remote"
desc "pin [*PACKAGES]", "Pin new packages"
option :env, type: :string, aliases: :e, default: "production"
option :from, type: :string, aliases: :f, default: "jspm"
def verify(*packages)
if packages.empty?
packages = npm.packages_with_versions.map do |p, v|
v.blank? ? p : [p, v].join("@")
end
end

if imports = packager.import(*packages, env: options[:env], from: options[:from])
imports.each do |package, url|
puts %(Verifying "#{package}" download from #{url})
packager.verify(package, url)
end
else
puts "No packages found"
end
rescue Importmap::Packager::VerifyError => error
puts error.message
exit 1
end

desc "outdated", "Check for outdated packages"
def outdated
if (outdated_packages = npm.outdated_packages).any?
Expand Down
36 changes: 31 additions & 5 deletions lib/importmap/packager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

class Importmap::Packager
Error = Class.new(StandardError)
VerifyError = Class.new(Error)
HTTPError = Class.new(Error)
ServiceError = Error.new(Error)

Expand Down Expand Up @@ -51,6 +52,14 @@ def packaged?(package)
importmap.match(/^pin ["']#{package}["'].*$/)
end

def verify(package, url)
ensure_vendor_directory_exists

if vendored_package_path(package).file?
verify_vendored_package(package, url)
end
end

def download(package, url)
ensure_vendor_directory_exists
remove_existing_package_file(package)
Expand Down Expand Up @@ -95,7 +104,6 @@ def importmap
@importmap ||= File.read(@importmap_path)
end


def ensure_vendor_directory_exists
FileUtils.mkdir_p @vendor_path
end
Expand All @@ -114,27 +122,45 @@ def remove_package_from_importmap(package)
end

def download_package_file(package, url)
body = load_package_file(package, url)
save_vendored_package(package, url, body)
end

def load_package_file(package, url)
response = Net::HTTP.get_response(URI(url))

if response.code == "200"
save_vendored_package(package, url, response.body)
format_vendored_package(package, url, response.body)
else
handle_failure_response(response)
end
end

def format_vendored_package(package, url, source)
formatted = "// #{package}#{extract_package_version_from(url)} downloaded from #{url}\n\n"
formatted.concat remove_sourcemap_comment_from(source).force_encoding("UTF-8")
formatted
end

def save_vendored_package(package, url, source)
File.open(vendored_package_path(package), "w+") do |vendored_package|
vendored_package.write "// #{package}#{extract_package_version_from(url)} downloaded from #{url}\n\n"

vendored_package.write remove_sourcemap_comment_from(source).force_encoding("UTF-8")
vendored_package.write source
end
end

def remove_sourcemap_comment_from(source)
source.gsub(/^\/\/# sourceMappingURL=.*/, "")
end

def verify_vendored_package(package, url)
vendored_body = vendored_package_path(package).read.strip
remote_body = load_package_file(package, url).strip

return true if vendored_body == remote_body

raise VerifyError.new("Vendored #{package}#{extract_package_version_from(url)} does not match remote #{url}")
end

def vendored_package_path(package)
@vendor_path.join(package_filename(package))
end
Expand Down
12 changes: 11 additions & 1 deletion test/packager_integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,25 @@ class Importmap::PackagerIntegrationTest < ActiveSupport::TestCase
vendored_package_file = Pathname.new(vendor_dir).join("@github--webauthn-json.js")
assert File.exist?(vendored_package_file)
assert_equal "// @github/[email protected] downloaded from #{package_url}", File.readlines(vendored_package_file).first.strip
assert @packager.verify("@github/webauthn-json", package_url)

package_url = "https://ga.jspm.io/npm:[email protected]/index.js"
vendored_package_file = Pathname.new(vendor_dir).join("react.js")
@packager.download("react", package_url)
assert File.exist?(vendored_package_file)
assert_equal "// [email protected] downloaded from #{package_url}", File.readlines(vendored_package_file).first.strip

assert @packager.verify("react", package_url)

File.write(vendored_package_file, "// altered content")

assert_raises(Importmap::Packager::VerifyError) do
@packager.verify("react", package_url)
end

@packager.remove("react")
assert_not File.exist?(Pathname.new(vendor_dir).join("react.js"))

refute @packager.verify("react", package_url)
end
end
end