releasehx 0.1.0
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 +7 -0
- data/README.adoc +2915 -0
- data/bin/releasehx +7 -0
- data/bin/rhx +7 -0
- data/bin/rhx-mcp +7 -0
- data/bin/sourcerer +32 -0
- data/build/docs/CNAME +1 -0
- data/build/docs/Gemfile.lock +95 -0
- data/build/docs/_config.yml +36 -0
- data/build/docs/config-reference.adoc +4104 -0
- data/build/docs/config-reference.json +1546 -0
- data/build/docs/index.adoc +2915 -0
- data/build/docs/landing.adoc +21 -0
- data/build/docs/manpage.adoc +68 -0
- data/build/docs/releasehx.1 +281 -0
- data/build/docs/releasehx_readme.html +367 -0
- data/build/docs/sample-config.adoc +9 -0
- data/build/docs/sample-config.yml +251 -0
- data/build/docs/schemagraphy_readme.html +0 -0
- data/build/docs/sourcerer_readme.html +46 -0
- data/build/snippets/helpscreen.txt +29 -0
- data/lib/docopslab/mcp/asset_packager.rb +30 -0
- data/lib/docopslab/mcp/manifest.rb +67 -0
- data/lib/docopslab/mcp/resource_pack.rb +46 -0
- data/lib/docopslab/mcp/server.rb +92 -0
- data/lib/docopslab/mcp.rb +6 -0
- data/lib/releasehx/cli.rb +937 -0
- data/lib/releasehx/configuration.rb +215 -0
- data/lib/releasehx/generated.rb +17 -0
- data/lib/releasehx/helpers.rb +58 -0
- data/lib/releasehx/mcp/asset_packager.rb +21 -0
- data/lib/releasehx/mcp/assets/agent-config-guide.md +178 -0
- data/lib/releasehx/mcp/assets/config-def.yml +1426 -0
- data/lib/releasehx/mcp/assets/config-reference.adoc +4104 -0
- data/lib/releasehx/mcp/assets/config-reference.json +1546 -0
- data/lib/releasehx/mcp/assets/sample-config.yml +251 -0
- data/lib/releasehx/mcp/manifest.rb +18 -0
- data/lib/releasehx/mcp/resource_pack.rb +26 -0
- data/lib/releasehx/mcp/server.rb +57 -0
- data/lib/releasehx/mcp.rb +7 -0
- data/lib/releasehx/ops/check_ops.rb +136 -0
- data/lib/releasehx/ops/draft_ops.rb +173 -0
- data/lib/releasehx/ops/enrich_ops.rb +221 -0
- data/lib/releasehx/ops/template_ops.rb +61 -0
- data/lib/releasehx/ops/write_ops.rb +124 -0
- data/lib/releasehx/rest/clients/github.yml +46 -0
- data/lib/releasehx/rest/clients/gitlab.yml +31 -0
- data/lib/releasehx/rest/clients/jira.yml +31 -0
- data/lib/releasehx/rest/yaml_client.rb +418 -0
- data/lib/releasehx/rhyml/adapter.rb +740 -0
- data/lib/releasehx/rhyml/change.rb +167 -0
- data/lib/releasehx/rhyml/liquid.rb +13 -0
- data/lib/releasehx/rhyml/loaders.rb +37 -0
- data/lib/releasehx/rhyml/mappings/github.yaml +60 -0
- data/lib/releasehx/rhyml/mappings/gitlab.yaml +73 -0
- data/lib/releasehx/rhyml/mappings/jira.yaml +29 -0
- data/lib/releasehx/rhyml/mappings/verb_past_tenses.yml +98 -0
- data/lib/releasehx/rhyml/release.rb +144 -0
- data/lib/releasehx/rhyml.rb +15 -0
- data/lib/releasehx/sgyml/helpers.rb +45 -0
- data/lib/releasehx/transforms/adf_to_markdown.rb +307 -0
- data/lib/releasehx/version.rb +7 -0
- data/lib/releasehx.rb +69 -0
- data/lib/schemagraphy/attribute_resolver.rb +48 -0
- data/lib/schemagraphy/cfgyml/definition.rb +90 -0
- data/lib/schemagraphy/cfgyml/doc_builder.rb +52 -0
- data/lib/schemagraphy/cfgyml/path_reference.rb +24 -0
- data/lib/schemagraphy/data_query/json_pointer.rb +42 -0
- data/lib/schemagraphy/loader.rb +59 -0
- data/lib/schemagraphy/regexp_utils.rb +215 -0
- data/lib/schemagraphy/safe_expression.rb +189 -0
- data/lib/schemagraphy/schema_utils.rb +124 -0
- data/lib/schemagraphy/tag_utils.rb +32 -0
- data/lib/schemagraphy/templating.rb +104 -0
- data/lib/schemagraphy.rb +17 -0
- data/lib/sourcerer/builder.rb +120 -0
- data/lib/sourcerer/jekyll/bootstrapper.rb +78 -0
- data/lib/sourcerer/jekyll/liquid/file_system.rb +74 -0
- data/lib/sourcerer/jekyll/liquid/filters.rb +215 -0
- data/lib/sourcerer/jekyll/liquid/tags.rb +44 -0
- data/lib/sourcerer/jekyll/monkeypatches.rb +73 -0
- data/lib/sourcerer/jekyll.rb +26 -0
- data/lib/sourcerer/plaintext_converter.rb +75 -0
- data/lib/sourcerer/templating.rb +190 -0
- data/lib/sourcerer.rb +322 -0
- data/specs/data/api-client-schema.yaml +160 -0
- data/specs/data/config-def.yml +1426 -0
- data/specs/data/mcp-manifest.yml +50 -0
- data/specs/data/rhyml-mapping-schema.yaml +410 -0
- data/specs/data/rhyml-schema.yaml +152 -0
- metadata +376 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'faraday'
|
|
5
|
+
require 'base64'
|
|
6
|
+
require 'liquid'
|
|
7
|
+
require 'erb'
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
require 'digest'
|
|
10
|
+
|
|
11
|
+
module ReleaseHx
|
|
12
|
+
module REST
|
|
13
|
+
class YamlClient
|
|
14
|
+
attr_reader :raw_response
|
|
15
|
+
|
|
16
|
+
def initialize config, version = nil
|
|
17
|
+
@version = version
|
|
18
|
+
@config = config
|
|
19
|
+
@origin_cfg = config['origin'] || {}
|
|
20
|
+
@origin_source = @origin_cfg['source']
|
|
21
|
+
@vars = build_scope
|
|
22
|
+
@client_def = load_render_client_def
|
|
23
|
+
@resolved_values = {}
|
|
24
|
+
@cache_config = config.dig('paths', 'cache') || {}
|
|
25
|
+
@raw_response = nil
|
|
26
|
+
normalize_fields!
|
|
27
|
+
setup_connection
|
|
28
|
+
perform_resolutions!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fetch_all
|
|
32
|
+
# Check for cached response first (unless force fetch is requested)
|
|
33
|
+
if cache_enabled? && !force_fetch_requested? && (cached_data = cached_response)
|
|
34
|
+
ReleaseHx.logger.info "Using cached API response (#{cached_data.size} items) from #{@origin_source}"
|
|
35
|
+
return cached_data
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Fetch fresh data from API
|
|
39
|
+
results = fetch_fresh_data
|
|
40
|
+
|
|
41
|
+
# Save to cache if caching is enabled
|
|
42
|
+
save_to_cache(results) if cache_enabled?
|
|
43
|
+
|
|
44
|
+
ReleaseHx.logger.info "Fetched #{results.size} items from #{@origin_source} API"
|
|
45
|
+
results
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fetch_fresh_data
|
|
49
|
+
results = []
|
|
50
|
+
|
|
51
|
+
if pagination?
|
|
52
|
+
page_param = pagination['param']
|
|
53
|
+
page_size_key = pagination['page_size_param']
|
|
54
|
+
page_size_val = pagination['page_size']
|
|
55
|
+
|
|
56
|
+
current_index = 0
|
|
57
|
+
loop_count = 0
|
|
58
|
+
max_pages = pagination['max_pages'] || 100
|
|
59
|
+
|
|
60
|
+
loop do
|
|
61
|
+
query = query_params.merge(
|
|
62
|
+
{
|
|
63
|
+
page_param => current_index,
|
|
64
|
+
page_size_key => page_size_val
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
# Report to logger debug the API URL and query params
|
|
68
|
+
ReleaseHx.logger.debug "Fetching from: #{@href} with query: #{query.inspect}"
|
|
69
|
+
|
|
70
|
+
resp = @conn.get(@href, query, @headers)
|
|
71
|
+
body = resp.body
|
|
72
|
+
raise "HTTP Error #{resp.status}" unless resp.success?
|
|
73
|
+
|
|
74
|
+
# Save raw response from first page for payload export
|
|
75
|
+
@raw_response = body if loop_count.zero?
|
|
76
|
+
|
|
77
|
+
issues = extract_issues_from_response(body)
|
|
78
|
+
results.concat(Array(issues))
|
|
79
|
+
|
|
80
|
+
break if issues.nil? || issues.size.to_i < page_size_val
|
|
81
|
+
|
|
82
|
+
current_index += page_size_val
|
|
83
|
+
loop_count += 1
|
|
84
|
+
break if loop_count >= max_pages
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
resp = @conn.get(@href, query_params, @headers)
|
|
88
|
+
raise "HTTP Error #{resp.status}" unless resp.success?
|
|
89
|
+
|
|
90
|
+
# Save raw response before extraction
|
|
91
|
+
@raw_response = resp.body
|
|
92
|
+
|
|
93
|
+
issues = extract_issues_from_response(resp.body)
|
|
94
|
+
results = Array(issues)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
results
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def extract_issues_from_response body
|
|
103
|
+
root_path = @client_def['root_issues_path']
|
|
104
|
+
if root_path && !root_path.empty? && root_path != '.'
|
|
105
|
+
body[root_path]
|
|
106
|
+
else
|
|
107
|
+
body
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_scope
|
|
112
|
+
{
|
|
113
|
+
'origin' => @origin_cfg.merge('version' => @version),
|
|
114
|
+
'env' => ENV.to_h
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def load_render_client_def
|
|
119
|
+
user_dir = @config.dig('paths', 'api_clients_dir') || '_apis'
|
|
120
|
+
user_file = File.join(user_dir, "#{@origin_source}.yaml")
|
|
121
|
+
builtin_file = File.expand_path("clients/#{@origin_source}.yml", __dir__)
|
|
122
|
+
path = File.exist?(user_file) ? user_file : builtin_file
|
|
123
|
+
raise "Missing client config for API: #{@origin_source}" unless File.exist?(path)
|
|
124
|
+
|
|
125
|
+
raw = File.read(path)
|
|
126
|
+
# Load raw YAML first, then selectively render templated fields
|
|
127
|
+
YAML.safe_load(raw)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def normalize_fields!
|
|
131
|
+
@href = @origin_cfg['href'] || render_field(@client_def['href'])
|
|
132
|
+
# Use client auth if main config auth is incomplete (missing mode/header/format)
|
|
133
|
+
main_auth = @origin_cfg['auth'] || {}
|
|
134
|
+
client_auth = @client_def['auth'] || {}
|
|
135
|
+
has_required = main_auth['mode'] && main_auth['header'] && (main_auth['format'] || main_auth['key_env'])
|
|
136
|
+
@auth = has_required ? main_auth : client_auth
|
|
137
|
+
@headers = build_headers
|
|
138
|
+
@query_string = @origin_cfg['string'] || @client_def['query_string']
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def pagination
|
|
142
|
+
@origin_cfg['pagination'] || @client_def['pagination']
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def pagination?
|
|
146
|
+
!!pagination
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def render_field val
|
|
150
|
+
context = build_scope['origin'].merge('env' => build_scope['env'])
|
|
151
|
+
render_if_templated(val, context)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def render_if_templated template_def, context
|
|
155
|
+
return template_def unless template_def.is_a?(String) && template_def.include?('{{')
|
|
156
|
+
|
|
157
|
+
# Use Liquid templating directly like RHYML adapter
|
|
158
|
+
template = ::Liquid::Template.parse(template_def)
|
|
159
|
+
template.render(context)
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
ReleaseHx.logger.error "Error rendering template '#{template_def}': #{e.message}"
|
|
162
|
+
template_def # Return original on error
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def build_headers
|
|
166
|
+
return {} unless @auth['mode'] && @auth['header'] && @auth['format']
|
|
167
|
+
|
|
168
|
+
value = render_field(@auth['format'])
|
|
169
|
+
{
|
|
170
|
+
@auth['header'] => value
|
|
171
|
+
}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def query_params
|
|
175
|
+
base = {}
|
|
176
|
+
|
|
177
|
+
# Prefer structured query_params over legacy query_string
|
|
178
|
+
if @client_def['query_params']
|
|
179
|
+
# New structured approach
|
|
180
|
+
query_type = @client_def['query_type'] || 'key_value'
|
|
181
|
+
|
|
182
|
+
case query_type.to_s.downcase
|
|
183
|
+
when 'jql'
|
|
184
|
+
# Jira JQL; render the whole query_params as a single JQL string
|
|
185
|
+
base['jql'] = render_structured_params(@client_def['query_params'])
|
|
186
|
+
else
|
|
187
|
+
# GitHub/GitLab style; render each param individually
|
|
188
|
+
@client_def['query_params'].each do |key, value|
|
|
189
|
+
base[key] = render_field_with_resolutions(value)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
elsif @query_string
|
|
193
|
+
# Legacy query_string approach (backward compatibility)
|
|
194
|
+
rendered_query = render_field_with_resolutions(@query_string)
|
|
195
|
+
query_type = @client_def['query_type'] || detect_query_type(rendered_query)
|
|
196
|
+
|
|
197
|
+
case query_type.to_s.downcase
|
|
198
|
+
when 'jql'
|
|
199
|
+
base['jql'] = rendered_query
|
|
200
|
+
when 'key_value'
|
|
201
|
+
base.merge!(parse_query_string_to_hash(rendered_query))
|
|
202
|
+
when 'query_string'
|
|
203
|
+
base['string'] = rendered_query
|
|
204
|
+
else
|
|
205
|
+
base.merge!(smart_parse_query_params(rendered_query))
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Merge any additional params from config
|
|
210
|
+
base.merge!(@origin_cfg['params'] || {})
|
|
211
|
+
base
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def detect_query_type query_str
|
|
215
|
+
# Smart detection based on query string characteristics
|
|
216
|
+
if query_str.include?('&') && query_str.include?('=')
|
|
217
|
+
# Contains & and = - likely key_value format
|
|
218
|
+
'key_value'
|
|
219
|
+
elsif query_str.match?(/\b(AND|OR|IN|NOT|ORDER BY|WHERE)\b/i)
|
|
220
|
+
# Contains SQL/JQL keywords; likely JQL
|
|
221
|
+
'jql'
|
|
222
|
+
else
|
|
223
|
+
# Default to query_string for safety
|
|
224
|
+
'query_string'
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def parse_query_string_to_hash query_str
|
|
229
|
+
# Parse "key=value&key2=value2" into hash
|
|
230
|
+
result = {}
|
|
231
|
+
query_str.split('&').each do |pair|
|
|
232
|
+
key, value = pair.split('=', 2)
|
|
233
|
+
result[key] = value if key && value
|
|
234
|
+
end
|
|
235
|
+
result
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def smart_parse_query_params query_str
|
|
239
|
+
# Smart fallback parsing
|
|
240
|
+
if query_str.include?('&') && query_str.include?('=')
|
|
241
|
+
# Looks like key-value pairs
|
|
242
|
+
parse_query_string_to_hash(query_str)
|
|
243
|
+
elsif query_str.match?(/\b(AND|OR|IN|NOT)\b/i)
|
|
244
|
+
# Single string; could be JQL or raw query
|
|
245
|
+
# Looks like JQL
|
|
246
|
+
{ 'jql' => query_str }
|
|
247
|
+
else
|
|
248
|
+
# Raw query string
|
|
249
|
+
{ 'string' => query_str }
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def perform_resolutions!
|
|
254
|
+
resolutions = @client_def['resolutions'] || {}
|
|
255
|
+
return if resolutions.empty?
|
|
256
|
+
|
|
257
|
+
ReleaseHx.logger.debug "Performing #{resolutions.keys.size} resolutions: #{resolutions.keys.join(', ')}"
|
|
258
|
+
|
|
259
|
+
resolutions.each do |name, config|
|
|
260
|
+
@resolved_values[name] = resolve_entity(config)
|
|
261
|
+
ReleaseHx.logger.debug "Resolved #{name}: #{@resolved_values[name]}"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def resolve_entity config
|
|
266
|
+
# Build resolution endpoint URL with proper context
|
|
267
|
+
context = build_scope['origin'].merge('env' => build_scope['env'])
|
|
268
|
+
endpoint = render_if_templated(config['endpoint'], context)
|
|
269
|
+
base_url = @href.split('/repos/').first # Extract base API URL
|
|
270
|
+
resolution_url = "#{base_url}#{endpoint}"
|
|
271
|
+
|
|
272
|
+
ReleaseHx.logger.debug "Resolving entity from: #{resolution_url}"
|
|
273
|
+
|
|
274
|
+
# Fetch resolution data
|
|
275
|
+
resp = @conn.get(resolution_url, {}, @headers)
|
|
276
|
+
raise "Resolution HTTP Error #{resp.status} for #{endpoint}" unless resp.success?
|
|
277
|
+
|
|
278
|
+
entities = Array(resp.body)
|
|
279
|
+
match_value = render_if_templated(config['match_value'], context)
|
|
280
|
+
lookup_field = config['lookup_field']
|
|
281
|
+
return_field = config['return_field']
|
|
282
|
+
|
|
283
|
+
ReleaseHx.logger.debug "Looking for #{lookup_field}='#{match_value}' in #{entities.size} entities"
|
|
284
|
+
|
|
285
|
+
# Find matching entity
|
|
286
|
+
matching_entity = entities.find { |entity| entity[lookup_field] == match_value }
|
|
287
|
+
|
|
288
|
+
if matching_entity
|
|
289
|
+
result = matching_entity[return_field]
|
|
290
|
+
ReleaseHx.logger.debug "Found match: #{lookup_field}='#{match_value}' -> #{return_field}='#{result}'"
|
|
291
|
+
result
|
|
292
|
+
else
|
|
293
|
+
available = entities.map { |e| e[lookup_field] }.compact.join(', ')
|
|
294
|
+
raise "No entity found with #{lookup_field}='#{match_value}'. Available: #{available}"
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def render_field_with_resolutions val
|
|
299
|
+
# Build context with resolved values for templating
|
|
300
|
+
context = build_scope['origin'].merge('env' => build_scope['env']).merge(@resolved_values)
|
|
301
|
+
ReleaseHx.logger.debug "Template context for '#{val}': #{context.keys} (resolved: #{@resolved_values})"
|
|
302
|
+
result = render_if_templated(val, context)
|
|
303
|
+
ReleaseHx.logger.debug "Template result: '#{result}'"
|
|
304
|
+
result
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def render_structured_params params_hash
|
|
308
|
+
# For JQL; combine all params into a single query string
|
|
309
|
+
rendered_parts = params_hash.map do |key, value|
|
|
310
|
+
rendered_value = render_field_with_resolutions(value)
|
|
311
|
+
"#{key}=#{rendered_value}"
|
|
312
|
+
end
|
|
313
|
+
rendered_parts.join(' AND ')
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def setup_connection
|
|
317
|
+
require 'faraday/follow_redirects'
|
|
318
|
+
@conn = Faraday.new do |f|
|
|
319
|
+
f.request :url_encoded
|
|
320
|
+
f.response :json, parser_options: { symbolize_names: false }
|
|
321
|
+
f.use Faraday::FollowRedirects::Middleware
|
|
322
|
+
f.adapter Faraday.default_adapter
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Cache management methods
|
|
327
|
+
def cache_enabled?
|
|
328
|
+
@cache_config['enabled']
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def force_fetch_requested?
|
|
332
|
+
# Check if CLI options force fresh fetch (--force or --fetch flags)
|
|
333
|
+
# These are passed through the config under cli_flags
|
|
334
|
+
@config.dig('cli_flags', 'force') || @config.dig('cli_flags', 'fetch')
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def cache_dir
|
|
338
|
+
@cache_config['dir']
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def cache_ttl_hours
|
|
342
|
+
@cache_config['ttl_hours']
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def cache_file_path
|
|
346
|
+
# Create structured cache path: cache_dir/api_from/version/payload.json
|
|
347
|
+
version_part = @version || 'default'
|
|
348
|
+
cache_subdir = File.join(cache_dir, @source_type, version_part)
|
|
349
|
+
File.join(cache_subdir, 'payload.json')
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def cached_response
|
|
353
|
+
cache_path = cache_file_path
|
|
354
|
+
return nil unless File.exist?(cache_path)
|
|
355
|
+
|
|
356
|
+
# Check if cache is still valid (within TTL)
|
|
357
|
+
cache_age_hours = (Time.now - File.mtime(cache_path)) / 3600.0
|
|
358
|
+
if cache_age_hours > cache_ttl_hours
|
|
359
|
+
ReleaseHx.logger.debug "Cache expired (#{cache_age_hours.round(1)}h old, TTL: #{cache_ttl_hours}h)"
|
|
360
|
+
return nil
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
ReleaseHx.logger.debug "Using cache from #{cache_path} (#{cache_age_hours.round(1)}h old)"
|
|
364
|
+
|
|
365
|
+
begin
|
|
366
|
+
cached_content = File.read(cache_path)
|
|
367
|
+
JSON.parse(cached_content)
|
|
368
|
+
rescue StandardError => e
|
|
369
|
+
ReleaseHx.logger.warn "Failed to read cache file #{cache_path}: #{e.message}"
|
|
370
|
+
nil
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def save_to_cache api_response
|
|
375
|
+
cache_path = cache_file_path
|
|
376
|
+
cache_subdir = File.dirname(cache_path)
|
|
377
|
+
|
|
378
|
+
begin
|
|
379
|
+
# Create cache directory structure
|
|
380
|
+
FileUtils.mkdir_p(cache_subdir)
|
|
381
|
+
|
|
382
|
+
# Write API response to cache file
|
|
383
|
+
File.write(cache_path, JSON.pretty_generate(api_response))
|
|
384
|
+
ReleaseHx.logger.debug "Saved API response to cache: #{cache_path}"
|
|
385
|
+
|
|
386
|
+
# Handle .gitignore if requested
|
|
387
|
+
handle_gitignore_prompt if @cache_config['prompt_gitignore']
|
|
388
|
+
rescue StandardError => e
|
|
389
|
+
ReleaseHx.logger.warn "Failed to save cache to #{cache_path}: #{e.message}"
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def handle_gitignore_prompt
|
|
394
|
+
gitignore_path = '.gitignore'
|
|
395
|
+
cache_dir_pattern = "/#{cache_dir.gsub(%r{^\.?/}, '')}"
|
|
396
|
+
|
|
397
|
+
# Check if .gitignore already contains our cache directory
|
|
398
|
+
if File.exist?(gitignore_path)
|
|
399
|
+
gitignore_content = File.read(gitignore_path)
|
|
400
|
+
return if gitignore_content.include?(cache_dir_pattern)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Prompt user to add cache directory to .gitignore
|
|
404
|
+
# For now, we'll just add it automatically since we can't prompt in non-interactive mode
|
|
405
|
+
begin
|
|
406
|
+
File.open(gitignore_path, 'a') do |f|
|
|
407
|
+
f.puts
|
|
408
|
+
f.puts '# ReleaseHx API cache'
|
|
409
|
+
f.puts cache_dir_pattern
|
|
410
|
+
end
|
|
411
|
+
ReleaseHx.logger.info "Added #{cache_dir_pattern} to .gitignore"
|
|
412
|
+
rescue StandardError => e
|
|
413
|
+
ReleaseHx.logger.warn "Could not update .gitignore: #{e.message}"
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|