trifle-docs 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f51e6c6b892eaca5b790d652ccb970146c9e98b624cf3353c84af3ffb95ed4c
4
- data.tar.gz: f5058c9fa39c81a635db554fabffaab5535f57a76a078138319df0e72732cd49
3
+ metadata.gz: 4660d54f61927575620d127f9493bc2921bf4a0395362acd2f71b4eeaee38712
4
+ data.tar.gz: c59600b536c976dbf67eaf4f99a61d214b1022b6a18795cc7ea37d9f5caf4ba7
5
5
  SHA512:
6
- metadata.gz: 48ce9ec16b882be46f90825f0e12bb13c783fa16e5b22f5eae27551418a5e58d112a321496fdf46810474c68de5f8933661931a696b6378bc836ffd7a8e74192
7
- data.tar.gz: 9721a213e1f2cef167e0adf4ed18025b0740ba6594e26f6f0f326e4e6412631f5957447f503cd2ca45c4ce4e2df5efb497a15a8aaf2943a29b246a2c373869a8
6
+ metadata.gz: fc4c3fa9f2d46e94c6a7a19169830b33c2601fbc2a39360945d57691332b3d3a6d00be8fdc12aa13a379595aefd655f887c02dcbd3ceda561c6431fdda1b0d2d
7
+ data.tar.gz: cd867ffc069b37ee0e513893abe097c8ed4c6a5ecdd8e7fb1d47c0fd0c33f950892024ca04ac653f3281383aab64cb3d7c88671ee5b5b8579b543b49112b3745
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trifle-docs (0.7.0)
4
+ trifle-docs (0.7.2)
5
5
  redcarpet
6
6
  rouge
7
7
  sinatra
data/README.md CHANGED
@@ -41,6 +41,8 @@ require 'trifle/docs'
41
41
  Trifle::Docs.configure do |config|
42
42
  config.path = File.join(__dir__, 'docs')
43
43
  config.views = File.join(__dir__, 'templates')
44
+ # Optional: canonical base URL used for absolute <loc> values in sitemap.xml
45
+ # config.sitemap_base_url = ENV.fetch('TRIFLE_DOCS_SITEMAP_BASE_URL', nil)
44
46
  config.register_harvester(Trifle::Docs::Harvester::Markdown)
45
47
  config.register_harvester(Trifle::Docs::Harvester::File)
46
48
  end
@@ -110,6 +112,22 @@ There are several variables available in your template file (except `layout.erb`
110
112
  - **Caching support** - Optional caching for production environments
111
113
  - **Navigation helpers** - Automatic menu and breadcrumb generation
112
114
 
115
+ ## Sitemap URL configuration
116
+
117
+ `/sitemap.xml` emits absolute URLs. By default, Trifle::Docs uses the incoming request host (`request.base_url`).
118
+
119
+ If your app runs behind a proxy/ingress and you need a canonical public domain, set:
120
+
121
+ ```ruby
122
+ config.sitemap_base_url = 'https://docs.trifle.io'
123
+ ```
124
+
125
+ In containerized deployments you can wire this through an env var, for example:
126
+
127
+ ```ruby
128
+ config.sitemap_base_url = ENV.fetch('TRIFLE_DOCS_SITEMAP_BASE_URL', nil)
129
+ ```
130
+
113
131
  ## Harvesters
114
132
 
115
133
  Trifle::Docs supports multiple content processors:
@@ -4,65 +4,11 @@ require 'sinatra/base'
4
4
 
5
5
  module Trifle
6
6
  module Docs
7
- class App < Sinatra::Base
8
- configure do
9
- set :bind, '0.0.0.0'
10
- set :views, proc { Trifle::Docs.default.views }
11
- end
12
-
13
- get '/search' do
14
- results = Trifle::Docs.search(query: params['query'], scope: params['scope'])
15
- erb(
16
- 'search'.to_sym,
17
- {},
18
- {
19
- results: results,
20
- query: params['query'],
21
- scope: params['scope'],
22
- sitemap: Trifle::Docs.sitemap,
23
- meta: { description: 'Search' }
24
- }
25
- )
26
- end
27
-
28
- get '/llms.txt' do
29
- meta = Trifle::Docs.meta(url: 'llms.txt')
30
- return send_file(meta['path']) if meta && meta['type'] == 'file'
31
-
32
- content = Trifle::Docs::Helper::Llms.homepage_markdown
33
- halt(404, 'Not Found') if content.nil?
34
-
35
- content_type 'text/markdown'
36
- content
37
- end
38
-
39
- get '/llms-full.txt' do
40
- meta = Trifle::Docs.meta(url: 'llms-full.txt')
41
- return send_file(meta['path']) if meta && meta['type'] == 'file'
42
-
43
- content = Trifle::Docs::Helper::Llms.full_markdown
44
- halt(404, 'Not Found') if content.nil? || content.strip.empty?
45
-
46
- content_type 'text/markdown'
47
- content
48
- end
49
-
50
- get '/sitemap.xml' do
51
- meta = Trifle::Docs.meta(url: 'sitemap.xml')
52
- return send_file(meta['path']) if meta && meta['type'] == 'file'
53
-
54
- render_generated_sitemap
55
- end
56
-
57
- get '/*' do
58
- handle_request(params, request)
59
- end
60
-
7
+ module AppHelpers
61
8
  def markdown_requested?(request, params)
62
9
  return true if params['format'].to_s.downcase == 'md'
63
10
 
64
- accept = request.env['HTTP_ACCEPT'].to_s
65
- accept.include?('text/markdown')
11
+ request.env['HTTP_ACCEPT'].to_s.include?('text/markdown')
66
12
  end
67
13
 
68
14
  def render_markdown?(meta, request, params)
@@ -77,18 +23,18 @@ module Trifle
77
23
  meta = Trifle::Docs.meta(url: url)
78
24
  halt(404, 'Not Found') if meta.nil?
79
25
 
26
+ set_vary_header unless file_meta?(meta)
80
27
  return render_markdown(meta, url) if render_markdown?(meta, request, params)
81
- return send_file(meta['path']) if meta['type'] == 'file'
28
+ return send_file(meta['path']) if file_meta?(meta)
82
29
 
83
30
  render_html(meta, url)
84
31
  end
85
32
 
86
33
  def render_markdown(meta, url)
87
- content_type 'text/markdown'
34
+ content_type markdown_content_type(request)
88
35
  Trifle::Docs::Helper::MarkdownLayout.render(
89
36
  meta: meta,
90
- raw_content: Trifle::Docs.raw_content(url: url),
91
- sitemap: Trifle::Docs.sitemap
37
+ raw_content: Trifle::Docs.raw_content(url: url)
92
38
  )
93
39
  end
94
40
 
@@ -103,14 +49,126 @@ module Trifle
103
49
  end
104
50
 
105
51
  def render_generated_sitemap
106
- content = Trifle::Docs::Helper::Sitemap.xml
52
+ base_url = Trifle::Docs.default.sitemap_base_url || request.base_url
53
+ content = Trifle::Docs::Helper::Sitemap.xml(base_url: base_url)
107
54
  halt(404, 'Not Found') if content.nil? || content.strip.empty?
108
55
 
109
56
  content_type 'application/xml'
110
57
  content
111
58
  end
112
59
 
113
- private :handle_request
60
+ def markdown_content_type(request)
61
+ return 'text/plain' if Trifle::Docs::Helper::AiDetection.ai_scraper?(request.user_agent)
62
+
63
+ 'text/markdown'
64
+ end
65
+
66
+ def set_vary_header
67
+ headers['Vary'] = append_vary(headers['Vary'], 'User-Agent')
68
+ headers['Vary'] = append_vary(headers['Vary'], 'Accept')
69
+ end
70
+
71
+ def render_llms_for(base_url, full:)
72
+ base_url = normalize_base_url(base_url)
73
+ url = llms_url(base_url, full: full)
74
+ meta = Trifle::Docs.meta(url: url)
75
+ return send_file(meta['path']) if file_meta?(meta)
76
+
77
+ content = llms_content(full: full, base_url: base_url)
78
+ halt(404, 'Not Found') if llms_missing?(content, full: full)
79
+
80
+ content_type 'text/plain'
81
+ content
82
+ end
83
+
84
+ private
85
+
86
+ def append_vary(existing, value)
87
+ values = existing.to_s.split(',').map(&:strip).reject(&:empty?)
88
+ return value if values.empty?
89
+ return existing if values.any? { |entry| entry.casecmp(value).zero? }
90
+
91
+ (values + [value]).join(', ')
92
+ end
93
+
94
+ def normalize_base_url(base_url)
95
+ base_url.to_s.gsub(%r{^/+}, '').gsub(%r{/+$}, '')
96
+ end
97
+
98
+ def llms_url(base_url, full:)
99
+ llms_name = full ? 'llms-full.txt' : 'llms.txt'
100
+ [base_url, llms_name].reject(&:empty?).join('/')
101
+ end
102
+
103
+ def llms_content(full:, base_url:)
104
+ if full
105
+ Trifle::Docs::Helper::Llms.full_markdown(base_url: base_url)
106
+ else
107
+ Trifle::Docs::Helper::Llms.homepage_markdown(base_url: base_url)
108
+ end
109
+ end
110
+
111
+ def llms_missing?(content, full:)
112
+ return true if content.nil?
113
+ return false unless full
114
+
115
+ content.strip.empty?
116
+ end
117
+
118
+ def file_meta?(meta)
119
+ meta && meta['type'] == 'file'
120
+ end
121
+ end
122
+
123
+ class App < Sinatra::Base
124
+ configure do
125
+ set :bind, '0.0.0.0'
126
+ set :views, proc { Trifle::Docs.default.views }
127
+ end
128
+
129
+ helpers AppHelpers
130
+
131
+ get '/search' do
132
+ results = Trifle::Docs.search(query: params['query'], scope: params['scope'])
133
+ erb(
134
+ 'search'.to_sym,
135
+ {},
136
+ {
137
+ results: results,
138
+ query: params['query'],
139
+ scope: params['scope'],
140
+ sitemap: Trifle::Docs.sitemap,
141
+ meta: { description: 'Search' }
142
+ }
143
+ )
144
+ end
145
+
146
+ get '/llms.txt' do
147
+ render_llms_for('', full: false)
148
+ end
149
+
150
+ get %r{/(.+)/llms\.txt} do |path|
151
+ render_llms_for(path, full: false)
152
+ end
153
+
154
+ get '/llms-full.txt' do
155
+ render_llms_for('', full: true)
156
+ end
157
+
158
+ get %r{/(.+)/llms-full\.txt} do |path|
159
+ render_llms_for(path, full: true)
160
+ end
161
+
162
+ get '/sitemap.xml' do
163
+ meta = Trifle::Docs.meta(url: 'sitemap.xml')
164
+ return send_file(meta['path']) if meta && meta['type'] == 'file'
165
+
166
+ render_generated_sitemap
167
+ end
168
+
169
+ get '/*' do
170
+ handle_request(params, request)
171
+ end
114
172
  end
115
173
  end
116
174
  end
@@ -3,13 +3,14 @@
3
3
  module Trifle
4
4
  module Docs
5
5
  class Configuration
6
- attr_accessor :path, :views, :layout, :namespace, :cache
6
+ attr_accessor :path, :views, :layout, :namespace, :cache, :sitemap_base_url
7
7
 
8
8
  def initialize
9
9
  @harvesters = []
10
10
  @path = nil
11
11
  @namespace = nil
12
12
  @cache = true
13
+ @sitemap_base_url = nil
13
14
  end
14
15
 
15
16
  def harvester
@@ -19,6 +19,8 @@ if Object.const_defined?('Rails')
19
19
  root to: 'page#show'
20
20
  get 'llms.txt', to: 'page#llms'
21
21
  get 'llms-full.txt', to: 'page#llms_full'
22
+ get '*path/llms.txt', to: 'page#llms'
23
+ get '*path/llms-full.txt', to: 'page#llms_full'
22
24
  get 'sitemap.xml', to: 'page#sitemap'
23
25
  get 'search', to: 'page#search'
24
26
  get '*url', to: 'page#show'
@@ -37,7 +39,7 @@ if Object.const_defined?('Rails')
37
39
  return render_not_found if content.nil?
38
40
  return render_not_found if !allow_empty && content.strip.empty?
39
41
 
40
- render plain: content, content_type: 'text/markdown'
42
+ render plain: content, content_type: 'text/plain'
41
43
  end
42
44
 
43
45
  def render_sitemap(url)
@@ -71,9 +73,8 @@ if Object.const_defined?('Rails')
71
73
  def render_markdown(url:, meta:)
72
74
  render plain: Trifle::Docs::Helper::MarkdownLayout.render(
73
75
  meta: meta,
74
- raw_content: Trifle::Docs.raw_content(url: url, config: configuration),
75
- sitemap: Trifle::Docs.sitemap(config: configuration)
76
- ), content_type: 'text/markdown'
76
+ raw_content: Trifle::Docs.raw_content(url: url, config: configuration)
77
+ ), content_type: markdown_content_type
77
78
  end
78
79
 
79
80
  def fetch_meta(url)
@@ -81,6 +82,7 @@ if Object.const_defined?('Rails')
81
82
  end
82
83
 
83
84
  def render_for_meta(meta, url, wants_md, request)
85
+ set_vary_header unless file_meta?(meta)
84
86
  return render_markdown(url: url, meta: meta) if render_markdown?(meta, wants_md, request)
85
87
  return render_file(meta: meta) if file_meta?(meta)
86
88
 
@@ -99,6 +101,10 @@ if Object.const_defined?('Rails')
99
101
  [url, wants_md]
100
102
  end
101
103
 
104
+ def llms_scope
105
+ params[:path].to_s.gsub(%r{^/+}, '').gsub(%r{/+$}, '')
106
+ end
107
+
102
108
  def render_markdown?(meta, wants_md, request)
103
109
  return false if meta['type'] == 'file'
104
110
 
@@ -110,6 +116,25 @@ if Object.const_defined?('Rails')
110
116
 
111
117
  request.headers['Accept'].to_s.include?('text/markdown')
112
118
  end
119
+
120
+ def markdown_content_type
121
+ return 'text/plain' if Trifle::Docs::Helper::AiDetection.ai_scraper?(request.user_agent)
122
+
123
+ 'text/markdown'
124
+ end
125
+
126
+ def set_vary_header
127
+ response.headers['Vary'] = append_vary(response.headers['Vary'], 'User-Agent')
128
+ response.headers['Vary'] = append_vary(response.headers['Vary'], 'Accept')
129
+ end
130
+
131
+ def append_vary(existing, value)
132
+ values = existing.to_s.split(',').map(&:strip).reject(&:empty?)
133
+ return value if values.empty?
134
+ return existing if values.any? { |entry| entry.casecmp(value).zero? }
135
+
136
+ (values + [value]).join(', ')
137
+ end
113
138
  end
114
139
 
115
140
  class PageController < ActionController::Base
@@ -146,20 +171,31 @@ if Object.const_defined?('Rails')
146
171
  end
147
172
 
148
173
  def llms
149
- render_llms('llms.txt', allow_empty: true) do
150
- Trifle::Docs::Helper::Llms.homepage_markdown(config: configuration)
174
+ base_url = llms_scope
175
+ llms_url = [base_url, 'llms.txt'].reject(&:empty?).join('/')
176
+ render_llms(llms_url, allow_empty: true) do
177
+ Trifle::Docs::Helper::Llms.homepage_markdown(
178
+ config: configuration,
179
+ base_url: base_url
180
+ )
151
181
  end
152
182
  end
153
183
 
154
184
  def llms_full
155
- render_llms('llms-full.txt') do
156
- Trifle::Docs::Helper::Llms.full_markdown(config: configuration)
185
+ base_url = llms_scope
186
+ llms_url = [base_url, 'llms-full.txt'].reject(&:empty?).join('/')
187
+ render_llms(llms_url) do
188
+ Trifle::Docs::Helper::Llms.full_markdown(
189
+ config: configuration,
190
+ base_url: base_url
191
+ )
157
192
  end
158
193
  end
159
194
 
160
195
  def sitemap
196
+ base_url = configuration.sitemap_base_url || request.base_url
161
197
  render_sitemap('sitemap.xml') do
162
- Trifle::Docs::Helper::Sitemap.xml(config: configuration)
198
+ Trifle::Docs::Helper::Sitemap.xml(config: configuration, base_url: base_url)
163
199
  end
164
200
  end
165
201
  end
@@ -6,32 +6,56 @@ module Trifle
6
6
  module Llms
7
7
  module_function
8
8
 
9
- def homepage_markdown(config: nil)
10
- meta = Trifle::Docs.meta(url: '', config: config)
9
+ def homepage_markdown(config: nil, base_url: '')
10
+ base_url = normalize_base_url(base_url)
11
+ meta = Trifle::Docs.meta(url: base_url, config: config)
11
12
  return nil if meta.nil?
12
13
 
13
14
  Trifle::Docs::Helper::MarkdownLayout.render(
14
15
  meta: meta,
15
- raw_content: Trifle::Docs.raw_content(url: '', config: config),
16
- sitemap: Trifle::Docs.sitemap(config: config)
16
+ raw_content: Trifle::Docs.raw_content(url: base_url, config: config)
17
17
  )
18
18
  end
19
19
 
20
- def full_markdown(config: nil)
20
+ def full_markdown(config: nil, base_url: '')
21
+ pages = llms_pages(config: config, base_url: base_url)
22
+ return nil if pages.nil?
23
+
24
+ pages.filter_map { |page| render_llms_page(page, config: config) }
25
+ .join("\n\n")
26
+ end
27
+
28
+ def llms_pages(config:, base_url:)
29
+ base_url = normalize_base_url(base_url)
21
30
  sitemap = Trifle::Docs.sitemap(config: config)
22
- pages = flatten_sitemap(sitemap)
31
+ scoped_sitemap = sitemap_subtree(sitemap, base_url)
32
+ return nil if scoped_sitemap.nil?
23
33
 
24
- chunks = pages.filter_map do |page|
25
- meta = page[:meta]
26
- next if meta.nil? || meta['type'] == 'file'
34
+ path = base_url.empty? ? [] : base_url.split('/')
35
+ flatten_sitemap(scoped_sitemap, path)
36
+ end
27
37
 
28
- raw = Trifle::Docs.raw_content(url: page[:url], config: config)
29
- next if raw.nil? || raw.strip.empty?
38
+ def render_llms_page(page, config:)
39
+ meta = page[:meta]
40
+ return nil if meta.nil? || meta['type'] == 'file'
30
41
 
31
- format_page(meta: meta, url: page[:url], raw_content: raw)
32
- end
42
+ raw = Trifle::Docs.raw_content(url: page[:url], config: config)
43
+ return nil if raw.nil? || raw.strip.empty?
44
+
45
+ format_page(meta: meta, url: page[:url], raw_content: raw)
46
+ end
47
+
48
+ def sitemap_subtree(sitemap, base_url)
49
+ return nil unless sitemap.is_a?(Hash)
50
+
51
+ base_url = normalize_base_url(base_url)
52
+ return sitemap if base_url.empty?
53
+
54
+ sitemap.dig(*base_url.split('/'))
55
+ end
33
56
 
34
- chunks.join("\n\n")
57
+ def normalize_base_url(base_url)
58
+ base_url.to_s.gsub(%r{^/+}, '').gsub(%r{/+$}, '')
35
59
  end
36
60
 
37
61
  def flatten_sitemap(node, path = [])
@@ -6,15 +6,12 @@ module Trifle
6
6
  module MarkdownLayout
7
7
  module_function
8
8
 
9
- def render(meta:, raw_content:, sitemap:) # rubocop:disable Metrics/MethodLength
9
+ def render(meta:, raw_content:)
10
10
  lines = []
11
11
  title = meta['title'] || derive_title_from_url(meta['url'])
12
12
 
13
13
  lines << "# #{title}"
14
14
  lines << ''
15
- lines << '## Navigation'
16
- lines << navigation_toc(sitemap)
17
- lines << ''
18
15
  lines << '## Content'
19
16
  lines << raw_content.to_s.strip
20
17
  lines << ''
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'cgi'
4
4
  require 'time'
5
+ require 'uri'
5
6
 
6
7
  module Trifle
7
8
  module Docs
@@ -9,9 +10,10 @@ module Trifle
9
10
  module Sitemap
10
11
  module_function
11
12
 
12
- def xml(config: nil)
13
+ def xml(config: nil, base_url: nil)
13
14
  sitemap = Trifle::Docs.sitemap(config: config)
14
- urls = sitemap_urls(sitemap)
15
+ resolved_base_url = resolve_base_url(config: config, base_url: base_url)
16
+ urls = sitemap_urls(sitemap, base_url: resolved_base_url)
15
17
  return nil if urls.empty?
16
18
 
17
19
  build_document(urls)
@@ -31,13 +33,13 @@ module Trifle
31
33
  entries
32
34
  end
33
35
 
34
- def sitemap_urls(sitemap)
36
+ def sitemap_urls(sitemap, base_url: nil)
35
37
  entries = flatten_sitemap(sitemap)
36
38
  entries.filter_map do |entry|
37
39
  meta = entry[:meta]
38
40
  next if meta.nil? || meta['type'] == 'file'
39
41
 
40
- build_url_entry(entry[:url], meta)
42
+ build_url_entry(entry[:url], meta, base_url: base_url)
41
43
  end
42
44
  end
43
45
 
@@ -50,21 +52,59 @@ module Trifle
50
52
  ].join("\n")
51
53
  end
52
54
 
53
- def build_url_entry(url, meta)
54
- loc = normalize_loc(url, meta)
55
+ def build_url_entry(url, meta, base_url: nil)
56
+ loc = normalize_loc(url, meta, base_url: base_url)
55
57
  lastmod = format_lastmod(meta['updated_at'])
56
58
  lastmod_tag = lastmod ? "<lastmod>#{lastmod}</lastmod>" : ''
57
59
 
58
60
  "<url><loc>#{loc}</loc>#{lastmod_tag}</url>"
59
61
  end
60
62
 
61
- def normalize_loc(url, meta)
63
+ def normalize_loc(url, meta, base_url: nil)
62
64
  loc = meta['url']
63
65
  loc = "/#{url}" if loc.nil? || loc.empty?
64
66
  loc = '/' if loc == '//'
67
+ loc = absolutize_loc(loc, base_url)
65
68
  CGI.escapeHTML(loc)
66
69
  end
67
70
 
71
+ def absolutize_loc(loc, base_url)
72
+ return loc if base_url.nil? || absolute_url?(loc)
73
+
74
+ path = loc.to_s.gsub(%r{^/+}, '')
75
+ return "#{base_url}/" if path.empty?
76
+
77
+ "#{base_url}/#{path}"
78
+ end
79
+
80
+ def absolute_url?(loc)
81
+ loc.to_s.match?(%r{\Ahttps?://}i)
82
+ end
83
+
84
+ def resolve_base_url(config:, base_url:)
85
+ normalize_base_url(base_url) || normalize_base_url(configuration_base_url(config))
86
+ end
87
+
88
+ def configuration_base_url(config)
89
+ return nil unless config.respond_to?(:sitemap_base_url)
90
+
91
+ config.sitemap_base_url
92
+ end
93
+
94
+ def normalize_base_url(base_url)
95
+ value = base_url.to_s.strip
96
+ return nil if value.empty?
97
+
98
+ uri = URI.parse(value)
99
+ return nil unless uri.is_a?(URI::HTTP) && uri.host
100
+
101
+ uri.query = nil
102
+ uri.fragment = nil
103
+ uri.to_s.gsub(%r{/+$}, '')
104
+ rescue URI::InvalidURIError
105
+ nil
106
+ end
107
+
68
108
  def format_lastmod(value)
69
109
  return nil if value.nil?
70
110
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Trifle
4
4
  module Docs
5
- VERSION = '0.7.0'
5
+ VERSION = '0.7.2'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trifle-docs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jozef Vaclavik
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-27 00:00:00.000000000 Z
11
+ date: 2026-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler