Skip to content

Commit cd61070

Browse files
hsbtclaude
andcommitted
Capture created_at metadata on EndpointSpecification
The cooldown feature needs each gem version's publish timestamp on the client side. Compact index v2 exposes it as a `created_at:ISO8601` entry in the info-line metadata; expose a Time-typed `created_at` attribute on the spec so the resolver can consult it later. Parsing is defensive against older rubygems whose APISet GemParser splits ISO8601 timestamps on every colon, accepting both Array and flat String shapes and silently dropping malformed values. The time stdlib is required lazily inside the case branch so loading the file does not activate the `time` default gem during Bundler.setup. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 227b3d6 commit cd61070

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

bundler/lib/bundler/endpoint_specification.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Bundler
55
class EndpointSpecification < Gem::Specification
66
include MatchRemoteMetadata
77

8-
attr_reader :name, :version, :platform, :checksum
8+
attr_reader :name, :version, :platform, :checksum, :created_at
99
attr_writer :dependencies
1010
attr_accessor :remote, :locked_platform
1111

@@ -145,6 +145,7 @@ def parse_metadata(data)
145145
unless data
146146
@required_ruby_version = nil
147147
@required_rubygems_version = nil
148+
@created_at = nil
148149
return
149150
end
150151

@@ -161,6 +162,16 @@ def parse_metadata(data)
161162
@required_rubygems_version = Gem::Requirement.new(v)
162163
when "ruby"
163164
@required_ruby_version = Gem::Requirement.new(v)
165+
when "created_at"
166+
value = v.is_a?(Array) ? v.last : v
167+
if value.is_a?(String)
168+
require "time"
169+
begin
170+
@created_at = Time.iso8601(value)
171+
rescue ArgumentError
172+
@created_at = nil
173+
end
174+
end
164175
end
165176
end
166177
rescue StandardError => e

spec/bundler/endpoint_specification_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,46 @@
4646
)
4747
end
4848
end
49+
50+
context "when the metadata has created_at" do
51+
let(:metadata) { { "created_at" => ["2026-05-12T10:00:00Z"] } }
52+
53+
it "parses created_at as a Time" do
54+
expect(subject.created_at).to eq(Time.utc(2026, 5, 12, 10, 0, 0))
55+
end
56+
end
57+
58+
context "when the metadata has a string created_at (older rubygems shape)" do
59+
let(:metadata) { { "created_at" => "2026-05-12T10:00:00Z" } }
60+
61+
it "still parses created_at" do
62+
expect(subject.created_at).to eq(Time.utc(2026, 5, 12, 10, 0, 0))
63+
end
64+
end
65+
66+
context "when created_at is truncated (older rubygems splits on colons)" do
67+
let(:metadata) { { "created_at" => "2026-05-12T10" } }
68+
69+
it "leaves created_at as nil instead of raising" do
70+
expect(subject.created_at).to be_nil
71+
end
72+
end
73+
74+
context "when the metadata has no created_at" do
75+
let(:metadata) { { "checksum" => ["abc"] } }
76+
let(:spec_fetcher) { double(:spec_fetcher, uri: "https://rubygems.org") }
77+
78+
it "leaves created_at as nil" do
79+
allow(Bundler::Checksum).to receive(:from_api).and_return(nil)
80+
expect(subject.created_at).to be_nil
81+
end
82+
end
83+
84+
context "when the metadata is nil" do
85+
it "leaves created_at as nil" do
86+
expect(subject.created_at).to be_nil
87+
end
88+
end
4989
end
5090

5191
describe "#required_ruby_version" do

0 commit comments

Comments
 (0)