Skip to content

Commit

Permalink
add npm package metadata controller
Browse files Browse the repository at this point in the history
  • Loading branch information
ezekg committed Nov 4, 2024
1 parent be22f33 commit eee1e73
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

require 'compact_index'

module Api::V1::ReleaseEngines
class Npm::PackageMetadataController < Api::V1::BaseController
before_action :scope_to_current_account!
before_action :require_active_subscription!
before_action :authenticate_with_token
before_action :set_package

def show
authorize! package,
to: :show?

artifacts = authorized_scope(package.artifacts.npm_package_tgz.order_by_version)
.where_assoc_exists(:manifest) # must exist
.preload(:manifest,
release: %i[product entitlements constraints],
)
authorize! artifacts,
to: :index?

latest = artifacts.first
metadata = artifacts.reduce(name: package.name, time: {}, 'dist-tags': { latest: latest.release.version }, versions: {}) do |metadata, artifact|
package_json = artifact.manifest.as_package_json
release = artifact.release

metadata[:time][release.version] = artifact.created_at.iso8601
metadata[:'dist-tags'][release.tag] = release.version if release.tag?
metadata[:versions][release.version] = package_json.merge(
dist: {
tarball: vanity_v1_account_release_artifact_url(current_account, artifact, filename: artifact.filename, host: request.host),
},
)

metadata
end

render json: metadata
end

private

attr_reader :package

def set_package
scoped_packages = authorized_scope(current_account.release_packages.npm)
.where_assoc_exists(
%i[releases artifacts manifest], # must exist
)

Current.resource = @package = FindByAliasService.call(
scoped_packages,
id: params[:package],
aliases: :key,
)
end
end
end
3 changes: 2 additions & 1 deletion app/models/release_artifact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,8 @@ class ReleaseArtifact < ApplicationRecord
scope :yanked, -> { joins(:release).where(releases: { status: 'YANKED' }) }
scope :unyanked, -> { joins(:release).where.not(releases: { status: 'YANKED' }) }

scope :gems, -> { for_filetype(:gem) }
scope :gems, -> { for_engine(:rubygems).for_filetype(:gem) }
scope :npm_package_tgz, -> { for_engine(:npm).for_filetype(:tgz) }

def key_for(path) = "artifacts/#{account_id}/#{release_id}/#{path}"
def key = key_for(filename)
Expand Down
3 changes: 2 additions & 1 deletion app/models/release_manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ class ReleaseManifest < ApplicationRecord
end
end

def as_gemspec = Gem::Specification.from_yaml(content)
def as_gemspec = Gem::Specification.from_yaml(content)
def as_package_json = JSON.parse(content)
end
8 changes: 4 additions & 4 deletions app/workers/process_docker_image_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ def perform(artifact_id)
.body

# unpack the package tarball
io = gunzip(tgz)
tar = gunzip(tgz)

unpack io do |tar|
tar.each do |entry|
unpack tar do |archive|
archive.each do |entry|
case entry.name
in 'manifest.json'
raise ImageNotAcceptableError, 'manifest must be a manifest.json file' unless
Expand Down Expand Up @@ -67,7 +67,7 @@ def perform(artifact_id)
end

# not sure why GzipReader#open doesn't take an io?
io.close
tar.close

artifact.update!(status: 'UPLOADED')

Expand Down
8 changes: 4 additions & 4 deletions app/workers/process_npm_package_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ def perform(artifact_id)
.body

# unpack the package tarball
io = gunzip(tgz)
tar = gunzip(tgz)

unpack io do |tar|
unpack tar do |archive|
# NOTE(ezekg) npm prefixes everything in the archive with package/
entry = tar.find { _1.name in 'package/package.json' }
entry = archive.find { _1.name in 'package/package.json' }

raise PackageNotAcceptableError, 'manifest at package/package.json must exist' if
entry.nil?
Expand All @@ -54,7 +54,7 @@ def perform(artifact_id)
end

# not sure why GzipReader#open doesn't take an io?
io.close
tar.close

artifact.update!(status: 'UPLOADED')

Expand Down
1 change: 1 addition & 0 deletions config/initializers/mime_types.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

Mime::Type.register 'application/vnd.npm.install-v1+json', :npm
Mime::Type.register 'application/octet-stream', :binary
Mime::Type.register 'application/vnd.api+json', :jsonapi, %W[
application/vnd.keygen+json
Expand Down
25 changes: 25 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@
end

concern :pypi do
# see: https://peps.python.org/pep-0503/
scope module: :pypi, constraints: MimeTypeConstraint.new(:html, raise_on_no_match: true), defaults: { format: :html } do
get 'simple/', to: 'simple#index', as: :pypi_simple_packages, trailing_slash: true
get 'simple/:package/', to: 'simple#show', as: :pypi_simple_package, trailing_slash: true
end
end

concern :tauri do
# see: https://v2.tauri.app/plugin/updater/#dynamic-update-server
scope module: :tauri, constraints: MimeTypeConstraint.new(:binary, :json, raise_on_no_match: true), defaults: { format: :json } do
get ':package', to: 'upgrades#show'
end
Expand Down Expand Up @@ -89,6 +91,15 @@
end
end

concern :npm do
# see: https://github.com/npm/registry/blob/ae49abf1bac0ec1a3f3f1fceea1cca6fe2dc00e1/docs/responses/package-metadata.md
scope module: :npm, constraints: MimeTypeConstraint.new(:json, :npm, raise_on_no_match: true), defaults: { format: :json } do
get ':package', to: 'package_metadata#show', as: :npm_package_metadata, constraints: {
package: /.*/
}
end
end

concern :v1 do
get :ping, to: 'health#general_ping'

Expand Down Expand Up @@ -465,6 +476,9 @@
scope :rubygems do
concerns :rubygems
end
scope :npm do
concerns :npm
end
end
end

Expand Down Expand Up @@ -601,6 +615,17 @@
concerns :rubygems
end
end

scope module: 'api/v1/release_engines', constraints: { subdomain: 'npm.pkg' } do
case
when Keygen.multiplayer?
scope ':account_id', as: :account do
concerns :npm
end
when Keygen.singleplayer?
concerns :npm
end
end
end

%w[500 503].each do |code|
Expand Down

0 comments on commit eee1e73

Please sign in to comment.