From cc14193961eae316349bc5576f4607755a6c29e9 Mon Sep 17 00:00:00 2001 From: Zeke Gabrielse Date: Mon, 4 Nov 2024 09:25:52 -0600 Subject: [PATCH] add npm package metadata controller --- .../npm/package_metadata_controller.rb | 57 +++++++++++++++++++ app/models/release_manifest.rb | 3 +- config/initializers/mime_types.rb | 1 + config/routes.rb | 11 ++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb diff --git a/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb b/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb new file mode 100644 index 0000000000..05159c28f6 --- /dev/null +++ b/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'compact_index' + +module Api::V1::ReleaseEngines + class Npm::PackageMetadateController < 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) + .where_assoc_exists(:manifest) # must exist + .preload(:manifest, + release: %i[product entitlements constraints], + ) + authorize! artifacts, + to: :index? + + metadata = artifacts.reduce(name: package.name, time: {}, 'dist-tags': {}, versions: {}) do |metadata, artifact| + manifest = artifact.manifest + release = artifact.release + package_json = manifest.as_package_json + version = release.version + tag = release.tag + + metadata[:time][version] = artifact.created_at.iso8601 + metadata[:'dist-tags'][tag] = version + metadata[:versions][version] = { + ...package_json, + dist: { + tarball: vanity_v1_account_release_artifact_url(current_account, artifact, filename: artifact, host: request.host), + }, + } + end + + render json: metadata + end + + 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 diff --git a/app/models/release_manifest.rb b/app/models/release_manifest.rb index f88c1f3ad0..c41e93e0e4 100644 --- a/app/models/release_manifest.rb +++ b/app/models/release_manifest.rb @@ -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 diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 89eccd865e..6ef26151f0 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 50a1f603e5..3493b10ecf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,6 +46,7 @@ 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 @@ -53,6 +54,7 @@ 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 @@ -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'