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,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReleaseHx
|
|
4
|
+
module RHYML
|
|
5
|
+
# Represents a single Change within a Release, such as a bug fix, feature, or enhancement.
|
|
6
|
+
#
|
|
7
|
+
# The Change class encapsulates all metadata associated with an individual modification,
|
|
8
|
+
# including its classification (type), descriptive content (summary, notes),
|
|
9
|
+
# organizational data (tags, parts), and contributor information.
|
|
10
|
+
# Changes are always associated with a parent Release object.
|
|
11
|
+
class Change
|
|
12
|
+
attr_accessor :release, :version
|
|
13
|
+
attr_reader :vrsn, :chid, :tick, :hash, :type, :parts, :summ, :head, :note, :tags, :lead, :auths, :links
|
|
14
|
+
|
|
15
|
+
# Initializes a new Change object from attribute hash and parent Release.
|
|
16
|
+
#
|
|
17
|
+
# Processes the provided attributes to populate Change properties, handling
|
|
18
|
+
# multiple possible field names (e.g., 'summ', 'summary', 'title') and
|
|
19
|
+
# normalizing complex attributes like authors and links.
|
|
20
|
+
#
|
|
21
|
+
# @param attrs [Hash] A hash of attributes for the Change.
|
|
22
|
+
# @param release [Release] The required parent Release object.
|
|
23
|
+
# @raise [ArgumentError] If attrs is not a Hash or if both 'part' and 'parts' are provided.
|
|
24
|
+
def initialize attrs = {}, release:
|
|
25
|
+
raise ArgumentError, 'attrs must be a Hash' unless attrs.is_a? Hash
|
|
26
|
+
|
|
27
|
+
@release = release
|
|
28
|
+
@vrsn = @release.code
|
|
29
|
+
@chid = attrs['chid']
|
|
30
|
+
@tick = attrs_value(attrs, %w[tick ticketid])
|
|
31
|
+
@hash = attrs['hash']
|
|
32
|
+
@type = attrs['type']
|
|
33
|
+
@summ = attrs_value(attrs, %w[summ summary title])
|
|
34
|
+
@head = attrs['head']
|
|
35
|
+
@note = attrs['note']
|
|
36
|
+
@tags = attrs['tags'] || []
|
|
37
|
+
@lead = attrs_value(attrs, %w[lead contributor auth])
|
|
38
|
+
@auths = normalize_auths(attrs['auths'])
|
|
39
|
+
@links = normalize_links(attrs['links'])
|
|
40
|
+
|
|
41
|
+
# Handle 'part' vs 'parts'; mutually exclusive attributes
|
|
42
|
+
part = attrs['part']
|
|
43
|
+
parts = attrs['parts']
|
|
44
|
+
raise ArgumentError, "Change cannot have both 'part' and 'parts'" if part && parts
|
|
45
|
+
|
|
46
|
+
@parts = if parts
|
|
47
|
+
Array(parts).map(&:to_s)
|
|
48
|
+
elsif part
|
|
49
|
+
[part.to_s]
|
|
50
|
+
else
|
|
51
|
+
[]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
ReleaseHx.logger.debug "Initialized Change: #{@tick} – #{@summ}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Produces a comprehensive hash representation of the Change.
|
|
58
|
+
#
|
|
59
|
+
# Includes all public attributes plus computed boolean properties
|
|
60
|
+
# for common Change classifications (highlight, breaking, etc.).
|
|
61
|
+
#
|
|
62
|
+
# @return [Hash] A hash containing all public attributes of the Change.
|
|
63
|
+
def to_h
|
|
64
|
+
{
|
|
65
|
+
'vrsn' => vrsn,
|
|
66
|
+
'chid' => chid,
|
|
67
|
+
'tick' => tick,
|
|
68
|
+
'hash' => hash,
|
|
69
|
+
'type' => type,
|
|
70
|
+
'parts' => parts,
|
|
71
|
+
'summ' => summ,
|
|
72
|
+
'head' => head,
|
|
73
|
+
'note' => note,
|
|
74
|
+
'tags' => tags,
|
|
75
|
+
'lead' => lead,
|
|
76
|
+
'auths' => auths,
|
|
77
|
+
'links' => links,
|
|
78
|
+
'deprecation' => deprecation?,
|
|
79
|
+
'removal' => removal?,
|
|
80
|
+
'highlight' => highlight?,
|
|
81
|
+
'breaking' => breaking?,
|
|
82
|
+
'experimental' => experimental?
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [Boolean] True if the Change is tagged as a highlight.
|
|
87
|
+
def highlight? = tags.include?('highlight')
|
|
88
|
+
# @return [Boolean] True if the Change is a breaking change.
|
|
89
|
+
def breaking? = tags.include?('breaking')
|
|
90
|
+
# @return [Boolean] True if the Change is experimental.
|
|
91
|
+
def experimental? = tags.include?('experimental')
|
|
92
|
+
# @return [Boolean] True if the Change includes a deprecation.
|
|
93
|
+
def deprecation? = tags.include?('deprecation')
|
|
94
|
+
# @return [Boolean] True if the Change includes a removal.
|
|
95
|
+
def removal? = tags.include?('removal')
|
|
96
|
+
|
|
97
|
+
# Checks if a given tag is associated with the Change.
|
|
98
|
+
#
|
|
99
|
+
# Performs flexible tag matching, checking for the tag as provided,
|
|
100
|
+
# as a string, and as a symbol to handle different input types.
|
|
101
|
+
#
|
|
102
|
+
# @param tag_name [String, Symbol] The name of the tag to check.
|
|
103
|
+
# @return [Boolean] True if the tag exists in any form.
|
|
104
|
+
def tag? tag_name
|
|
105
|
+
tags.include?(tag_name) || tags.include?(tag_name.to_s) || tags.include?(tag_name.to_sym)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# Retrieves the first available attribute value from a prioritized list of keys.
|
|
111
|
+
#
|
|
112
|
+
# Used for handling multiple possible field names in source data
|
|
113
|
+
# (example: 'tick' or 'ticketid', 'summ' or 'summary' or 'title').
|
|
114
|
+
#
|
|
115
|
+
# @param attrs [Hash] The attributes hash to search.
|
|
116
|
+
# @param keys [Array<String>] Ordered list of keys to try.
|
|
117
|
+
# @return [Object, nil] The first found value or nil if none exist.
|
|
118
|
+
def attrs_value attrs, keys
|
|
119
|
+
keys.find { |key| return attrs[key] if attrs.key?(key) }
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Normalizes the 'auths' attribute to ensure consistent structure.
|
|
124
|
+
#
|
|
125
|
+
# Converts various input formats (strings, hashes, arrays) into a
|
|
126
|
+
# standardized array of hashes with 'user' and optional 'memo' keys.
|
|
127
|
+
#
|
|
128
|
+
# @param val [String, Hash, Array, nil] The authors data to normalize.
|
|
129
|
+
# @return [Array<Hash>] Normalized array of author hashes.
|
|
130
|
+
def normalize_auths val
|
|
131
|
+
return [] if val.nil?
|
|
132
|
+
|
|
133
|
+
Array(val).map do |a|
|
|
134
|
+
if a.is_a?(String)
|
|
135
|
+
{ 'user' => a }
|
|
136
|
+
elsif a.is_a?(Hash)
|
|
137
|
+
{
|
|
138
|
+
'user' => a['user'] || a[:user],
|
|
139
|
+
'memo' => a['memo'] || a[:memo]
|
|
140
|
+
}.compact
|
|
141
|
+
else
|
|
142
|
+
{ 'user' => a.to_s }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Normalizes the 'links' attribute to ensure consistent structure.
|
|
148
|
+
#
|
|
149
|
+
# Ensures all links have standardized 'text', 'xref', and 'href' keys,
|
|
150
|
+
# converting symbol keys to string keys and filtering out nil values.
|
|
151
|
+
#
|
|
152
|
+
# @param val [Array<Hash>, nil] The links data to normalize.
|
|
153
|
+
# @return [Array<Hash>] Normalized array of link hashes.
|
|
154
|
+
def normalize_links val
|
|
155
|
+
return [] if val.nil?
|
|
156
|
+
|
|
157
|
+
val.map do |l|
|
|
158
|
+
{
|
|
159
|
+
'text' => l['text'] || l[:text],
|
|
160
|
+
'xref' => l['xref'] || l[:xref],
|
|
161
|
+
'href' => l['href'] || l[:href]
|
|
162
|
+
}.compact
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReleaseHx
|
|
4
|
+
module RHYML
|
|
5
|
+
class Loader
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require 'json'
|
|
8
|
+
|
|
9
|
+
def self.load_file path
|
|
10
|
+
ext = File.extname(path)
|
|
11
|
+
case ext
|
|
12
|
+
when '.yml', '.yaml'
|
|
13
|
+
SchemaGraphy::Loader.load_yaml_with_tags(path)
|
|
14
|
+
when '.json'
|
|
15
|
+
JSON.parse(File.read(path))
|
|
16
|
+
else
|
|
17
|
+
raise "Unsupported format: #{ext}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class MappingLoader < Loader
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class ReleaseLoader < Loader
|
|
26
|
+
def self.load path
|
|
27
|
+
hash = load_file(path)
|
|
28
|
+
Release.new(
|
|
29
|
+
code: hash['code'],
|
|
30
|
+
date: hash['date'],
|
|
31
|
+
hash: hash['hash'],
|
|
32
|
+
memo: hash['memo'],
|
|
33
|
+
changes: hash['changes'] || hash['work'] || [])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# GitHub Issues API to RHYML mapping configuration
|
|
2
|
+
# Maps GitHub Issues API response to RHYML Change entries
|
|
3
|
+
|
|
4
|
+
$config:
|
|
5
|
+
desc: "GitHub Issues API to RHYML mapping"
|
|
6
|
+
path_lang: jmespath
|
|
7
|
+
tplt_lang: liquid
|
|
8
|
+
|
|
9
|
+
# GitHub Issues API returns an array of issues directly
|
|
10
|
+
# But some payloads may have an {issues: [...]} wrapper
|
|
11
|
+
changes_array_path: "issues || @"
|
|
12
|
+
|
|
13
|
+
# Map each issue to RHYML Change properties
|
|
14
|
+
tick:
|
|
15
|
+
path: "number"
|
|
16
|
+
|
|
17
|
+
# hash:
|
|
18
|
+
# path: $nil # GitHub Issues API doesn't include git commit hashes
|
|
19
|
+
|
|
20
|
+
summ:
|
|
21
|
+
path: "title"
|
|
22
|
+
|
|
23
|
+
note:
|
|
24
|
+
path: "body"
|
|
25
|
+
|
|
26
|
+
# Extract type from the native GitHub Issues type field (modern approach)
|
|
27
|
+
type:
|
|
28
|
+
path: "issue_type.name"
|
|
29
|
+
|
|
30
|
+
# Derive `parts` from labels using direct key matching or slug override
|
|
31
|
+
parts:
|
|
32
|
+
path: "labels[].name"
|
|
33
|
+
ruby: |
|
|
34
|
+
labels = path.is_a?(Array) ? path : [path]
|
|
35
|
+
parts_defs = config['parts'] || {}
|
|
36
|
+
label_prefix = parts_defs['label_prefix'] || ''
|
|
37
|
+
|
|
38
|
+
label_to_part = parts_defs.each_with_object({}) do |(part_key, part_config), memo|
|
|
39
|
+
if part_config.is_a?(Hash)
|
|
40
|
+
slug = part_config['slug']
|
|
41
|
+
memo[part_key.to_s.downcase] = part_key
|
|
42
|
+
memo[slug.to_s.downcase] = part_key if slug
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
found_parts = labels.map do |label|
|
|
47
|
+
l = label.to_s.downcase
|
|
48
|
+
if !label_prefix.to_s.empty? && l.start_with?(label_prefix.to_s.downcase)
|
|
49
|
+
l = l.sub(/^#{Regexp.escape(label_prefix)}/i, '')
|
|
50
|
+
end
|
|
51
|
+
label_to_part[l]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
found_parts.compact.uniq
|
|
55
|
+
|
|
56
|
+
tags:
|
|
57
|
+
path: "labels[].name"
|
|
58
|
+
|
|
59
|
+
lead:
|
|
60
|
+
path: "assignee.login"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# GitLab Issues API to RHYML mapping configuration
|
|
2
|
+
# Maps GitLab Issues API response to RHYML Change entries
|
|
3
|
+
|
|
4
|
+
$config:
|
|
5
|
+
desc: "GitLab Issues API to RHYML mapping"
|
|
6
|
+
path_lang: jmespath
|
|
7
|
+
tplt_lang: liquid
|
|
8
|
+
|
|
9
|
+
# GitLab Issues API returns an array of issues in "issues" key
|
|
10
|
+
changes_array_path: "issues"
|
|
11
|
+
|
|
12
|
+
# Map each issue to RHYML Change properties
|
|
13
|
+
tick:
|
|
14
|
+
path: "iid"
|
|
15
|
+
|
|
16
|
+
summ:
|
|
17
|
+
path: "title"
|
|
18
|
+
|
|
19
|
+
note:
|
|
20
|
+
path: "description"
|
|
21
|
+
|
|
22
|
+
# Derive `type` from labels using direct key matching or slug override
|
|
23
|
+
type:
|
|
24
|
+
path: "labels"
|
|
25
|
+
ruby: |
|
|
26
|
+
labels = path.is_a?(Array) ? path : [path]
|
|
27
|
+
type_defs = config['types'] || {}
|
|
28
|
+
|
|
29
|
+
label_to_type = type_defs.each_with_object({}) do |(type_key, type_config), memo|
|
|
30
|
+
if type_config.is_a?(Hash)
|
|
31
|
+
slug = type_config['slug']
|
|
32
|
+
memo[type_key.to_s.downcase] = type_key
|
|
33
|
+
memo[slug.to_s.downcase] = type_key if slug
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
found_type = labels.find do |label|
|
|
38
|
+
label_to_type[label.to_s.downcase]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
label_to_type[found_type.to_s.downcase] if found_type
|
|
42
|
+
|
|
43
|
+
# Derive `parts` from labels using direct key matching or slug override
|
|
44
|
+
parts:
|
|
45
|
+
path: "labels"
|
|
46
|
+
ruby: |
|
|
47
|
+
labels = path.is_a?(Array) ? path : [path]
|
|
48
|
+
parts_defs = config['parts'] || {}
|
|
49
|
+
label_prefix = parts_defs['label_prefix'] || ''
|
|
50
|
+
|
|
51
|
+
label_to_part = parts_defs.each_with_object({}) do |(part_key, part_config), memo|
|
|
52
|
+
if part_config.is_a?(Hash)
|
|
53
|
+
slug = part_config['slug']
|
|
54
|
+
memo[part_key.to_s.downcase] = part_key
|
|
55
|
+
memo[slug.to_s.downcase] = part_key if slug
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
found_parts = labels.map do |label|
|
|
60
|
+
l = label.to_s.downcase
|
|
61
|
+
if !label_prefix.to_s.empty? && l.start_with?(label_prefix.to_s.downcase)
|
|
62
|
+
l = l.sub(/^#{Regexp.escape(label_prefix)}/i, '')
|
|
63
|
+
end
|
|
64
|
+
label_to_part[l]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
found_parts.compact.uniq
|
|
68
|
+
|
|
69
|
+
tags:
|
|
70
|
+
path: "labels"
|
|
71
|
+
|
|
72
|
+
lead:
|
|
73
|
+
path: "assignees[0].username"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Jira Issues API to RHYML mapping configuration
|
|
2
|
+
$config:
|
|
3
|
+
desc: "Jira Issues API to RHYML mapping"
|
|
4
|
+
path_lang: jmespath
|
|
5
|
+
tplt_lang: liquid
|
|
6
|
+
|
|
7
|
+
changes_array_path: "@"
|
|
8
|
+
|
|
9
|
+
tick:
|
|
10
|
+
path: "key"
|
|
11
|
+
|
|
12
|
+
summ:
|
|
13
|
+
path: "fields.summary"
|
|
14
|
+
|
|
15
|
+
note:
|
|
16
|
+
path: "fields.description"
|
|
17
|
+
|
|
18
|
+
type:
|
|
19
|
+
path: "fields.issuetype.name"
|
|
20
|
+
tplt: '{{ path | downcase }}'
|
|
21
|
+
|
|
22
|
+
parts:
|
|
23
|
+
path: "fields.components[].name"
|
|
24
|
+
|
|
25
|
+
tags:
|
|
26
|
+
path: "fields.labels"
|
|
27
|
+
|
|
28
|
+
lead:
|
|
29
|
+
path: "fields.assignee.displayName"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Verb past tense mappings for the pasterize feature
|
|
2
|
+
# Maps present tense verbs to their past tense equivalents
|
|
3
|
+
add: added
|
|
4
|
+
adds: added
|
|
5
|
+
address: addressed
|
|
6
|
+
addresses: addressed
|
|
7
|
+
change: changed
|
|
8
|
+
changes: changed
|
|
9
|
+
clarify: clarified
|
|
10
|
+
clarifies: clarified
|
|
11
|
+
clean: cleaned
|
|
12
|
+
cleans: cleaned
|
|
13
|
+
configure: configured
|
|
14
|
+
configures: configured
|
|
15
|
+
correct: corrected
|
|
16
|
+
corrects: corrected
|
|
17
|
+
create: created
|
|
18
|
+
creates: created
|
|
19
|
+
deprecate: deprecated
|
|
20
|
+
deprecates: deprecated
|
|
21
|
+
document: documented
|
|
22
|
+
documents: documented
|
|
23
|
+
downgrade: downgraded
|
|
24
|
+
downgrades: downgraded
|
|
25
|
+
enhance: enhanced
|
|
26
|
+
enhances: enhanced
|
|
27
|
+
enable: enabled
|
|
28
|
+
enables: enabled
|
|
29
|
+
evaluate: evaluated
|
|
30
|
+
evaluates: evaluated
|
|
31
|
+
expand: expanded
|
|
32
|
+
expands: expanded
|
|
33
|
+
fix: fixed
|
|
34
|
+
fixes: fixed
|
|
35
|
+
improve: improved
|
|
36
|
+
improves: improved
|
|
37
|
+
handle: handled
|
|
38
|
+
handles: handled
|
|
39
|
+
implement: implemented
|
|
40
|
+
implements: implemented
|
|
41
|
+
initialize: initialized
|
|
42
|
+
initializes: initialized
|
|
43
|
+
integrate: integrated
|
|
44
|
+
integrates: integrated
|
|
45
|
+
investigate: investigated
|
|
46
|
+
investigates: investigated
|
|
47
|
+
launch: launched
|
|
48
|
+
launches: launched
|
|
49
|
+
make: made
|
|
50
|
+
makes: made
|
|
51
|
+
merge: merged
|
|
52
|
+
merges: merged
|
|
53
|
+
migrate: migrated
|
|
54
|
+
migrates: migrated
|
|
55
|
+
optimize: optimized
|
|
56
|
+
optimizes: optimized
|
|
57
|
+
patch: patched
|
|
58
|
+
patches: patched
|
|
59
|
+
refactor: refactored
|
|
60
|
+
refactors: refactored
|
|
61
|
+
refine: refined
|
|
62
|
+
refines: refined
|
|
63
|
+
remove: removed
|
|
64
|
+
removes: removed
|
|
65
|
+
rename: renamed
|
|
66
|
+
renames: renamed
|
|
67
|
+
revert: reverted
|
|
68
|
+
reverts: reverted
|
|
69
|
+
review: reviewed
|
|
70
|
+
reviews: reviewed
|
|
71
|
+
resolve: resolved
|
|
72
|
+
resolves: resolved
|
|
73
|
+
restructure: restructured
|
|
74
|
+
restructures: restructured
|
|
75
|
+
rework: reworked
|
|
76
|
+
reworks: reworked
|
|
77
|
+
secure: secured
|
|
78
|
+
secures: secured
|
|
79
|
+
simplify: simplified
|
|
80
|
+
simplifies: simplified
|
|
81
|
+
speedup: "sped up"
|
|
82
|
+
speedups: "sped up"
|
|
83
|
+
standardize: standardized
|
|
84
|
+
standardizes: standardized
|
|
85
|
+
streamline: streamlined
|
|
86
|
+
streamlines: streamlined
|
|
87
|
+
support: supported
|
|
88
|
+
supports: supported
|
|
89
|
+
test: tested
|
|
90
|
+
tests: tested
|
|
91
|
+
tweak: tweaked
|
|
92
|
+
tweaks: tweaked
|
|
93
|
+
update: updated
|
|
94
|
+
updates: updated
|
|
95
|
+
upgrade: upgraded
|
|
96
|
+
upgrades: upgraded
|
|
97
|
+
verify: verified
|
|
98
|
+
verifies: verified
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReleaseHx
|
|
4
|
+
module RHYML
|
|
5
|
+
# Represents a single versioned release containing metadata and associated changes.
|
|
6
|
+
#
|
|
7
|
+
# The Release class serves as a container for release metadata (version code, date, etc.)
|
|
8
|
+
# and manages a collection of Change objects that comprise the Release content.
|
|
9
|
+
class Release
|
|
10
|
+
attr_reader :code, :date, :hash, :memo, :changes
|
|
11
|
+
|
|
12
|
+
# Initializes a new Release object.
|
|
13
|
+
#
|
|
14
|
+
# @param code [String] The version code for the release (e.g., '1.2.0').
|
|
15
|
+
# @param date [Date, String] The release date.
|
|
16
|
+
# @param hash [String] The Git commit hash associated with the release.
|
|
17
|
+
# @param memo [String] A descriptive memo for the release.
|
|
18
|
+
# @param changes [Array<Change, Hash>] An array of Change objects or Hashes
|
|
19
|
+
# to be converted into Change objects.
|
|
20
|
+
def initialize code:, date: nil, hash: nil, memo: nil, changes: []
|
|
21
|
+
@code = code
|
|
22
|
+
@date = date
|
|
23
|
+
@hash = hash
|
|
24
|
+
@memo = memo
|
|
25
|
+
@changes = Array(changes).map { |ch| init_change(ch) }.compact
|
|
26
|
+
|
|
27
|
+
ReleaseHx.logger.debug 'Release initialized with changes (post-compact):'
|
|
28
|
+
@changes.each_with_index do |ch, i|
|
|
29
|
+
ReleaseHx.logger.debug " changes[#{i}]: #{ch.class}" unless ch.nil?
|
|
30
|
+
end
|
|
31
|
+
raise 'Unexpected nil in changes' if @changes.any?(&:nil?)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Adds a Change object to the Release.
|
|
35
|
+
#
|
|
36
|
+
# @param change [Change] The Change object to add.
|
|
37
|
+
# @return [Array<Change>] The updated array of Changes.
|
|
38
|
+
def add_change change
|
|
39
|
+
attach_release(change)
|
|
40
|
+
@changes << change
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns the number of Changes in the Release.
|
|
44
|
+
#
|
|
45
|
+
# @return [Integer] The count of Changes.
|
|
46
|
+
def change_count
|
|
47
|
+
changes.size
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Retrieves a unique, sorted list of contributor logins for the Release.
|
|
51
|
+
#
|
|
52
|
+
# @return [Array<String>] An array of unique contributor names.
|
|
53
|
+
def contributors
|
|
54
|
+
changes.map(&:lead).compact.uniq
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Calculates a hash with the count of each tag used in the Release.
|
|
58
|
+
#
|
|
59
|
+
# @return [Hash{String => Integer}] A hash where keys are tag names and
|
|
60
|
+
# values are their counts.
|
|
61
|
+
def tag_stats
|
|
62
|
+
changes.compact.flat_map { |c| c.tags || [] }.tally
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Converts the Release metadata to a hash representation.
|
|
66
|
+
#
|
|
67
|
+
# @note This method excludes the Changes array from the output.
|
|
68
|
+
# @return [Hash] A hash containing the Release's metadata fields.
|
|
69
|
+
def to_h
|
|
70
|
+
{
|
|
71
|
+
'code' => code,
|
|
72
|
+
'version' => code, # alias for backward compatibility
|
|
73
|
+
'date' => date,
|
|
74
|
+
'hash' => hash,
|
|
75
|
+
'memo' => memo,
|
|
76
|
+
'tag_stats' => tag_stats,
|
|
77
|
+
'contributors' => contributors
|
|
78
|
+
}.compact
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Initializes a Change object from various input types.
|
|
84
|
+
# Handles both existing Change objects and raw Hash data.
|
|
85
|
+
#
|
|
86
|
+
# @param change [Hash, Change] The item to process into a Change object.
|
|
87
|
+
# @return [Change, nil] A valid Change object or nil if input is invalid.
|
|
88
|
+
def init_change change
|
|
89
|
+
return nil unless change
|
|
90
|
+
|
|
91
|
+
if change.is_a?(Change)
|
|
92
|
+
change.release = self
|
|
93
|
+
change
|
|
94
|
+
elsif change.is_a?(Hash)
|
|
95
|
+
begin
|
|
96
|
+
obj = Change.new(change, release: self)
|
|
97
|
+
obj.release = self
|
|
98
|
+
obj
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
ReleaseHx.logger.warn "Skipping malformed change: #{e.message}"
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
ReleaseHx.logger.warn "Unknown change type: #{ch.class}"
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Associates a Change object with this Release by setting its release property.
|
|
110
|
+
#
|
|
111
|
+
# @param change [Change] The Change to associate with this release.
|
|
112
|
+
# @return [Change] The associated Change object.
|
|
113
|
+
def attach_release change
|
|
114
|
+
change.release = self
|
|
115
|
+
change
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Manages a collection of Release objects for historical tracking.
|
|
120
|
+
#
|
|
121
|
+
# @note This class is currently unused but maintained as part of the core
|
|
122
|
+
# RHYML data model for future functionality like cross-release analytics.
|
|
123
|
+
class History
|
|
124
|
+
attr_reader :releases
|
|
125
|
+
|
|
126
|
+
# Initializes a new, empty History object.
|
|
127
|
+
def initialize
|
|
128
|
+
@releases = []
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Adds a Release to the history.
|
|
132
|
+
#
|
|
133
|
+
# @param release [Release] The Release to add.
|
|
134
|
+
# @return [Release] The Release that was added.
|
|
135
|
+
def add_release release
|
|
136
|
+
raise ArgumentError, 'Release must be a Release object' unless release.is_a? Release
|
|
137
|
+
|
|
138
|
+
@releases << release
|
|
139
|
+
ReleaseHx.logger.debug "Added Release: #{release.code} (#{release.date})"
|
|
140
|
+
release
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'rhyml/adapter'
|
|
4
|
+
require_relative 'rhyml/change'
|
|
5
|
+
require_relative 'rhyml/loaders'
|
|
6
|
+
require_relative 'rhyml/release'
|
|
7
|
+
require_relative 'rhyml/liquid'
|
|
8
|
+
|
|
9
|
+
module ReleaseHx
|
|
10
|
+
# RHYML (Release History YAML) is the core data modeling language for ReleaseHx.
|
|
11
|
+
# This module provides the components for loading, validating, and transforming
|
|
12
|
+
# RHYML data.
|
|
13
|
+
module RHYML
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../schemagraphy/templating'
|
|
4
|
+
|
|
5
|
+
module ReleaseHx
|
|
6
|
+
module SgymlHelpers
|
|
7
|
+
# Precompiles a schema into a set of templates, using the provided data and schema.
|
|
8
|
+
def self.precompile_from_schema! data, schema, base_path = '', scope: {}
|
|
9
|
+
SchemaGraphy::Templating.precompile_from_schema!(data, schema, base_path, scope: scope)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Renders all templated fields in a given Hash if they've previously been parsed
|
|
13
|
+
def self.render_stage_fields! data, stage
|
|
14
|
+
data.each do |key, value|
|
|
15
|
+
next unless value.is_a?(Sourcerer::Templating::TemplatedField)
|
|
16
|
+
|
|
17
|
+
tmpl_context = value.context
|
|
18
|
+
next unless tmpl_context.respond_to?(:stage)
|
|
19
|
+
next unless tmpl_context.stage.to_sym == stage.to_sym
|
|
20
|
+
|
|
21
|
+
data[key] = value.render
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Recursively converts all keys in a Hash or Array to strings,
|
|
26
|
+
# safely handling non-stringifiable objects
|
|
27
|
+
def self.deep_stringify_safe obj
|
|
28
|
+
case obj
|
|
29
|
+
when Hash
|
|
30
|
+
obj.each_with_object({}) do |(k, v), h|
|
|
31
|
+
h[k.to_s] = deep_stringify_safe(v)
|
|
32
|
+
end
|
|
33
|
+
when Array
|
|
34
|
+
obj.map { |v| deep_stringify_safe(v) }
|
|
35
|
+
else
|
|
36
|
+
begin
|
|
37
|
+
obj.to_yaml
|
|
38
|
+
obj
|
|
39
|
+
rescue TypeError
|
|
40
|
+
obj.to_s
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|