diff --git a/.config/docopslab-dev.yml b/.config/docopslab-dev.yml index c606bb8..8b2188c 100644 --- a/.config/docopslab-dev.yml +++ b/.config/docopslab-dev.yml @@ -47,6 +47,10 @@ tools: paths: skip: - build/* + - specs/* + - "**/config-reference.adoc" + - docs/releases.adoc + - docs/release/* - tool: htmlproofer enabled: true # Disabled by default, enable per project diff --git a/.config/releasehx.yml b/.config/releasehx.yml new file mode 100644 index 0000000..e1bdab3 --- /dev/null +++ b/.config/releasehx.yml @@ -0,0 +1,182 @@ +# ReleaseHx configuration for DocOps/releasehx repository +# Self-dogfooding configuration to manage ReleaseHx's own release history + +origin: + source: github + project: DocOps/releasehx + version: 3 + label: GitHub Issues API v3 + format: json + +paths: + drafts_dir: docs/release/drafts + payloads_dir: docs/release/payloads + templates_dir: docs/release/templates + mappings_dir: docs/release/mappings + +conversions: + summ: issue_heading + note: issue_body + note_pattern: | + /^(((#|=)+ (Draft )?Release Note.*?)|())\n+(?(.|)+)/gmi + markup: markdown + +rhyml: + pasterize_summ: true + pasterize_head: true + chid: "{{ release.code | replace: '.', '_' | upcase }}-{{ change.tick }}" + empty_notes: skip + +parts: + label_prefix: "component:" + cli: + text: CLI + head: CLI + icon: terminal + api: + text: API + head: API + icon: code + docs: + text: documentation + head: Documentation + icon: book + mcp: + text: MCP Server + head: MCP Server + icon: plug + templates: + text: templates + head: Templates + icon: file-text-o + rhyml: + text: RHYML + head: RHYML + icon: file-code-o + schemagraphy: + text: SchemaGraphy + head: SchemaGraphy + icon: sitemap + configuration: + text: configuration + head: Configuration + icon: cogs + +types: + label_prefix: "type:" + feature: + slug: feature + text: new feature + head: Added + icon: plus-square-o + bug: + slug: bug + text: bug fix + head: Fixed + icon: bug + improvement: + slug: improvement + text: improvement + head: Improved + icon: wrench + documentation: + slug: documentation + text: documentation + head: Documentation + icon: book + removal: + slug: removal + text: removal + head: Removed + icon: exclamation-triangle + deprecation: + slug: deprecation + text: deprecation + head: Deprecated + icon: exclamation-circle + +tags: + # Include all tags by default for 0.1.2 (no filtering) + # _include: ['highlight', 'changelog', 'breaking', 'deprecation', 'experimental'] + # _exclude: ['internal', 'wontfix', 'duplicate', 'invalid'] + + highlight: + head: Highlights + icon: star + text: Featured changes + + breaking: + head: Breaking Changes + icon: exclamation-triangle + text: Breaking changes + + deprecation: + head: Deprecations + icon: clock-o + text: Deprecated features + + experimental: + head: Experimental Features + icon: flask + text: Experimental features + + # Pass-through tags for labels not explicitly configured + documentation: + slug: documentation + changelog: + slug: changelog + +links: + web: https://github.com/DocOps/releasehx/issues/{{ tick }} + git: https://github.com/DocOps/releasehx/commit/{{ hash }} + usr: https://github.com/{{ vars.lead }} + +modes: + asciidoc_frontmatter: true + markdown_frontmatter: false + remove_excess_lines: 2 + +history: + items: + issue_links: true + git_links: false + metadata_icons: true + +# Changelog section configuration +changelog: + head: What's Changed + text: Summary of all changes in this release. + htag: h2 + spot: 1 + sort: + - breaking:grouping1 + - type:grouping1 + items: + frame: unordered + allow_redundant: false + show_issue_links: true + metadata_labels: before + metadata_icons: before + show_parts_label: true + show_tags_label: false + show_type_label: false + show_lead_label: false + +# Release notes section configuration +notes: + head: Release Notes + text: Detailed descriptions of notable changes. + htag: h2 + spot: 2 + sort: + - type:grouping1 + items: + frame: table-cols-1 + allow_redundant: true + show_issue_links: true + metadata_labels: before + metadata_icons: before + show_parts_label: true + show_tags_label: true + show_type_label: true + show_lead_label: true diff --git a/.gitignore b/.gitignore index 8625b88..24c60d1 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,7 @@ repomix-output.xml # Local development directories dev/ releasehx-demo/ + +# Release history working directories +docs/release/drafts/ +docs/release/payloads/ diff --git a/Gemfile b/Gemfile index ee60260..7ef0445 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,7 @@ end group :documentation do gem 'jekyll' gem 'jekyll-asciidoc' + gem 'jekyll-redirect-from' gem 'just-the-docs' gem 'yard' end diff --git a/Gemfile.lock b/Gemfile.lock index f29368f..f4a85b4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - releasehx (0.1.0) + releasehx (0.1.2) asciidoctor-pdf (~> 2.3) commonmarker (~> 0.23) faraday (~> 2.9) @@ -188,6 +188,8 @@ GEM jekyll (>= 3.0.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) jekyll-sass-converter (3.1.0) sass-embedded (~> 1.75) jekyll-seo-tag (2.8.0) @@ -370,6 +372,7 @@ DEPENDENCIES docopslab-dev jekyll jekyll-asciidoc + jekyll-redirect-from just-the-docs releasehx! rspec (~> 3.0) diff --git a/README.adoc b/README.adoc index 150af28..2afddc3 100644 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,6 @@ :page-layout: default -:page-permalink: /docs +:page-permalink: /docs/ +:page-title: ReleaseHx Docs :page-nav_order: 1 [[releasehx]] = ReleaseHx @@ -15,7 +16,7 @@ :this_prod_vrsn_major: 0 :this_prod_vrsn_minor: 1 :this_prod_vrsn_major-minor: {this_prod_vrsn_major}.{this_prod_vrsn_minor} -:this_prod_vrsn_patch: 1 +:this_prod_vrsn_patch: 2 :this_prod_vrsn: {this_prod_vrsn_major-minor}.{this_prod_vrsn_patch} :next_prod_vrsn: 0.2.0 :tagline: Generate formatted release histories from Jira, GitHub, GitLab, YAML, or JSON sources. @@ -2475,6 +2476,7 @@ Use `PORT=NNNN` environment argument to specify a different port. [.prompt] PORT=4000 bundle exec rake serve +[[docopslab-devtool]] ==== DocOps Lab Devtool (`docopslab-dev`) Special dev Rake tasks and libraries are available via the `docopslab-dev` gem. diff --git a/Rakefile b/Rakefile index 4193efd..4892fce 100644 --- a/Rakefile +++ b/Rakefile @@ -36,6 +36,7 @@ task :prebuild do require_relative 'lib/releasehx/mcp' ReleaseHx::MCP::AssetPackager.new.package! Sourcerer.generate_manpage('docs/manpage.adoc', 'build/docs/releasehx.1') + generate_release_index end desc 'Build and tag multi-arch Docker image for releasehx' @@ -192,6 +193,86 @@ task :serve do puts "Serving docs at http://localhost:#{port}" end +# ReleaseHx self-dogfooding tasks +RHX_CONFIG_PATH = '.config/releasehx.yml' + +namespace :rhx do + def self.rhx_config + unless File.exist?(RHX_CONFIG_PATH) + warn "ERROR: Config file not found: #{RHX_CONFIG_PATH}" + exit 1 + end + + config = YAML.safe_load_file(RHX_CONFIG_PATH, aliases: true) + [RHX_CONFIG_PATH, config] + end + + def self.get_version args + args[:version] || extract_version + end + + def self.run_cmd cmd + puts "Running: #{cmd}" + exit 1 unless system(cmd) + end + + def self.with_rhx args + config_path, config = rhx_config + version = get_version(args) + yield(config_path, config, version) + end + + desc 'Draft a YAML release-history document for the current version' + task :draft, [:version] do |_t, args| + with_rhx(args) do |config_path, _config, version| + puts "Fetching issues for version #{version} from GitHub..." + cmd = "bundle exec bin/rhx #{version} --config #{config_path} --fetch --yaml" + run_cmd(cmd) + puts "✓ Successfully fetched and generated draft for version #{version}" + end + end + + desc 'Append new issues to existing release draft' + task :append, [:version] do |_t, args| + with_rhx(args) do |config_path, _config, version| + puts "Appending new issues for version #{version}..." + cmd = "bundle exec bin/rhx #{version} --config #{config_path} --fetch --append" + run_cmd(cmd) + puts "✓ Successfully appended new issues to version #{version} draft" + end + end + + desc 'Publish release notes as AsciiDoc' + task :adoc, [:version] do |_t, args| + with_rhx(args) do |config_path, config, version| + drafts_dir = config.dig('paths', 'drafts_dir') || 'docs/release/drafts' + yaml_file = File.join(drafts_dir, "#{version}.yml") + + unless File.exist?(yaml_file) + warn "ERROR: Draft not found: #{yaml_file}" + warn "Run 'rake rhx:draft[#{version}]' first to create the draft." + exit 1 + end + + puts "Publishing release notes for version #{version}..." + output_file = "docs/release/#{version}.adoc" + cmd = "bundle exec bin/rhx #{yaml_file} --config #{config_path} --adoc #{output_file}" + run_cmd(cmd) + puts "✓ Successfully published release notes to #{output_file}" + end + end + + desc 'Complete workflow: draft, then publish as AsciiDoc' + task :generate, [:version] do |_t, args| + version = get_version(args) + + puts "=== Generating complete release documentation for #{version} ===" + Rake::Task['rhx:draft'].invoke(version) + Rake::Task['rhx:adoc'].invoke(version) + puts "\n✓ Complete! Release documentation generated for version #{version}" + end +end + def extract_version attrs = readme_attrs return attrs['this_prod_vrsn'].strip if attrs['this_prod_vrsn'] @@ -212,3 +293,60 @@ def ensure_buildx_builder sh "docker buildx create --name #{BUILDER_NAME} --driver docker-container --use" sh "docker buildx inspect --builder #{BUILDER_NAME} --bootstrap" end + +def generate_release_index + require 'fileutils' + require 'yaml' + + release_dir = 'docs/release' + output_file = 'build/docs/_release_index.adoc' + + # Ensure output directory exists + FileUtils.mkdir_p(File.dirname(output_file)) + + # Find all release AsciiDoc files (not drafts, not test files) + release_files = Dir.glob("#{release_dir}/*.adoc") + .reject { |f| f.include?('-test') || f.include?('draft') } + .sort + .reverse + + return if release_files.empty? + + # Build the index content + content = [] + content << '== Available Releases' + content << '' + content << 'Each release includes detailed notes about new features, improvements, bug fixes, and breaking changes.' + content << '' + + # List releases + release_files.each do |file| + version = File.basename(file, '.adoc') + # Try to extract date from the file + date = extract_release_date(file) || 'TBD' + content << "* link:../release/#{version}.html[#{version}] - #{date}" + end + + content << '' + content << '== Latest Release' + content << '' + + # Include the latest release content + if release_files.any? + latest_file = release_files.first + latest_version = File.basename(latest_file, '.adoc') + content << "include::release/#{latest_version}.adoc[leveloffset=+1]" + end + + # Write the file + File.write(output_file, content.join("\n")) + puts "✓ Generated release index: #{output_file}" +end + +def extract_release_date file + # Read first 20 lines looking for :page-date: attribute + File.foreach(file).first(20).each do |line| + return Regexp.last_match(1).strip if line =~ /:page-date:\s+(.+)$/ + end + nil +end diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock deleted file mode 100644 index 692d94c..0000000 --- a/docs/Gemfile.lock +++ /dev/null @@ -1,95 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) - asciidoctor (2.0.23) - base64 (0.3.0) - bigdecimal (3.2.3) - colorator (1.1.0) - concurrent-ruby (1.3.5) - csv (3.3.5) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - eventmachine (1.2.7) - ffi (1.17.2-x86_64-linux-gnu) - forwardable-extended (2.6.0) - google-protobuf (4.32.1-x86_64-linux-gnu) - bigdecimal - rake (>= 13) - http_parser.rb (0.8.0) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - jekyll (4.4.1) - addressable (~> 2.4) - base64 (~> 0.2) - colorator (~> 1.0) - csv (~> 3.0) - em-websocket (~> 0.5) - i18n (~> 1.0) - jekyll-sass-converter (>= 2.0, < 4.0) - jekyll-watch (~> 2.0) - json (~> 2.6) - kramdown (~> 2.3, >= 2.3.1) - kramdown-parser-gfm (~> 1.0) - liquid (~> 4.0) - mercenary (~> 0.3, >= 0.3.6) - pathutil (~> 0.9) - rouge (>= 3.0, < 5.0) - safe_yaml (~> 1.0) - terminal-table (>= 1.8, < 4.0) - webrick (~> 1.7) - jekyll-asciidoc (3.0.1) - asciidoctor (>= 1.5.0, < 3.0.0) - jekyll (>= 3.0.0) - jekyll-include-cache (0.2.1) - jekyll (>= 3.7, < 5.0) - jekyll-sass-converter (3.1.0) - sass-embedded (~> 1.75) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - json (2.15.0) - just-the-docs (0.10.1) - jekyll (>= 3.8.5) - jekyll-include-cache - jekyll-seo-tag (>= 2.0) - rake (>= 12.3.1) - kramdown (2.5.1) - rexml (>= 3.3.9) - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.4) - listen (3.9.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - mercenary (0.4.0) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (6.0.2) - rake (13.3.0) - rb-fsevent (0.11.2) - rb-inotify (0.11.1) - ffi (~> 1.0) - rexml (3.4.4) - rouge (4.6.1) - safe_yaml (1.0.5) - sass-embedded (1.93.2-x86_64-linux-gnu) - google-protobuf (~> 4.31) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.6.0) - webrick (1.9.1) - -PLATFORMS - x86_64-linux - -DEPENDENCIES - jekyll - jekyll-asciidoc - just-the-docs - -BUNDLED WITH - 2.4.19 diff --git a/docs/_config.yml b/docs/_config.yml index 8515039..cfe449e 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -4,12 +4,24 @@ baseurl: / plugins: - jekyll-asciidoc + - jekyll-redirect-from + +collections: + releases: + output: true + permalink: /docs/releases/:name/ defaults: - scope: - path: "api" + path: "docs/api" values: render_with_liquid: false + - scope: + path: "" + type: "releases" + values: + layout: default + nav_exclude: true include: - index.adoc @@ -17,6 +29,10 @@ include: exclude: - manpage.adoc + - jekyll/ + - payloads/ + - release/ + - yard/ # Enable Rouge for syntax highlighting in AsciiDoc blocks asciidoctor: diff --git a/docs/jekyll/_sass/custom/custom.scss b/docs/jekyll/_sass/custom/custom.scss index b8cdbd2..db0b6fd 100644 --- a/docs/jekyll/_sass/custom/custom.scss +++ b/docs/jekyll/_sass/custom/custom.scss @@ -53,4 +53,78 @@ h5 { .cl { color: #6367A9; } +} + +/* Release notes styling */ +.release-note { + margin-bottom: 1.5em; + padding: 1em 1.25em; + border-left: 3px solid #7253ed; + background-color: rgba(114, 83, 237, 0.04); + border-radius: 0 4px 4px 0; +} + +.release-note .paragraph:last-child p { + margin-bottom: 0; +} + +.release-note .listingblock { + margin-bottom: 0.5em; +} + +/* Component/part labels styled as badges */ +.rhx-component { + display: inline-block; + padding: 0.1em 0.4em; + margin: 0 0.2em; + font-size: 0.85em; + font-weight: 500; + border-radius: 3px; + background-color: rgba(100, 100, 255, 0.15); + border: 1px solid rgba(100, 100, 255, 0.3); +} + +/* Type labels styled as badges */ +.rhx-type { + display: inline-block; + padding: 0.1em 0.5em; + margin: 0 0.3em; + font-size: 0.85em; + font-weight: 600; + border-radius: 3px; +} + +.rhx-type-bug { + background-color: rgba(255, 100, 100, 0.15); + border: 1px solid rgba(255, 100, 100, 0.4); + color: #ff6b6b; +} + +.rhx-type-feature { + background-color: rgba(100, 200, 100, 0.15); + border: 1px solid rgba(100, 200, 100, 0.4); + color: #69db7c; +} + +.rhx-type-improvement { + background-color: rgba(100, 180, 255, 0.15); + border: 1px solid rgba(100, 180, 255, 0.4); + color: #74c0fc; +} + +.rhx-type-documentation { + background-color: rgba(200, 150, 255, 0.15); + border: 1px solid rgba(200, 150, 255, 0.4); + color: #da77f2; +} + +.rhx-type-deprecation { + background-color: rgba(255, 200, 100, 0.15); + border: 1px solid rgba(255, 200, 100, 0.4); + color: #ffc078; +} + +/* Icon spacing in metadata */ +.meta-icon { + margin-right: 0.2em; } \ No newline at end of file diff --git a/docs/landing.adoc b/docs/landing.adoc index bd309b4..fadd47a 100644 --- a/docs/landing.adoc +++ b/docs/landing.adoc @@ -1,6 +1,7 @@ :page-permalink: / :page-layout: minimal :page-liquid: true +:page-nav_exclude: true = ReleaseHx ++++ @@ -10,6 +11,15 @@

ReleaseHx

+ diff --git a/docs/release/0.1.2.adoc b/docs/release/0.1.2.adoc new file mode 100644 index 0000000..7b92403 --- /dev/null +++ b/docs/release/0.1.2.adoc @@ -0,0 +1,89 @@ +:icons: font + +:page-title: Release History for 0.1.2 +:page-version: 0.1.2 +:page-date: 2026-01-29 + += Release History -- 0.1.2 - 2026-01-29 + + +== What's Changed + + +Summary of all changes in this release. + + +=== Added + + +* Established an instance of ReleaseHx for managing the release history of... ReleaseHx + + +xref:note-0_1_2-30[NOTE] + + +=== Fixed + + +* The documented regexp format (/pattern/flags) is unsupported + + +xref:note-0_1_2-32[NOTE] + + +* Config property mismatch: code reads `note_source` but docs specify `note` + + +xref:note-0_1_2-28[NOTE] + + +* Formatting errors in AsciiDoc templates + + +xref:note-0_1_2-36[NOTE] + + +== Release Notes + + +Detailed descriptions of notable changes. + + +=== Added + +[.release-note] +-- +The ReleaseHx website now hosts a rich-text roster of change reports built with ReleaseHx itself at https://releasehx.docopslab.org/docs/releases/. +The output is configured in `.config/releasehx.yml`, and the process is documented in `README.adoc`. + +-- + + +=== Fixed + +[.release-note] +-- +Regular expressions in YAML files such as the releasehx application config now support being written as fully qualified strings with the pattern fenced in `/` markers. +Use `/pattern/flags` formats such as the following and expect proper parsing. + +[,yaml] +---- + note_pattern: /^((#+ Draft Release Note.*?)\n+(?(.|)+)/gmi +---- + +-- + + +[.release-note] +-- +Added support for a proper configuration property for release-note extraction, bringing the product into line with the documented config. +The effective property was never documented, so it has been removed, but no deprecation was deemed necessary. +Backward compatibility has been sacrificed in this rare case, since the functionality was obscured. + +-- + + +[.release-note] +-- +AsciiDoc templates have been cleaned up and streamlined significantly, mostly by removing the clumsy metadata that was hard to style in rich-text output. +We will improve on these templates later and welcome contributions cleaning them up. + +-- + + diff --git a/docs/releases.adoc b/docs/releases.adoc new file mode 100644 index 0000000..559c36a --- /dev/null +++ b/docs/releases.adoc @@ -0,0 +1,28 @@ +--- +layout: default +title: Releases +nav_order: 90 +permalink: /docs/releases/ +--- += Release History +:page-layout: default +:page-title: Releases +:page-nav_order: 90 +:page-permalink: /docs/releases/ + +Complete release history for ReleaseHx, generated using ReleaseHx itself. + +ifdef::env-site[] +// Generated during prebuild - includes release index and content +include::_release_index.adoc[] +endif::[] + +ifndef::env-site[] +== Development Mode + +Release history is generated during the prebuild process. Run `bundle exec rake prebuild` to generate the full release index. +endif::[] + +--- + +For more information about ReleaseHx, see the link:/docs/[main documentation]. diff --git a/docs/sample-config.adoc b/docs/sample-config.adoc index 2c123df..b401c43 100644 --- a/docs/sample-config.adoc +++ b/docs/sample-config.adoc @@ -1,4 +1,6 @@ :page-nav_order: 3 +:page-permalink: /docs/sample-config/ +:page-redirect_from: /sample-config.html = Sample Configuration File This is a "`kitchen sink`" example of the ReleaseHx configuration settings. diff --git a/lib/releasehx/cli.rb b/lib/releasehx/cli.rb index 09028ae..c790d6d 100644 --- a/lib/releasehx/cli.rb +++ b/lib/releasehx/cli.rb @@ -688,23 +688,32 @@ def enrich_direct_from_source source_path, version end def create_rhyml_from_source source_path, version - # Determine source type from configuration, not just file extension - configured_source_type = @settings.dig('origin', 'source') || 'json' - - # Handle different source types based on configuration - case configured_source_type - when 'rhyml' + # First check if source_path is actually a file (overrides config) + if version_or_file(source_path) == :file && File.exist?(source_path) # Load RHYML data directly from YAML file + ReleaseHx.logger.debug "Loading RHYML from file: #{source_path}" if options[:verbose] rhyml_data = SchemaGraphy::Loader.load_yaml_with_tags(source_path) release_data = rhyml_data['releases'] ? rhyml_data['releases'].first : rhyml_data # Convert hash keys to keyword arguments for Release constructor - ReleaseHx::RHYML::Release.new( + return ReleaseHx::RHYML::Release.new( code: release_data['code'] || version, date: release_data['date'], hash: release_data['hash'], memo: release_data['memo'], changes: release_data['changes'] || []) + end + + # Determine source type from configuration + configured_source_type = @settings.dig('origin', 'source') || 'json' + + # Handle different source types based on configuration + case configured_source_type + when 'rhyml' + # Config says rhyml but source_path is not a file - error + raise Thor::Error, + "ERROR: origin.source is 'rhyml' but no YAML file provided. " \ + 'Specify a YAML file path as the first argument.' when 'json' # For json type, only use local files (never API calls) if options[:api_data] diff --git a/lib/releasehx/rhyml/adapter.rb b/lib/releasehx/rhyml/adapter.rb index 41f87df..2c05fda 100644 --- a/lib/releasehx/rhyml/adapter.rb +++ b/lib/releasehx/rhyml/adapter.rb @@ -349,7 +349,7 @@ def extract_note_and_head! data note_pattern = sources['note_pattern'] || templates['note_pattern'] head_pattern = sources['head_pattern'] || templates['head_pattern'] head_source = sources['head_source'] - note_source = sources['note_source'] + note_source = sources['note'] extract_note!(data, note_source, note_pattern) extract_head!(data, head_source, head_pattern) @@ -406,10 +406,10 @@ def extract_note! data, note_source, note_pattern end # STEP 2: Apply regex pattern extraction if configured - return unless note_source =~ /issue_body/i && original_content.is_a?(String) && note_pattern.is_a?(String) + return unless note_source == 'issue_body' && original_content.is_a?(String) && note_pattern ReleaseHx.logger.debug "Extracting note using pattern: #{note_pattern}" - ReleaseHx.logger.debug "Original content: #{original_content}" + ReleaseHx.logger.debug "Original content: #{original_content[0..100]}..." begin # Apply sensible default flag 'm' (multiline/dotall in Ruby) when no flags provided @@ -421,11 +421,18 @@ def extract_note! data, note_source, note_pattern pattern_info, 'note') - # Only update if we got a match - data['note'] = extracted_note.strip if extracted_note + if extracted_note + # Pattern matched - use extracted content + data['note'] = extracted_note.strip + ReleaseHx.logger.debug "Extracted note (#{extracted_note.length} chars)" + else + # Pattern didn't match - clear the note so empty_notes policy applies + ReleaseHx.logger.warn "Note pattern did not match for issue #{data['tick']} - no Release Note section found" + data['note'] = nil + end rescue RegexpError => e ReleaseHx.logger.warn "Invalid note_pattern '#{note_pattern}': #{e.message}" - data['note'] = original_content # Restore original on error + # Preserve original content on pattern error - don't lose data due to bad config end end diff --git a/lib/releasehx/rhyml/mappings/github.yaml b/lib/releasehx/rhyml/mappings/github.yaml index 537acc6..f5fe4d7 100644 --- a/lib/releasehx/rhyml/mappings/github.yaml +++ b/lib/releasehx/rhyml/mappings/github.yaml @@ -25,7 +25,9 @@ note: # Extract type from the native GitHub Issues type field (modern approach) type: - path: "issue_type.name" + path: "type.name" + ruby: | + path&.downcase # Derive `parts` from labels using direct key matching or slug override parts: diff --git a/lib/releasehx/rhyml/templates/entry.adoc.liquid b/lib/releasehx/rhyml/templates/entry.adoc.liquid index 93bb160..278a88e 100644 --- a/lib/releasehx/rhyml/templates/entry.adoc.liquid +++ b/lib/releasehx/rhyml/templates/entry.adoc.liquid @@ -1,4 +1,3 @@ -{%- embed metadata-entry.adoc.liquid -%} {%- assign frame = content_config.items.frame %} {%- assign summ = change.summ %} {%- case frame %} @@ -17,10 +16,7 @@ [start={{ item_count }}] {%- endif %} {{ bullet -}} -{{ change.summ | trim }} + -{%- if change_metadata != "" -%} -{{ change_metadata | trim }} -{%- endif %} -{%- if change.note %} -xref:note-{{ change.chid }}[icon:sticky-note[]NOTE] +{{ change.summ | trim }} +{%- if change.note %} + +xref:note-{{ change.chid }}[NOTE] {%- endif %} diff --git a/lib/releasehx/rhyml/templates/entry.md.liquid b/lib/releasehx/rhyml/templates/entry.md.liquid index e1849fd..f121f62 100644 --- a/lib/releasehx/rhyml/templates/entry.md.liquid +++ b/lib/releasehx/rhyml/templates/entry.md.liquid @@ -1,4 +1,3 @@ -{%- embed metadata-entry.md.liquid -%} {%- assign frame = content_config.items.frame | default: "unordered" %} {%- assign summ = change.summ %} {%- case frame %} @@ -14,12 +13,12 @@ {%- if frame == "basic" %} -{{ bullet }}{{ change.summ | trim }}{% if change_metadata != "" %} ({{ change_metadata | trim | replace: " | ", ", " }}){% endif %}{% if change.note and change.note != "" %} [NOTE](#note-{{ change.chid }}){% endif %} +{{ bullet }}{{ change.summ | trim }}{% if change.note and change.note != "" %} [NOTE](#note-{{ change.chid }}){% endif %} {%- elsif frame == "paragraph" %} -{{ change.summ | trim }}{% if change_metadata != "" %} ({{ change_metadata | trim }}){% endif %}{% if change.note and change.note != "" %} [NOTE](#note-{{ change.chid }}){% endif %} +{{ change.summ | trim }}{% if change.note and change.note != "" %} [NOTE](#note-{{ change.chid }}){% endif %} {%- else %} -{{ bullet }}{{ change.summ | trim }}{% if change_metadata != "" %} ({{ change_metadata | trim }}){% endif %}{% if change.note and change.note != "" %} [NOTE](#note-{{ change.chid }}){% endif %} +{{ bullet }}{{ change.summ | trim }}{% if change.note and change.note != "" %} [NOTE](#note-{{ change.chid }}){% endif %} {%- endif %} diff --git a/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid b/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid index a04b8cb..d6b2fe6 100644 --- a/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid +++ b/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid @@ -4,53 +4,42 @@ {%- assign format = "asciidoc" %} {%- embed tags-listing.liquid %} {%- embed parts-listing.liquid %} -{%- capture change_metadata %} -{%- if parts_listing != "" and entry_show_parts_label %} -{{- parts_listing | trim }} -{%- elsif change.parts and change.parts.size > 0 and entry_show_parts_label == false %} -{%- for part in change.parts %} -{%- unless part == "changelog" or part == "release_note_needed" %} -{{ config.parts[part]['text'] | default: part }} -{%- endunless %} -{%- endfor %} -{%- endif %} -{%- if change.type %} -{%- assign type_config = config.types[change.type] %} -{%- if entry_show_type_label %} -[.meta-label.type-label]*{{ labeling_type_label }}:* -{%- endif %} -{%- if type_config.icon and show_icons and show_icons == "before" %} -icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon] -{%- endif %} -{{ type_config.text | default: change.type }} -{%- if type_config.icon and show_icons and show_icons == "after" %} -icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon] -{%- endif %} -{%- endif %} -{%- if tags_listing != "" and entry_show_tags_label %} -{{- tags_listing | trim }} -{%- elsif change.tags and change.tags.size > 0 and entry_show_tags_label == false %} -{%- for tag in change.tags %} -{%- unless tag == "changelog" or tag == "release_note_needed" %} -{{ config.tags[tag]['text'] | default: tag }} -{%- endunless %} -{%- endfor %} -{%- endif %} -{%- if entry_show_lead and change.lead %} -{%- if entry_show_lead_label %} -[.meta-label.lead-label]*{{ labeling_lead_label }}:* -{%- endif %} -{%- if entry_user_link_template %} -link:{{ entry_user_link_template | render: change }}[icon:user[]{{ change.lead }}] -{%- else %} -icon:user[]{{ change.lead }} -{%- endif %} -{%- endif %} -{%- if config.links.web.href and config.links.web.href != "" %} -link:{{ config.links.web.href | render: change }}[icon:{{ config.links.web.icon }}[]{{ config.links.web.text | render: change }}] +{%- capture change_metadata -%} +{%- if parts_listing != "" and entry_show_parts_label -%} +{{ parts_listing | trim }} {% endif -%} +{%- if change.parts and change.parts.size > 0 and entry_show_parts_label == false -%} +{%- for part in change.parts -%} +{%- unless part == "changelog" or part == "release_note_needed" -%} +{{ config.parts[part]['text'] | default: part }} {% endunless -%} +{%- endfor -%} {%- endif -%} -{%- if entry_show_git_links and config.links.git.href and config.links.git.href != "" %} -link:{{ config.links.git.href | render: change }}[icon:{{ config.links.git.icon | default: 'code-fork' }}[]{{ change.hash | slice: 0, 7 }}] -{%- endif %} +{%- if change.type -%} +{%- assign type_config = config.types[change.type] -%} +{%- if entry_show_type_label -%} +**{{ labeling_type_label }}:** {% endif -%} +{%- if type_config.icon and show_icons and show_icons == "before" -%} +icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon]{% endif -%} +{{ type_config.text | default: change.type }} {% if type_config.icon and show_icons and show_icons == "after" -%} +icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon]{% endif -%} +{%- endif -%} +{%- if tags_listing != "" and entry_show_tags_label -%} +{{ tags_listing | trim }} {% endif -%} +{%- if change.tags and change.tags.size > 0 and entry_show_tags_label == false -%} +{%- for tag in change.tags -%} +{%- unless tag == "changelog" or tag == "release_note_needed" -%} +{{ config.tags[tag]['text'] | default: tag }} {% endunless -%} +{%- endfor -%} +{%- endif -%} +{%- if entry_show_lead and change.lead -%} +{%- if entry_show_lead_label -%} +**{{ labeling_lead_label }}:** {% endif -%} +{%- if entry_user_link_template -%} +link:{{ entry_user_link_template | render: change }}[icon:user[]{{ change.lead }}] {% else -%} +icon:user[]{{ change.lead }} {% endif -%} +{%- endif -%} +{%- if config.links.web.href and config.links.web.href != "" -%} +link:{{ config.links.web.href | render: change }}[icon:{{ config.links.web.icon }}[]{{ config.links.web.text | render: change }}] {% endif -%} +{%- if entry_show_git_links and config.links.git.href and config.links.git.href != "" -%} +link:{{ config.links.git.href | render: change }}[icon:{{ config.links.git.icon | default: 'code-fork' }}[]{{ change.hash | slice: 0, 7 }}] {% endif -%} {%- endcapture %} {%- assign catch_metadata_error = change_metadata %} diff --git a/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid b/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid index 0f0477c..b9b43cc 100644 --- a/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid +++ b/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid @@ -4,53 +4,42 @@ {%- assign format = "asciidoc" %} {%- embed tags-listing.liquid %} {%- embed parts-listing.liquid %} -{%- capture change_metadata %} -{%- if parts_listing != "" and note_show_parts_label %} -{{- parts_listing | trim }} -{%- elsif change.parts and change.parts.size > 0 and note_show_parts_label == false %} -{%- for part in change.parts %} -{%- unless part == "changelog" or part == "release_note_needed" %} -{{ config.parts[part]['text'] | default: part }} -{%- endunless %} -{%- endfor %} -{%- endif %} -{%- if change.type %} -{%- assign type_config = config.types[change.type] %} -{%- if note_show_type_label %} -[.meta-label.type-label]*{{ labeling_type_label }}:* -{%- endif %} -{%- if type_config.icon and show_icons and show_icons == "before" %} -icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon] -{%- endif %} -{{ type_config.text | default: change.type }} -{%- if type_config.icon and show_icons and show_icons == "after" %} -icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon] -{%- endif %} -{%- endif %} -{%- if tags_listing != "" and note_show_tags_label %} -{{- tags_listing | trim }} -{%- elsif change.tags and change.tags.size > 0 and note_show_tags_label == false %} -{%- for tag in change.tags %} -{%- unless tag == "changelog" or tag == "release_note_needed" %} -{{ config.tags[tag]['text'] | default: tag }} -{%- endunless %} -{%- endfor %} -{%- endif %} -{%- if note_show_lead and change.lead %} -{%- if note_show_lead_label %} -[.meta-label.lead-label]*{{ labeling_lead_label }}:* -{%- endif %} -{%- if note_user_link_template %} -link:{{ note_user_link_template | render: change }}[icon:user[]{{ change.lead }}] -{%- else %} -icon:user[]{{ change.lead }} -{%- endif %} -{%- endif %} -{%- if config.links.web.href and config.links.web.href != "" %} -link:{{ config.links.web.href | render: change }}[icon:{{ config.links.web.icon }}[]{{ config.links.web.text | render: change }}] +{%- capture change_metadata -%} +{%- if parts_listing != "" and note_show_parts_label -%} +{{ parts_listing | trim }} {% endif -%} +{%- if change.parts and change.parts.size > 0 and note_show_parts_label == false -%} +{%- for part in change.parts -%} +{%- unless part == "changelog" or part == "release_note_needed" -%} +{{ config.parts[part]['text'] | default: part }} {% endunless -%} +{%- endfor -%} {%- endif -%} -{%- if note_show_git_links and config.links.git.href and config.links.git.href != "" %} -link:{{ config.links.git.href | render: change }}[icon:{{ config.links.git.icon | default: 'code-fork' }}[]{{ change.hash | slice: 0, 7 }}] -{%- endif %} +{%- if change.type -%} +{%- assign type_config = config.types[change.type] -%} +{%- if note_show_type_label -%} +**{{ labeling_type_label }}:** {% endif -%} +{%- if type_config.icon and show_icons and show_icons == "before" -%} +icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon]{% endif -%} +{{ type_config.text | default: change.type }} {% if type_config.icon and show_icons and show_icons == "after" -%} +icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon]{% endif -%} +{%- endif -%} +{%- if tags_listing != "" and note_show_tags_label -%} +{{ tags_listing | trim }} {% endif -%} +{%- if change.tags and change.tags.size > 0 and note_show_tags_label == false -%} +{%- for tag in change.tags -%} +{%- unless tag == "changelog" or tag == "release_note_needed" -%} +{{ config.tags[tag]['text'] | default: tag }} {% endunless -%} +{%- endfor -%} +{%- endif -%} +{%- if note_show_lead and change.lead -%} +{%- if note_show_lead_label -%} +**{{ labeling_lead_label }}:** {% endif -%} +{%- if note_user_link_template -%} +link:{{ note_user_link_template | render: change }}[icon:user[]{{ change.lead }}] {% else -%} +icon:user[]{{ change.lead }} {% endif -%} +{%- endif -%} +{%- if config.links.web.href and config.links.web.href != "" -%} +link:{{ config.links.web.href | render: change }}[icon:{{ config.links.web.icon }}[]{{ config.links.web.text | render: change }}] {% endif -%} +{%- if note_show_git_links and config.links.git.href and config.links.git.href != "" -%} +link:{{ config.links.git.href | render: change }}[icon:{{ config.links.git.icon | default: 'code-fork' }}[]{{ change.hash | slice: 0, 7 }}] {% endif -%} {%- endcapture %} {%- assign catch_metadata_error = change_metadata %} diff --git a/lib/releasehx/rhyml/templates/note.adoc.liquid b/lib/releasehx/rhyml/templates/note.adoc.liquid index a1ef955..7554b4c 100644 --- a/lib/releasehx/rhyml/templates/note.adoc.liquid +++ b/lib/releasehx/rhyml/templates/note.adoc.liquid @@ -3,50 +3,12 @@ {%- else %} {%- assign change_note = change.note | default: "NO RELEASE NOTE PROVIDED" | trim %} {%- endif %} -{%- include metadata-note.adoc.liquid %} {%- unless changes_listed contains change.chid %} [[note-{{ change.chid }}]] {%- endunless %} [.release-note] -{%- if config.notes.items.frame == "table-cols-1" %} -|=== -a| {{ change_metadata }} + -{%- if change.head %} -*{{ change.head }}* + -{%- endif %} +-- {{ change_note }} -|=== -{%- elsif config.notes.items.frame == "table-cols-2" %} -[cols="5,2"] -|=== -{%- if change.head %} -2+a| *{{ change.head }}* -{%- endif %} -a| {{ change_note }} -a| {{ change_metadata }} -|=== -{%- elsif config.notes.items.frame == "desc-list" %} -{{ change.head }}:: -{{ change_note }} + -{{ change_metadata }} -{%- elsif config.notes.items.frame == "admonition" %} -{%- if change.type == "removal" or change.type == "deprecation" or change.type == "breaking" %} -{%- assign admonition_type = "WARNING" %} -{%- elsif change.type == "security" %} -{%- assign admonition_type = "IMPORTANT" %} -{%- elsif change.type == "experimental" %} -{%- assign admonition_type = "TIP" %} -{%- else %} -{%- assign admonition_type = "NOTE" %} -{%- endif %} -[{{ admonition_type }}] -{%- if change.head %} -.{{ change.head }} -{%- endif %} -==== -{{ change_note }} + -{{ change_metadata }} -==== -{%- endif %} +-- diff --git a/lib/releasehx/rhyml/templates/note.md.liquid b/lib/releasehx/rhyml/templates/note.md.liquid index 58b1c08..a71e5d0 100644 --- a/lib/releasehx/rhyml/templates/note.md.liquid +++ b/lib/releasehx/rhyml/templates/note.md.liquid @@ -3,17 +3,15 @@ {% else -%} {% assign change_note = change.note | default: "NO RELEASE NOTE PROVIDED" | adoc_to_md | trim -%} {% endif -%} -{% embed metadata-note.md.liquid -%} {%- if config.notes.items.frame == "table" %} -| Change | Details | -|--------|---------| -| {{ change_metadata }} | {% if change.head %}**{{ change.head }}**
{% endif %}{{ change_note }} | +| Details | +|---------| +| {% if change.head %}**{{ change.head }}**
{% endif %}{{ change_note }} | {%- elsif config.notes.items.frame == "desc-list" %} -**{{ change.head }}** -{{ change_note }} -*{{ change_metadata }}* +**{{ change.head | default: change.summ }}** +{{ change_note }} {% elsif config.notes.items.frame == "admonition" %} {% if change.type == "removal" or change.type == "deprecation" or change.type == "breaking" %} {% assign admonition_type = "Warning" %} @@ -25,13 +23,11 @@ {% assign admonition_type = "Note" %} {% endif %} -> **{{ admonition_type }}**: {% if change.head %}**{{ change.head }}** - {% endif %}{{ change_note }} -> {{ change_metadata }} +> **{{ admonition_type }}**: {% if change.head %}**{{ change.head }}** - {% endif %}{{ change_note }} {% else -%} {% if change.head -%} ### {{ change.head }} {% endif -%} {{ change_note }} -{{- change_metadata }} {% endif %} diff --git a/lib/releasehx/rhyml/templates/parts-listing.liquid b/lib/releasehx/rhyml/templates/parts-listing.liquid index 87024b1..c384c4d 100644 --- a/lib/releasehx/rhyml/templates/parts-listing.liquid +++ b/lib/releasehx/rhyml/templates/parts-listing.liquid @@ -19,10 +19,10 @@ {%- if show_labels -%} {%- if format == "asciidoc" -%} {%- if labeling_singularize_labels and parts_count == 1 -%} -[.meta-label.parts-label]*{{ labeling_part_label }}:*{%- else -%}[.meta-label.parts-label]*{{ labeling_parts_label }}:*{%- endif -%} +**{{ labeling_part_label }}:** {% else -%}**{{ labeling_parts_label }}:** {% endif -%} {%- elsif format == "markdown" -%} {%- if labeling_singularize_labels and parts_count == 1 -%} -{{ labeling_part_label }}: {%- else -%}{{ labeling_parts_label }}: {%- endif -%} +{{ labeling_part_label }}: {% else -%}{{ labeling_parts_label }}: {% endif -%} {%- endif -%} {%- endif -%} {%- assign part_texts = "" | split: "," -%} @@ -38,10 +38,10 @@ {%- unless skip_part -%} {%- capture part_text -%} {%- if config.parts[part].icon and show_icons and show_icons == "before" -%} -{%- if format == "asciidoc" -%}icon:{{ config.parts[part].icon }}[] {%- endif -%} +{%- if format == "asciidoc" -%}icon:{{ config.parts[part].icon }}[]{%- endif -%} {%- endif -%} {%- if format == "asciidoc" -%} -{{ config.parts[part]['text'] | default: part }}{%- elsif format == "markdown" -%}**{{ config.parts[part]['text'] | default: part }}**{%- endif -%} +{{ config.parts[part]['head'] | default: config.parts[part]['text'] | default: part }}{%- elsif format == "markdown" -%}**{{ config.parts[part]['head'] | default: config.parts[part]['text'] | default: part }}**{%- endif -%} {%- if config.parts[part].icon and show_icons and show_icons == "after" -%} {%- if format == "asciidoc" -%} icon:{{ config.parts[part].icon }}[]{%- endif -%} {%- endif -%} @@ -52,10 +52,10 @@ {%- if part_texts.size > 0 -%} {%- if format == "asciidoc" -%} {%- for part_text in part_texts -%} -{{ part_text }}{%- unless forloop.last %} {%- endunless -%} +{{ part_text }}{%- unless forloop.last %}, {% endunless -%} {%- endfor -%} {%- elsif format == "markdown" -%} -{{ part_texts | join: ", " }}{%- endif -%} +{{ part_texts | join: ", " }}{% endif -%} {%- endif -%} {%- endcapture -%} {%- endif -%} diff --git a/lib/releasehx/rhyml/templates/rhyml-change.yaml.liquid b/lib/releasehx/rhyml/templates/rhyml-change.yaml.liquid index 70232cf..feed26a 100644 --- a/lib/releasehx/rhyml/templates/rhyml-change.yaml.liquid +++ b/lib/releasehx/rhyml/templates/rhyml-change.yaml.liquid @@ -20,7 +20,8 @@ {%- if change.hash %} hash: {{ change.hash }} {%- endif %} - summ: {{ change.summ }} + summ: | + {{ change.summ }} {%- if change.head %} head: {{ change.head }} {%- endif %} diff --git a/lib/schemagraphy/regexp_utils.rb b/lib/schemagraphy/regexp_utils.rb index f9cc1e7..c58a339 100644 --- a/lib/schemagraphy/regexp_utils.rb +++ b/lib/schemagraphy/regexp_utils.rb @@ -35,8 +35,28 @@ def parse_pattern input, default_flags = '' # Remove surrounding quotes that might come from YAML parsing clean_input = input_str.gsub(/^["']|["']$/, '') + # Manual parsing for /pattern/flags format (common in YAML configs) + if clean_input =~ %r{^/(.+)/([a-z]*)$} + pattern_str = Regexp.last_match(1) + flags_str = Regexp.last_match(2) + options = flags_to_options(flags_str) + + begin + regexp_obj = Regexp.new(pattern_str, options) + + return { + pattern: pattern_str, + flags: flags_str, + regexp: regexp_obj, + options: options + } + rescue RegexpError => e + raise RegexpError, "Invalid regex pattern '#{input}': #{e.message}" + end + end + # Heuristic to detect if it's a Regexp literal - is_literal = (clean_input.start_with?('/') && clean_input.rindex('/').positive?) || clean_input.start_with?('%r{') + is_literal = clean_input.start_with?('%r{') if is_literal # Try to parse as regex literal using to_regexp diff --git a/lib/schemagraphy/templates/cfgyml/config-reference.adoc.liquid b/lib/schemagraphy/templates/cfgyml/config-reference.adoc.liquid index 4588ddf..f60130a 100644 --- a/lib/schemagraphy/templates/cfgyml/config-reference.adoc.liquid +++ b/lib/schemagraphy/templates/cfgyml/config-reference.adoc.liquid @@ -1,7 +1,3 @@ -:page-layout: default -:page-permalink: /config-reference/ -:page-nav_order: 2 -:page-title: Configuration Reference {%- for property in config_def.properties -%} {%- assign ppty_key1 = property[0] -%} {%- assign ppty_path_t1 = ppty_key1 -%} diff --git a/scripts/build_docs.rb b/scripts/build_docs.rb index cff1303..92c3fa6 100644 --- a/scripts/build_docs.rb +++ b/scripts/build_docs.rb @@ -32,6 +32,9 @@ def self.prepare_jekyll_source _version FileUtils.cp 'README.adoc', 'build/docs/index.adoc' FileUtils.cp_r 'docs/.', 'build/docs/' + # Add front matter to config-reference.adoc + add_config_reference_front_matter + # Update version in Jekyll config config_path = 'build/docs/_config.yml' raise "Jekyll config not found at #{config_path}" unless File.exist?(config_path) @@ -40,6 +43,26 @@ def self.prepare_jekyll_source _version File.write(config_path, config_content) end + def self.add_config_reference_front_matter + config_ref_path = 'build/docs/config-reference.adoc' + return unless File.exist?(config_ref_path) + + content = File.read(config_ref_path) + + # Add front matter if not already present + return if content.start_with?(':page-layout:') + + front_matter = <<~FRONT_MATTER + :page-layout: default + :page-permalink: /docs/config-reference/ + :page-nav_order: 2 + :page-redirect_from: ["/config-reference"] + :page-title: Configuration Reference + FRONT_MATTER + + File.write(config_ref_path, front_matter + content) + end + def self.generate_module_docs version puts 'Generating modular API docs with YARD...' @@ -65,7 +88,7 @@ def self.generate_module_docs version temp_readme_path = "build/docs/#{mod[:name].downcase}_readme.html" File.write(temp_readme_path, processed_readme_html) - output_dir = "build/docs/api/#{mod[:name].downcase}" + output_dir = "build/docs/docs/api/#{mod[:name].downcase}" FileUtils.mkdir_p output_dir file_list = mod[:files].join(' ') @@ -90,6 +113,18 @@ def self.generate_module_docs version Dir.glob("#{output_dir}/**/*.html").each do |html_file| add_custom_css_to_html(html_file, output_dir) end + + # Fix YARD index file naming: _index.html is the real API index, + # but index.html is generated from README. Rename them appropriately. + yard_index = File.join(output_dir, '_index.html') + readme_index = File.join(output_dir, 'index.html') + + next unless File.exist?(yard_index) + + # Rename README-based index to readme.html + File.rename(readme_index, File.join(output_dir, 'readme.html')) if File.exist?(readme_index) + # Rename _index.html to index.html (this is the real API overview) + File.rename(yard_index, readme_index) end end @@ -169,7 +204,7 @@ def self.add_jekyll_front_matter end # Add front matter to YARD API documentation files - api_files = Dir.glob('build/docs/api/**/*.html') + api_files = Dir.glob('build/docs/docs/api/**/*.html') return if api_files.empty? api_files.each do |file| diff --git a/specs/data/config-def.yml b/specs/data/config-def.yml index c3def6a..2e85683 100644 --- a/specs/data/config-def.yml +++ b/specs/data/config-def.yml @@ -165,6 +165,7 @@ properties: Must be `issue_body`, `custom_field`, or `commit_message`. Defaults to `issue_body` for GitHub and GitLab, but to `custom_field` for Jira. + dflt: issue_body note_custom_field: type: String desc: | @@ -182,7 +183,8 @@ properties: Uses Capture group `note` in the Regular Expression to establish the entire note content. See the `conversions.head_pattern` property for details on extracting a heading (`head` in RHYML) from the `note` content. - dflt: '/^((#|=)+ (Draft )?Release Note.*)|()\n(?\w(.|\n)+)/gmi' + dflt: '/^(((#|=)+ (Draft )?Release Note.*?)|())\n+(?(.| +)+)/gmi' head_pattern: type: RegExp desc: | @@ -1162,7 +1164,7 @@ properties: The label to use for the _singular_ part/component affected by the change, when only one part is permitted. This value will apply either when <> is set to `1` or when the change has only one part _and_ <> is `true`. - dflt: Part + dflt: Component parts_icon: type: String desc: | diff --git a/specs/tests/rspec/build_integration_spec.rb b/specs/tests/rspec/build_integration_spec.rb index c99dcbb..c1bb72b 100644 --- a/specs/tests/rspec/build_integration_spec.rb +++ b/specs/tests/rspec/build_integration_spec.rb @@ -89,9 +89,7 @@ expect(stdout).to match(/prebuild artifacts missing/i) ensure # Restore generated.rb if we backed it up - if generated_backup - FileUtils.mv('lib/releasehx/generated.rb.bak', 'lib/releasehx/generated.rb') - end + FileUtils.mv('lib/releasehx/generated.rb.bak', 'lib/releasehx/generated.rb') if generated_backup end end end diff --git a/specs/tests/rspec/dependencies_spec.rb b/specs/tests/rspec/dependencies_spec.rb index ff7c87e..abb9803 100644 --- a/specs/tests/rspec/dependencies_spec.rb +++ b/specs/tests/rspec/dependencies_spec.rb @@ -65,8 +65,9 @@ temp_config.close begin - result = system("bundle exec bin/releasehx 1.0.0 --config #{temp_config.path} " \ - "--api-data #{temp_json} --md #{temp_output}/output.md --force") + result = system( + "bundle exec bin/releasehx 1.0.0 --config #{temp_config.path} " \ + "--api-data #{temp_json} --md #{temp_output}/output.md --force") expect(result).to be(true), 'Template processing failed with sample data' # Should have generated some output files diff --git a/specs/tests/rspec/rhyml/adapter_spec.rb b/specs/tests/rspec/rhyml/adapter_spec.rb index 391809a..4563882 100644 --- a/specs/tests/rspec/rhyml/adapter_spec.rb +++ b/specs/tests/rspec/rhyml/adapter_spec.rb @@ -7,7 +7,7 @@ let(:config) do { 'conversions' => { - 'note_source' => 'issue_body', + 'note' => 'issue_body', 'note_pattern' => '/## Release Notes?\n(?m:(?.*?))(?=\n##|\z)/', 'head_source' => 'release_note_heading', 'head_pattern' => '/^## (?.*?)$/m' @@ -50,7 +50,9 @@ end it 'extracts head from note content' do - data['note'] = "## Important Change\nThis is the content" + # Note content must match the note_pattern first (contains "## Release Notes") + # Then head_pattern extracts the heading from the extracted content + data['note'] = "## Release Notes\n## Important Change\nThis is the content\n## Another Section" processed = adapter.send(:postprocess, data.dup) expect(processed['head']).to eq('Important Change') expect(processed['note']).to eq('This is the content')