Class: Bundler::Spinel::Proxy

Inherits:
Object
  • Object
show all
Defined in:
lib/bundler/spinel/proxy.rb

Overview

A curated RubyGems source (Compact Index protocol) that serves only vetted gems. Point a Gemfile at it:

source "http://localhost:9292"
gem "cleangem"

and ‘bundle lock` resolves *only against vetted gems*. A gem that isn’t served (no acceptable verdict for the pinned engine rev) becomes a plain “could not find compatible versions” resolution failure — no plugin, no engine-directive trick. The whitelist is not a file: it’s the acceptable subset of the ledger at this rev, plus a local store of .gem artifacts we built/verified ourselves.

Empirically the third-party rubygems ecosystem is ~all-rejected today, so the load-bearing mode is ‘:store` — a directory of our own Spinel-vetted .gem files. (Filtered read-through of upstream is a documented extension.)

NOTE: this CRuby/WEBrick implementation is the MVP that proves Bundler resolves against it. The dogfood target is to serve the same endpoints from a Spinel-compiled Tep app — see ARCHITECTURE.md §“Dogfooding”.

Instance Method Summary collapse

Constructor Details

#initialize(store:, ledger: Ledger.new, engine: Engine.new, min_verdict: :verified) ⇒ Proxy

store: dir of vetted *.gem files (the curated artifacts).



31
32
33
34
35
36
37
# File 'lib/bundler/spinel/proxy.rb', line 31

def initialize(store:, ledger: Ledger.new, engine: Engine.new,
               min_verdict: :verified)
  @store = store
  @ledger = ledger
  @engine = engine
  @min_verdict = min_verdict
end

Instance Method Details

#catalogObject

name => { version => spec } for every vetted gem in the store.



40
41
42
43
44
45
46
47
# File 'lib/bundler/spinel/proxy.rb', line 40

def catalog
  @catalog ||= Dir[File.join(@store, "*.gem")].each_with_object({}) do |path, acc|
    spec = Gem::Package.new(path).spec
    next unless acceptable?(spec.name, spec.version.to_s)

    (acc[spec.name] ||= {})[spec.version.to_s] = { spec: spec, path: path }
  end
end

#mount_on(server) ⇒ Object

Mount the Compact Index endpoints onto an existing WEBrick server, so a combined server (Server) can serve the human site statically and the curated source from the same process — the apex double-duty layout.



83
# File 'lib/bundler/spinel/proxy.rb', line 83

def mount_on(server) = mount(server)

#serve(port: 9292, quiet: true) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/bundler/spinel/proxy.rb', line 67

def serve(port: 9292, quiet: true)
  server = WEBrick::HTTPServer.new(
    Port: port,
    Logger: WEBrick::Log.new(quiet ? File::NULL : $stderr),
    AccessLog: []
  )
  mount(server)
  trap("INT") { server.shutdown }
  warn "[spinel-proxy] curated source on http://localhost:#{port} " \
       "(#{catalog.size} gems, min=#{@min_verdict}, rev=#{@engine.rev})"
  server.start
end

#write_static(out) ⇒ Object

Write the curated source as a static Compact Index tree:

out/names  out/versions  out/info/<gem>  out/gems/<file>.gem

All digest/JSON happens here, offline, in CRuby. The result is plain text + file bytes — so the dogfood server (Tep/Spinel, which has neither digest nor JSON — probed 2026-05-26) only has to serve static files.



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/bundler/spinel/proxy.rb', line 54

def write_static(out)
  require "fileutils"
  FileUtils.mkdir_p(File.join(out, "info"))
  FileUtils.mkdir_p(File.join(out, "gems"))
  File.write(File.join(out, "names"), names_body)
  File.write(File.join(out, "versions"), versions_body)
  catalog.each_key { |name| File.write(File.join(out, "info", name), info_body(name)) }
  catalog.values.flat_map(&:values).each do |e|
    FileUtils.cp(e[:path], File.join(out, "gems", File.basename(e[:path])))
  end
  out
end