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,937 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require_relative '../releasehx'
|
|
7
|
+
|
|
8
|
+
module ReleaseHx
|
|
9
|
+
class CLI < Thor
|
|
10
|
+
def self.exit_on_failure?
|
|
11
|
+
true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# =======================================
|
|
15
|
+
# OVERRIDE .start to handle no-arguments
|
|
16
|
+
# and default subcommand behavior
|
|
17
|
+
# =======================================
|
|
18
|
+
def self.start original_args = ARGV, config = {}
|
|
19
|
+
# If user gave no arguments at all, or only gave --help, allow that through
|
|
20
|
+
if original_args.empty? || (original_args.length == 1 && original_args.first =~ /^--?h(elp)?$/)
|
|
21
|
+
show_usage_snippet
|
|
22
|
+
return
|
|
23
|
+
elsif original_args.length == 1 && original_args.first =~ /^--man(page)?$/
|
|
24
|
+
show_manpage
|
|
25
|
+
return
|
|
26
|
+
elsif original_args.length == 1 && original_args.first =~ /^--version$/
|
|
27
|
+
puts ReleaseHx::VERSION
|
|
28
|
+
return
|
|
29
|
+
else
|
|
30
|
+
first = original_args[0]
|
|
31
|
+
original_args.unshift 'default' unless first.start_with?('-') || all_tasks.key?(first)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
default_task :default
|
|
38
|
+
|
|
39
|
+
desc 'VERSION|PATH [options]', ReleaseHx.attrs['tagline']
|
|
40
|
+
method_option :md, type: :string, lazy_default: '',
|
|
41
|
+
desc: ReleaseHx.attrs['cli_option_message_md']
|
|
42
|
+
method_option :adoc, type: :string, aliases: ['--ad'], lazy_default: '',
|
|
43
|
+
desc: ReleaseHx.attrs['cli_option_message_adoc']
|
|
44
|
+
method_option :yaml, type: :string, aliases: ['--yml'], lazy_default: '',
|
|
45
|
+
desc: ReleaseHx.attrs['cli_option_message_yaml']
|
|
46
|
+
method_option :html, type: :string, lazy_default: '',
|
|
47
|
+
desc: ReleaseHx.attrs['cli_option_message_html']
|
|
48
|
+
method_option :pdf, type: :string, lazy_default: '',
|
|
49
|
+
desc: ReleaseHx.attrs['cli_option_message_pdf']
|
|
50
|
+
method_option :output_dir, type: :string, lazy_default: '',
|
|
51
|
+
desc: ReleaseHx.attrs['cli_option_message_output_dir']
|
|
52
|
+
method_option :api_data, type: :string, banner: 'PATH',
|
|
53
|
+
desc: ReleaseHx.attrs['cli_option_message_api_data']
|
|
54
|
+
method_option :config, type: :string, banner: 'PATH',
|
|
55
|
+
desc: ReleaseHx.attrs['cli_option_message_config']
|
|
56
|
+
method_option :mapping, type: :string, banner: 'PATH',
|
|
57
|
+
desc: ReleaseHx.attrs['cli_option_message_mapping']
|
|
58
|
+
method_option :payload, type: :string, lazy_default: '',
|
|
59
|
+
desc: ReleaseHx.attrs['cli_option_message_payload']
|
|
60
|
+
method_option :fetch, type: :boolean,
|
|
61
|
+
desc: ReleaseHx.attrs['cli_option_message_fetch']
|
|
62
|
+
method_option :append, type: :boolean,
|
|
63
|
+
desc: ReleaseHx.attrs['cli_option_message_append']
|
|
64
|
+
method_option :over, type: :boolean, aliases: ['--force'],
|
|
65
|
+
desc: ReleaseHx.attrs['cli_option_message_over']
|
|
66
|
+
method_option :check, type: :boolean, aliases: ['--scan'],
|
|
67
|
+
desc: ReleaseHx.attrs['cli_option_message_check']
|
|
68
|
+
method_option :emptynotes, type: :string, aliases: ['--empty', '-e'], lazy_default: '',
|
|
69
|
+
desc: ReleaseHx.attrs['cli_option_message_emptynotes']
|
|
70
|
+
method_option :internal, type: :boolean,
|
|
71
|
+
desc: ReleaseHx.attrs['cli_option_message_internal']
|
|
72
|
+
method_option :wrap, type: :boolean, default: nil,
|
|
73
|
+
desc: ReleaseHx.attrs['cli_option_message_wrap']
|
|
74
|
+
method_option :frontmatter, type: :boolean, default: nil, desc: ReleaseHx.attrs['cli_option_message_frontmatter']
|
|
75
|
+
method_option :verbose, type: :boolean,
|
|
76
|
+
desc: ReleaseHx.attrs['cli_option_message_verbose']
|
|
77
|
+
method_option :debug, type: :boolean,
|
|
78
|
+
desc: ReleaseHx.attrs['cli_option_message_debug']
|
|
79
|
+
method_option :debug_dump, type: :boolean, desc: ReleaseHx.attrs['cli_option_message_debug_dump']
|
|
80
|
+
method_option :quiet, type: :boolean,
|
|
81
|
+
desc: ReleaseHx.attrs['cli_option_message_quiet']
|
|
82
|
+
|
|
83
|
+
# FIXME: This method is overly complex and handles too many concerns.
|
|
84
|
+
# It should be broken down into smaller methods, each handling a specific
|
|
85
|
+
# CLI action or workflow. A major refactor is planned for post-0.1.0.
|
|
86
|
+
def default source_arg
|
|
87
|
+
setup_logger
|
|
88
|
+
ReleaseHx.logger.debug "Starting ReleaseHx with version/source: #{source_arg}"
|
|
89
|
+
load_and_configure_settings(ReleaseHx.attrs['app_default_config_path'])
|
|
90
|
+
|
|
91
|
+
if options[:debug]
|
|
92
|
+
begin
|
|
93
|
+
config_dump = SgymlHelpers.deep_stringify_safe(@settings).to_yaml
|
|
94
|
+
ReleaseHx.logger.debug "Operative config settings:\n#{config_dump}"
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
require 'pp' # pretty print
|
|
97
|
+
ReleaseHx.logger.debug "Rescued config PP dump:\n#{PP.pp(@settings, +'')}"
|
|
98
|
+
raise e
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
source_arg_type = version_or_file(source_arg)
|
|
103
|
+
if source_arg_type == :invalid
|
|
104
|
+
raise Thor::Error, <<~ERRTXT
|
|
105
|
+
ERROR: Invalid file extension for source file: #{source_arg}
|
|
106
|
+
Valid draft file types are: #{ReleaseHx.attrs['draft_source_file_types']}
|
|
107
|
+
Valid extensions are: #{ReleaseHx.attrs['draft_source_extensions']}
|
|
108
|
+
ERRTXT
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
ReleaseHx.logger.info "Source type: #{source_arg_type}"
|
|
112
|
+
|
|
113
|
+
if options[:verbose] && @settings['origin']
|
|
114
|
+
ReleaseHx.logger.debug "✓ Source configured: #{@settings['origin']['source']}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if options[:check]
|
|
118
|
+
if source_arg_type == :file
|
|
119
|
+
raise Thor::Error,
|
|
120
|
+
'ERROR: Scan operations require a version number as the first argument.'
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
perform_scan(source_arg)
|
|
124
|
+
return
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if options[:api_data] && (options[:api_data].nil? || options[:api_data].empty?)
|
|
128
|
+
raise Thor::Error, 'Must specify a PATH for --api-data. E.g. --api-data cached-1-1-1.json'
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
if options[:api_data] && !File.exist?(options[:api_data])
|
|
132
|
+
raise Thor::Error, "API data file not found: #{options[:api_data]}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if options[:api_data]
|
|
136
|
+
ReleaseHx.logger.debug "✓ Using cached API data: #{options[:api_data]}"
|
|
137
|
+
elsif options[:fetch]
|
|
138
|
+
ReleaseHx.logger.info "✓ Will fetch fresh data from #{@settings['origin']['source']} API" if options[:verbose]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if options[:api_data] && options[:fetch]
|
|
142
|
+
ReleaseHx.logger.warn 'Warning: --fetch ignored when --api-data is specified'
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if [options[:adoc], options[:md], options[:yaml]].compact.size > 1
|
|
146
|
+
raise Thor::Error, 'ERROR: Only one of --adoc, --md, or --yaml (or aliases) may be specified.'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
if options[:append]
|
|
150
|
+
perform_append(source_arg)
|
|
151
|
+
return
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
determine_operations(source_arg)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def load_and_configure_settings default_config_path
|
|
160
|
+
if options[:config] && !File.exist?(options[:config])
|
|
161
|
+
raise Thor::Error, "ERROR: Configuration declared but not found: #{options[:config]}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
config_path = options[:config] || default_config_path
|
|
165
|
+
if options[:config]
|
|
166
|
+
ReleaseHx.logger.info "Using specified config file: #{config_path}"
|
|
167
|
+
elsif options[:verbose]
|
|
168
|
+
ReleaseHx.logger.info "Using default config file: #{config_path}"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
ReleaseHx.logger.info 'Loading configuration...' if options[:verbose]
|
|
172
|
+
@settings = ReleaseHx::Configuration.load(config_path).settings
|
|
173
|
+
|
|
174
|
+
merge_cli_flags_into_settings
|
|
175
|
+
|
|
176
|
+
apply_cli_overrides
|
|
177
|
+
|
|
178
|
+
return unless options[:debug]
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
config_dump = SgymlHelpers.deep_stringify_safe(@settings).to_yaml
|
|
182
|
+
ReleaseHx.logger.debug "Operative config settings:\n#{config_dump}"
|
|
183
|
+
rescue StandardError => e
|
|
184
|
+
require 'pp' # pretty print
|
|
185
|
+
ReleaseHx.logger.debug "Rescued config PP dump:\n#{PP.pp(@settings, +'')}"
|
|
186
|
+
raise e
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def setup_logger
|
|
191
|
+
# Ensure the global logger references the same object we configured
|
|
192
|
+
log = ReleaseHx.logger
|
|
193
|
+
|
|
194
|
+
log.level = if options[:debug_dump]
|
|
195
|
+
ReleaseHx::DUMP # lowest level, includes all output
|
|
196
|
+
elsif options[:debug]
|
|
197
|
+
Logger::DEBUG
|
|
198
|
+
elsif options[:quiet]
|
|
199
|
+
Logger::ERROR
|
|
200
|
+
else # default or :verbose
|
|
201
|
+
Logger::INFO
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Log the logging level change for verbose output
|
|
205
|
+
return unless options[:verbose]
|
|
206
|
+
|
|
207
|
+
level_name = case log.level
|
|
208
|
+
when ReleaseHx::DUMP then 'DUMP'
|
|
209
|
+
when Logger::DEBUG then 'DEBUG'
|
|
210
|
+
when Logger::INFO then 'INFO'
|
|
211
|
+
when Logger::WARN then 'WARN'
|
|
212
|
+
when Logger::ERROR then 'ERROR'
|
|
213
|
+
else 'UNKNOWN'
|
|
214
|
+
end
|
|
215
|
+
log.info "Logging level set to #{level_name}"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
class << self
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
def show_usage_snippet
|
|
222
|
+
usage_text = ReleaseHx.read_built_snippet(:helpscreen)
|
|
223
|
+
puts "\nVersion: #{ReleaseHx::VERSION}"
|
|
224
|
+
puts usage_text
|
|
225
|
+
puts
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def show_manpage
|
|
229
|
+
gem_manfile = File.expand_path('../../build/docs/releasehx.1', __dir__)
|
|
230
|
+
|
|
231
|
+
unless File.exist?(gem_manfile)
|
|
232
|
+
warn "Man file not found in gem: #{gem_manfile}"
|
|
233
|
+
return
|
|
234
|
+
end
|
|
235
|
+
display_manpage_locally(gem_manfile)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def display_manpage_locally manfile
|
|
239
|
+
# FIXME: Eventually we want to provide a proper man page for Unix environments
|
|
240
|
+
tmp_dir = File.join(Dir.home, '.rhxtmp')
|
|
241
|
+
FileUtils.mkdir_p(tmp_dir)
|
|
242
|
+
|
|
243
|
+
tmp_path = File.join(tmp_dir, 'releasehx.1')
|
|
244
|
+
FileUtils.cp(manfile, tmp_path)
|
|
245
|
+
|
|
246
|
+
# Use -l to display local file
|
|
247
|
+
system("man -l '#{tmp_path}'")
|
|
248
|
+
|
|
249
|
+
FileUtils.rm_f(tmp_path)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Determine whether first argument is a version # or a proper file path
|
|
254
|
+
def version_or_file source_arg
|
|
255
|
+
extensions = ReleaseHx.attrs['draft_source_extensions'].split(', ')
|
|
256
|
+
if extensions.any? { |ext| source_arg.end_with?(ext) }
|
|
257
|
+
:file
|
|
258
|
+
elsif source_arg.end_with?('.html', '.pdf')
|
|
259
|
+
:invalid
|
|
260
|
+
else
|
|
261
|
+
:version
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def perform_scan version
|
|
266
|
+
ReleaseHx.logger.info("Scanning for missing release notes in version #{version}")
|
|
267
|
+
|
|
268
|
+
# Use the same source determination logic as main operations
|
|
269
|
+
source_type = determine_payload_type(version)
|
|
270
|
+
|
|
271
|
+
case source_type
|
|
272
|
+
when :yaml
|
|
273
|
+
# For scan operations, we need the version code, not a file path
|
|
274
|
+
raise Thor::Error, 'ERROR: Scan operations require a version number, not a YAML file path.'
|
|
275
|
+
when :json
|
|
276
|
+
if options[:api_data]
|
|
277
|
+
payload = load_json_issues(options[:api_data])
|
|
278
|
+
else
|
|
279
|
+
raise Thor::Error,
|
|
280
|
+
'ERROR: JSON source specified but no --api-data file provided. ' \
|
|
281
|
+
'Use --api-data PATH to specify a JSON file.'
|
|
282
|
+
end
|
|
283
|
+
when :api
|
|
284
|
+
# Get configured source type for API operations
|
|
285
|
+
configured_source_type = @settings.dig('origin', 'source')
|
|
286
|
+
case configured_source_type
|
|
287
|
+
when 'jira', 'github', 'gitlab'
|
|
288
|
+
payload = fetch_issues_from_api(version)
|
|
289
|
+
else
|
|
290
|
+
raise Thor::Error,
|
|
291
|
+
"ERROR: API origin requires origin.source to be 'jira', 'github', or 'gitlab', " \
|
|
292
|
+
"but got '#{configured_source_type}'"
|
|
293
|
+
end
|
|
294
|
+
else
|
|
295
|
+
raise Thor::Error, "ERROR: Scanning not supported for source type '#{source_type}'"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
mapping = load_mapping
|
|
299
|
+
release_code = basename_for_output(version)
|
|
300
|
+
release_date = Date.today
|
|
301
|
+
issues_count = payload.is_a?(Array) ? payload.size : payload['issues']&.size || payload.size
|
|
302
|
+
|
|
303
|
+
emptynotes_arg = options[:emptynotes]
|
|
304
|
+
|
|
305
|
+
# Interpret CLI override for --empty/-e
|
|
306
|
+
if emptynotes_arg && !emptynotes_arg.empty?
|
|
307
|
+
if %w[skip empty dump ai].include?(emptynotes_arg)
|
|
308
|
+
# override setting
|
|
309
|
+
@settings['rhyml']['empty_notes'] = emptynotes_arg
|
|
310
|
+
else
|
|
311
|
+
# Toggle logic
|
|
312
|
+
current = @settings.dig('rhyml', 'empty_notes') || 'skip'
|
|
313
|
+
@settings['rhyml']['empty_notes'] = current == 'skip' ? 'empty' : 'skip'
|
|
314
|
+
end
|
|
315
|
+
elsif emptynotes_arg && emptynotes_arg.empty?
|
|
316
|
+
# Flag used without value; toggle logic
|
|
317
|
+
current = @settings.dig('rhyml', 'empty_notes') || 'skip'
|
|
318
|
+
@settings['rhyml']['empty_notes'] = current == 'skip' ? 'empty' : 'skip'
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
require_relative 'ops/check_ops'
|
|
322
|
+
|
|
323
|
+
release = ReleaseHx::DraftOps.from_payload(
|
|
324
|
+
payload: payload,
|
|
325
|
+
config: @settings,
|
|
326
|
+
mapping: mapping,
|
|
327
|
+
release_code: release_code,
|
|
328
|
+
release_date: release_date,
|
|
329
|
+
scan: true)
|
|
330
|
+
|
|
331
|
+
ReleaseHx::CheckOps.print_check_summary(release, issues_count, payload, @settings, mapping)
|
|
332
|
+
nil
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def perform_append source_arg
|
|
336
|
+
ReleaseHx.logger.info('Appending new issues to existing YAML draft')
|
|
337
|
+
|
|
338
|
+
# Determine the existing YAML file path
|
|
339
|
+
yaml_file_path = resolve_yaml_file_path(source_arg)
|
|
340
|
+
|
|
341
|
+
unless File.exist?(yaml_file_path)
|
|
342
|
+
raise Thor::Error, "ERROR: YAML file not found: #{yaml_file_path}. Cannot append without existing file."
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Load the YAML to get the actual version code from the 'code' field
|
|
346
|
+
yaml_data = SchemaGraphy::Loader.load_yaml_with_tags(yaml_file_path)
|
|
347
|
+
version_code = yaml_data['code']
|
|
348
|
+
|
|
349
|
+
raise Thor::Error, "ERROR: No 'code' field found in YAML file: #{yaml_file_path}" unless version_code
|
|
350
|
+
|
|
351
|
+
ReleaseHx.logger.debug "Using version code from YAML: #{version_code}"
|
|
352
|
+
|
|
353
|
+
source_type = determine_payload_type(version_code)
|
|
354
|
+
payload = fetch_or_load_issues(version_code, source_type)
|
|
355
|
+
|
|
356
|
+
# Delegate to DraftOps for the append logic
|
|
357
|
+
new_changes_count = ReleaseHx::DraftOps.append_changes(
|
|
358
|
+
yaml_file_path: yaml_file_path,
|
|
359
|
+
version_code: version_code,
|
|
360
|
+
config: @settings,
|
|
361
|
+
mapping: load_mapping,
|
|
362
|
+
source_type: source_type,
|
|
363
|
+
payload: payload,
|
|
364
|
+
force: options[:over])
|
|
365
|
+
|
|
366
|
+
if new_changes_count.positive?
|
|
367
|
+
ReleaseHx.logger.info "Successfully appended #{new_changes_count} changes to #{yaml_file_path}"
|
|
368
|
+
else
|
|
369
|
+
ReleaseHx.logger.info 'No new changes found to append.'
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def resolve_yaml_file_path source_arg
|
|
374
|
+
if version_or_file(source_arg) == :file
|
|
375
|
+
# Direct file path provided
|
|
376
|
+
unless source_arg.end_with?('.yml', '.yaml', '.rhyml')
|
|
377
|
+
raise Thor::Error, "ERROR: --append requires a YAML file. Got: #{source_arg}"
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
source_arg
|
|
381
|
+
else
|
|
382
|
+
# Version code; look for existing draft first
|
|
383
|
+
existing = find_existing_drafts(source_arg)
|
|
384
|
+
if existing && existing[:format] == :yml
|
|
385
|
+
existing[:path]
|
|
386
|
+
else
|
|
387
|
+
# Use the same path resolution logic as draft generation
|
|
388
|
+
resolve_draft_path(:yaml, source_arg)
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# IMPORTANT: This method is currently not used
|
|
394
|
+
def check_for_existing_draft version
|
|
395
|
+
# Implementation pending the draft handling refactor
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def determine_operations source_arg
|
|
399
|
+
operation_mode = determine_mode(source_arg)
|
|
400
|
+
|
|
401
|
+
case operation_mode
|
|
402
|
+
when :draft_only
|
|
403
|
+
ReleaseHx.logger.info('Generating draft only.')
|
|
404
|
+
generate_draft(source_arg)
|
|
405
|
+
when :enrich_only
|
|
406
|
+
ReleaseHx.logger.info('Rendering document/s only.')
|
|
407
|
+
enrich_docs(source_arg, source_arg)
|
|
408
|
+
when :draft_and_enrich
|
|
409
|
+
ReleaseHx.logger.info('Generating draft and enriched docs.')
|
|
410
|
+
generate_draft(source_arg)
|
|
411
|
+
enrich_docs(source_arg, source_arg)
|
|
412
|
+
else
|
|
413
|
+
raise Thor::Error, 'ERROR: Could not determine valid operation mode.'
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def determine_mode _source_arg
|
|
418
|
+
return :enrich_only if enrich_requested? && !draft_requested?
|
|
419
|
+
if options[:fetch] && !draft_requested? && !enrich_requested?
|
|
420
|
+
raise Thor::Error, 'ERROR: You must specify a draft or enrich format when using --fetch.'
|
|
421
|
+
end
|
|
422
|
+
return :draft_and_enrich if options[:fetch] && enrich_requested?
|
|
423
|
+
return :draft_and_enrich if draft_requested? && enrich_requested?
|
|
424
|
+
return :draft_only if options[:fetch] || draft_requested?
|
|
425
|
+
|
|
426
|
+
raise Thor::Error, 'ERROR: You must specify a draft or enrich format.'
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Returns true if any draft format flags exist
|
|
430
|
+
def draft_requested?
|
|
431
|
+
!options[:md].nil? || !options[:adoc].nil? || !options[:yaml].nil?
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Returns true if any enrich format flags exist
|
|
435
|
+
def enrich_requested?
|
|
436
|
+
!options[:html].nil? || !options[:pdf].nil?
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def determine_input_type version_arg
|
|
440
|
+
return { type: :file, format: file_format(version_arg), path: version_arg } if File.file?(version_arg)
|
|
441
|
+
|
|
442
|
+
# It's a version number. Try resolving a usable draft.
|
|
443
|
+
existing = find_existing_drafts(version_arg)
|
|
444
|
+
return { type: :draft, format: existing[:format], path: existing[:path] } if existing
|
|
445
|
+
|
|
446
|
+
{ type: :api, format: :api, path: version_arg }
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def basename_for_output source_arg
|
|
450
|
+
if version_or_file(source_arg) == :file
|
|
451
|
+
File.basename(source_arg, File.extname(source_arg))
|
|
452
|
+
else
|
|
453
|
+
source_arg
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def find_existing_drafts version
|
|
458
|
+
base = File.join(@settings['paths']['drafts_dir'], version)
|
|
459
|
+
|
|
460
|
+
%w[adoc md yml].each do |ext|
|
|
461
|
+
path = "#{base}.#{ext}"
|
|
462
|
+
return { format: ext.to_sym, path: path } if File.exist?(path)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
nil
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def generate_draft source_arg
|
|
469
|
+
version = basename_for_output(source_arg) # used only for filenames and template context
|
|
470
|
+
release = create_rhyml_from_source(source_arg, version)
|
|
471
|
+
|
|
472
|
+
fmt = draft_format_requested
|
|
473
|
+
outpath = resolve_draft_path(fmt, version)
|
|
474
|
+
|
|
475
|
+
if File.exist?(outpath) && !options[:over]
|
|
476
|
+
ReleaseHx.logger.warn "File exists: #{outpath}. Use --force to overwrite."
|
|
477
|
+
return
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
ReleaseHx::DraftOps.draft_output(
|
|
481
|
+
release: release,
|
|
482
|
+
config: @settings,
|
|
483
|
+
format: fmt,
|
|
484
|
+
outpath: outpath)
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def resolve_draft_path flag, version
|
|
488
|
+
user_path = options[flag]
|
|
489
|
+
if user_path && !user_path.empty?
|
|
490
|
+
ReleaseHx.logger.info "✓ Custom output path for #{flag}: #{user_path}" if options[:verbose]
|
|
491
|
+
return user_path
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
format = case flag
|
|
495
|
+
when :yaml then :yaml
|
|
496
|
+
when :md then :md
|
|
497
|
+
when :adoc then :adoc
|
|
498
|
+
else flag
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
ext = ReleaseHx.format_extension(format, @settings)
|
|
502
|
+
|
|
503
|
+
template_obj = @settings.dig('paths', 'draft_filename')
|
|
504
|
+
filename = Sourcerer::Templating::Engines.render(
|
|
505
|
+
template_obj, 'liquid',
|
|
506
|
+
{ 'version' => version, 'format_ext' => ext })
|
|
507
|
+
|
|
508
|
+
# Get output_dir and drafts_dir, making drafts_dir relative to output_dir
|
|
509
|
+
output_dir = @settings.dig('paths', 'output_dir')
|
|
510
|
+
drafts_dir = @settings.dig('paths', 'drafts_dir')
|
|
511
|
+
|
|
512
|
+
# Construct full path: output_dir/drafts_dir/filename
|
|
513
|
+
File.join(output_dir, drafts_dir, filename.strip)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def draft_format_requested
|
|
517
|
+
return :yaml if options[:yaml]
|
|
518
|
+
return :md if options[:md]
|
|
519
|
+
return :adoc if options[:adoc]
|
|
520
|
+
|
|
521
|
+
nil
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def derive_release_code arg
|
|
525
|
+
# If it's already a version code (no path separator or file extension), return as-is
|
|
526
|
+
return arg unless arg.include?('/') || arg.end_with?('.yaml', '.yml', '.json', '.adoc', '.md')
|
|
527
|
+
|
|
528
|
+
# Otherwise, it's a file path or filename; extract basename without extension
|
|
529
|
+
File.basename(arg, File.extname(arg))
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def load_mapping
|
|
533
|
+
if options[:mapping]
|
|
534
|
+
path = options[:mapping]
|
|
535
|
+
raise Thor::Error, "Mapping file not found: #{path}" unless File.exist?(path)
|
|
536
|
+
|
|
537
|
+
ReleaseHx.logger.info "✓ Using custom mapping file: #{path}" if options[:verbose]
|
|
538
|
+
SchemaGraphy::Loader.load_yaml_with_tags(path)
|
|
539
|
+
else
|
|
540
|
+
origin_source = @settings.dig('origin', 'source') || 'default'
|
|
541
|
+
local_dir = @settings.dig('paths', 'mappings_dir') || '_mappings'
|
|
542
|
+
|
|
543
|
+
# Try both .yaml and .yml extensions for local mappings
|
|
544
|
+
local_paths = [
|
|
545
|
+
File.join(local_dir, "#{origin_source}.yaml"),
|
|
546
|
+
File.join(local_dir, "#{origin_source}.yml")
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
local_paths.each do |local_path|
|
|
550
|
+
return SchemaGraphy::Loader.load_yaml_with_tags(local_path) if File.exist?(local_path)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# Fallback to built-in mapping shipped with gem
|
|
554
|
+
gem_root = File.expand_path('../..', __dir__) # adjust if needed
|
|
555
|
+
built_in_paths = [
|
|
556
|
+
File.join(gem_root, 'lib/releasehx/rhyml/mappings', "#{origin_source}.yaml"),
|
|
557
|
+
File.join(gem_root, 'lib/releasehx/rhyml/mappings', "#{origin_source}.yml")
|
|
558
|
+
]
|
|
559
|
+
|
|
560
|
+
built_in_paths.each do |built_in_path|
|
|
561
|
+
if File.exist?(built_in_path)
|
|
562
|
+
ReleaseHx.logger.debug "Found mapping at: #{built_in_path}"
|
|
563
|
+
return SchemaGraphy::Loader.load_yaml_with_tags(built_in_path)
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
# Better error message showing what was tried
|
|
568
|
+
tried_paths = local_paths + built_in_paths
|
|
569
|
+
raise Thor::Error, <<~ERRMSG
|
|
570
|
+
No mapping file found for source '#{origin_source}'.
|
|
571
|
+
|
|
572
|
+
Searched in these locations:
|
|
573
|
+
#{tried_paths.map { |p| " - #{p}" }.join("\n")}
|
|
574
|
+
|
|
575
|
+
Solutions:
|
|
576
|
+
1. Create a mapping file in #{local_dir}/#{origin_source}.yaml
|
|
577
|
+
2. Use --mapping to specify a custom mapping file path
|
|
578
|
+
3. Check that config.origin.source is set correctly (currently: '#{origin_source}')
|
|
579
|
+
ERRMSG
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def determine_payload_type identifier
|
|
584
|
+
return :yaml if identifier.end_with?('.yml', '.yaml')
|
|
585
|
+
return :json if options[:api_data]
|
|
586
|
+
|
|
587
|
+
:api
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def render_template template_path, context
|
|
591
|
+
engine = Tilt.new(template_path)
|
|
592
|
+
engine.render(Object.new, context)
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
def fetch_or_load_issues identifier, source_type
|
|
596
|
+
if source_type == :yaml
|
|
597
|
+
load_yaml_issues(identifier)
|
|
598
|
+
elsif source_type == :json
|
|
599
|
+
load_json_issues(options[:api_data])
|
|
600
|
+
else
|
|
601
|
+
fetch_issues_from_api(identifier)
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def generate_requested_outputs version, issues, source_type
|
|
606
|
+
if options[:yaml]
|
|
607
|
+
output_yaml(version, issues)
|
|
608
|
+
elsif options[:md]
|
|
609
|
+
output_markdown(version, issues, source_type)
|
|
610
|
+
elsif options[:adoc]
|
|
611
|
+
output_asciidoc(version, issues, source_type)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
enrich_docs(source_arg, version)
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def enrich_docs source_path, version
|
|
618
|
+
input_info = determine_input_type(source_path)
|
|
619
|
+
|
|
620
|
+
case input_info[:type]
|
|
621
|
+
when :file, :draft
|
|
622
|
+
enrich_from_draft_file(input_info[:path], version)
|
|
623
|
+
when :api
|
|
624
|
+
enrich_direct_from_source(source_path, version)
|
|
625
|
+
else
|
|
626
|
+
ReleaseHx.logger.error('Unable to determine input type for enrichment.')
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def enrich_from_draft_file file_path, version
|
|
631
|
+
unless File.exist?(file_path)
|
|
632
|
+
ReleaseHx.logger.warn("Draft file not found: #{file_path}")
|
|
633
|
+
return
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
apply_enrich_modes_to_config
|
|
637
|
+
|
|
638
|
+
if options[:html]
|
|
639
|
+
html_out = resolve_enrich_path(:html, version)
|
|
640
|
+
ReleaseHx::EnrichOps.enrich_from_file(
|
|
641
|
+
file_path,
|
|
642
|
+
format: :html,
|
|
643
|
+
config: @settings,
|
|
644
|
+
outpath: html_out,
|
|
645
|
+
force: options[:over])
|
|
646
|
+
ReleaseHx.logger.info("HTML processed: #{html_out}")
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
return unless options[:pdf]
|
|
650
|
+
|
|
651
|
+
pdf_out = resolve_enrich_path(:pdf, version)
|
|
652
|
+
ReleaseHx::EnrichOps.enrich_from_file(
|
|
653
|
+
file_path,
|
|
654
|
+
format: :pdf,
|
|
655
|
+
config: @settings,
|
|
656
|
+
outpath: pdf_out,
|
|
657
|
+
force: options[:over])
|
|
658
|
+
ReleaseHx.logger.info("PDF processed: #{pdf_out}")
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
# Take from API/JSON source to RHYML object, then enrich
|
|
662
|
+
def enrich_direct_from_source source_path, version
|
|
663
|
+
unless options[:fetch]
|
|
664
|
+
ReleaseHx.logger.warn("No draft found for enriching version #{version}. Use --fetch to generate from source.")
|
|
665
|
+
return
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
ReleaseHx.logger.info('Creating RHYML object from source for direct enrichment.')
|
|
669
|
+
release = create_rhyml_from_source(source_path, version)
|
|
670
|
+
|
|
671
|
+
apply_enrich_modes_to_config
|
|
672
|
+
|
|
673
|
+
if options[:html]
|
|
674
|
+
html_out = resolve_enrich_path(:html, version)
|
|
675
|
+
ReleaseHx::EnrichOps.enrich_from_rhyml(
|
|
676
|
+
release: release, format: :html, outpath: html_out, config: @settings,
|
|
677
|
+
force: options[:over])
|
|
678
|
+
ReleaseHx.logger.info("HTML processed: #{html_out}")
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
return unless options[:pdf]
|
|
682
|
+
|
|
683
|
+
pdf_out = resolve_enrich_path(:pdf, version)
|
|
684
|
+
ReleaseHx::EnrichOps.enrich_from_rhyml(
|
|
685
|
+
release: release, format: :pdf, outpath: pdf_out, config: @settings,
|
|
686
|
+
force: options[:over])
|
|
687
|
+
ReleaseHx.logger.info("PDF processed: #{pdf_out}")
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def create_rhyml_from_source source_path, version
|
|
691
|
+
# Determine source type from configuration, not just file extension
|
|
692
|
+
configured_source_type = @settings.dig('origin', 'source') || 'json'
|
|
693
|
+
|
|
694
|
+
# Handle different source types based on configuration
|
|
695
|
+
case configured_source_type
|
|
696
|
+
when 'rhyml'
|
|
697
|
+
# Load RHYML data directly from YAML file
|
|
698
|
+
rhyml_data = SchemaGraphy::Loader.load_yaml_with_tags(source_path)
|
|
699
|
+
release_data = rhyml_data['releases'] ? rhyml_data['releases'].first : rhyml_data
|
|
700
|
+
|
|
701
|
+
# Convert hash keys to keyword arguments for Release constructor
|
|
702
|
+
ReleaseHx::RHYML::Release.new(
|
|
703
|
+
code: release_data['code'] || version,
|
|
704
|
+
date: release_data['date'],
|
|
705
|
+
hash: release_data['hash'],
|
|
706
|
+
memo: release_data['memo'],
|
|
707
|
+
changes: release_data['changes'] || [])
|
|
708
|
+
when 'json'
|
|
709
|
+
# For json type, only use local files (never API calls)
|
|
710
|
+
if options[:api_data]
|
|
711
|
+
issues = load_json_issues(options[:api_data])
|
|
712
|
+
else
|
|
713
|
+
raise Thor::Error,
|
|
714
|
+
"ERROR: origin.source is 'json' but no --api-data file provided. " \
|
|
715
|
+
'Use --api-data PATH to specify a JSON file.'
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
ReleaseHx::DraftOps.from_payload(
|
|
719
|
+
payload: issues,
|
|
720
|
+
config: @settings,
|
|
721
|
+
mapping: load_mapping,
|
|
722
|
+
release_code: version)
|
|
723
|
+
when 'jira', 'github', 'gitlab'
|
|
724
|
+
# For API sources, use cached data if provided, otherwise fetch from API
|
|
725
|
+
issues = if options[:api_data]
|
|
726
|
+
load_json_issues(options[:api_data])
|
|
727
|
+
else
|
|
728
|
+
fetch_issues_from_api(version)
|
|
729
|
+
end
|
|
730
|
+
ReleaseHx::DraftOps.from_payload(
|
|
731
|
+
payload: issues,
|
|
732
|
+
config: @settings,
|
|
733
|
+
mapping: load_mapping,
|
|
734
|
+
release_code: version)
|
|
735
|
+
else
|
|
736
|
+
raise Thor::Error,
|
|
737
|
+
"ERROR: Unsupported source type '#{configured_source_type}'. " \
|
|
738
|
+
'Must be one of: json, rhyml, jira, github, gitlab'
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
def apply_enrich_modes_to_config
|
|
743
|
+
# Apply CLI flags to config for template enrichment
|
|
744
|
+
@settings['modes'] ||= {}
|
|
745
|
+
|
|
746
|
+
unless options[:wrap].nil?
|
|
747
|
+
@settings['modes']['wrapped'] = options[:wrap]
|
|
748
|
+
ReleaseHx.logger.info "✓ Changed HTML wrapping to: #{options[:wrap]}" if options[:verbose]
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
return if options[:frontmatter].nil?
|
|
752
|
+
|
|
753
|
+
@settings['modes']['html_frontmatter'] = options[:frontmatter]
|
|
754
|
+
ReleaseHx.logger.info "✓ Changed frontmatter inclusion to: #{options[:frontmatter]}" if options[:verbose]
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
def resolve_enrich_path flag, version
|
|
758
|
+
# Check if user provided a custom path
|
|
759
|
+
user_path = options[flag]
|
|
760
|
+
if user_path && !user_path.empty?
|
|
761
|
+
ReleaseHx.logger.info "✓ Custom output path for #{flag}: #{user_path}" if options[:verbose]
|
|
762
|
+
return user_path
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
ext = case flag
|
|
766
|
+
when :pdf then 'pdf'
|
|
767
|
+
when :html then 'html'
|
|
768
|
+
else raise ArgumentError, "Unknown enrich format: #{flag}"
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
template_obj = @settings.dig('paths', 'enrich_filename')
|
|
772
|
+
context = { 'version' => version, 'format_ext' => ext }
|
|
773
|
+
|
|
774
|
+
# Use the existing templating system to render the filename
|
|
775
|
+
filename = if template_obj.respond_to?(:render)
|
|
776
|
+
template_obj.render(context)
|
|
777
|
+
elsif template_obj.is_a?(String)
|
|
778
|
+
# It's a plain string, compile and render with Liquid
|
|
779
|
+
compiled = Liquid::Template.parse(template_obj)
|
|
780
|
+
compiled.render(context)
|
|
781
|
+
else
|
|
782
|
+
template_obj.to_s
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
output_dir = @settings.dig('paths', 'output_dir')
|
|
786
|
+
enrich_dir = @settings.dig('paths', 'enrich_dir')
|
|
787
|
+
|
|
788
|
+
File.join(output_dir, enrich_dir, filename.strip)
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
# IMPORTANT: This method is currently not used (identified by debride)
|
|
792
|
+
# Similar to find_existing_drafts but with simpler path handling
|
|
793
|
+
# Will be replaced as part of the draft handling refactor
|
|
794
|
+
def find_existing_draft version
|
|
795
|
+
["#{version}.adoc", "#{version}.md", "#{version}.yml"].find { |f| File.exist?(f) }
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
def output_yaml path, _issues
|
|
799
|
+
yaml_content = "---\n#YAML Draft" # placeholder
|
|
800
|
+
safe_write(path, yaml_content)
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
def output_markdown path, _issues, _source_type
|
|
804
|
+
markdown_content = "# Markdown Draft\n\n" # placeholder
|
|
805
|
+
safe_write(path, markdown_content)
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
def output_asciidoc path, _issues, _source_type
|
|
809
|
+
content = "= AsciiDoc Draft\n\n" # placeholder
|
|
810
|
+
safe_write(path, content)
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
def safe_write filename, content
|
|
814
|
+
ReleaseHx.logger.debug("Attempting to write file #{filename}")
|
|
815
|
+
if File.exist?(filename) && !options[:over]
|
|
816
|
+
ReleaseHx.logger.warn("#{filename} already exists. Use --force to overwrite.")
|
|
817
|
+
else
|
|
818
|
+
File.write(filename, content)
|
|
819
|
+
ReleaseHx.logger.info("Draft written: #{filename}")
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
def load_yaml_issues path
|
|
824
|
+
ReleaseHx.logger.info("Loading YAML issues from #{path}")
|
|
825
|
+
SchemaGraphy::Loader.load_yaml_with_tags(path)
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
def load_json_issues path
|
|
829
|
+
ReleaseHx.logger.info("Loading JSON issues from #{path}")
|
|
830
|
+
payload = JSON.parse(File.read(path))
|
|
831
|
+
|
|
832
|
+
# Extract issues array if client config specifies root_issues_path: "issues"
|
|
833
|
+
# This matches what the API client does when fetching live data
|
|
834
|
+
origin_source = @settings.dig('origin', 'source')
|
|
835
|
+
if origin_source && payload.is_a?(Hash) && payload.key?('issues')
|
|
836
|
+
client_path = find_api_client_config(origin_source)
|
|
837
|
+
if client_path
|
|
838
|
+
client_def = SchemaGraphy::Loader.load_yaml_with_tags(client_path)
|
|
839
|
+
if client_def['root_issues_path'] == 'issues'
|
|
840
|
+
ReleaseHx.logger.debug "Extracting 'issues' array from payload"
|
|
841
|
+
return payload['issues']
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
payload
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
def find_api_client_config origin_source
|
|
850
|
+
local_dir = @settings.dig('paths', 'api_clients_dir') || '_apis'
|
|
851
|
+
local_paths = [
|
|
852
|
+
File.join(local_dir, "#{origin_source}.yaml"),
|
|
853
|
+
File.join(local_dir, "#{origin_source}.yml")
|
|
854
|
+
]
|
|
855
|
+
|
|
856
|
+
local_paths.each do |local_path|
|
|
857
|
+
return local_path if File.exist?(local_path)
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
# Check built-in clients
|
|
861
|
+
gem_root = File.expand_path('../..', __dir__)
|
|
862
|
+
builtin_paths = [
|
|
863
|
+
File.join(gem_root, 'lib/releasehx/rest/clients', "#{origin_source}.yaml"),
|
|
864
|
+
File.join(gem_root, 'lib/releasehx/rest/clients', "#{origin_source}.yml")
|
|
865
|
+
]
|
|
866
|
+
|
|
867
|
+
builtin_paths.each do |builtin_path|
|
|
868
|
+
return builtin_path if File.exist?(builtin_path)
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
nil
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
def fetch_issues_from_api version
|
|
875
|
+
ReleaseHx.logger.info("Fetching issues for version #{version} from API using config")
|
|
876
|
+
|
|
877
|
+
client = ReleaseHx::REST::YamlClient.new(@settings, version)
|
|
878
|
+
results = client.fetch_all
|
|
879
|
+
|
|
880
|
+
if options[:payload]
|
|
881
|
+
ReleaseHx.logger.info '✓ Will save API payload to file' if options[:verbose]
|
|
882
|
+
save_api_payload(results, options[:payload])
|
|
883
|
+
else
|
|
884
|
+
ReleaseHx.logger.info("Fetched #{results.size} issues from API for version #{version}")
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
results
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
def save_api_payload results, custom_path = nil
|
|
891
|
+
if custom_path && !custom_path.empty?
|
|
892
|
+
payload_file = custom_path
|
|
893
|
+
payload_dir = File.dirname(payload_file)
|
|
894
|
+
FileUtils.mkdir_p(payload_dir)
|
|
895
|
+
else # Use default path when --payload used without value
|
|
896
|
+
payload_dir = @settings.dig('paths', 'payloads_dir') || 'payloads'
|
|
897
|
+
FileUtils.mkdir_p(payload_dir)
|
|
898
|
+
origin_source_name = @settings.dig('origin', 'source') || 'unknown'
|
|
899
|
+
payload_file = File.join(payload_dir, "#{origin_source_name}.json")
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
File.write(payload_file, JSON.pretty_generate(results))
|
|
903
|
+
ReleaseHx.logger.info("API payload saved to #{payload_file}")
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
def file_format file_path
|
|
907
|
+
ext = File.extname(file_path).downcase
|
|
908
|
+
case ext
|
|
909
|
+
when '.yml', '.yaml' then :yaml
|
|
910
|
+
when '.md' then :markdown
|
|
911
|
+
when '.adoc' then :asciidoc
|
|
912
|
+
else :unknown
|
|
913
|
+
end
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
def merge_cli_flags_into_settings
|
|
917
|
+
@settings['cli_flags'] = {
|
|
918
|
+
'force' => options[:over],
|
|
919
|
+
'fetch' => options[:fetch],
|
|
920
|
+
'verbose' => options[:verbose],
|
|
921
|
+
'debug' => options[:debug],
|
|
922
|
+
'internal' => options[:internal],
|
|
923
|
+
'wrap' => options[:wrap],
|
|
924
|
+
'frontmatter' => options[:frontmatter],
|
|
925
|
+
'empty_notes' => options[:emptynotes]
|
|
926
|
+
}.compact
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
def apply_cli_overrides
|
|
930
|
+
return unless options[:output_dir] && !options[:output_dir].empty?
|
|
931
|
+
|
|
932
|
+
@settings['paths'] ||= {}
|
|
933
|
+
@settings['paths']['output_dir'] = options[:output_dir]
|
|
934
|
+
ReleaseHx.logger.info "✓ Changed output directory to: #{options[:output_dir]}"
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
end
|