Skip to content

v1.6.0 — Architecture refactor

Choose a tag to compare

@github-actions github-actions released this 22 May 23:16

jarkdown-rs v1.6.0 — Architecture refactor release

This release closes a seven-issue architecture refactor (#7, #9, #10, #11, #12, #8, #13) that reshapes the crate's internals — typed wire models at the HTTP seam, a pure rendering function, smaller single-purpose modules, and a testable seam in hierarchical export — without changing CLI behavior or on-disk output.

Compatibility for CLI users: every export against the live regression harness is byte-identical to 1.5.0 across full, hierarchical, bulk, and changelog scenarios, including --strict-md byte-equality on the rendered Markdown. Drop-in upgrade.

Compatibility for library consumers: the public MarkdownConverter struct is removed; switch to jarkdown::compose(&RenderContext). See Breaking changes below.


Highlights

  • Typed wire models at the Jira HTTP seam. fetch_issue returns Issue, search_jql returns Vec<IssueSearchResult>, fetch_changelog returns Vec<ChangelogEntry>. jira_client.rs is pure transport again. The full Issue surface is typed — spine fields, comments, the 19 display-only standard fields, worklog entries, components/versions/fixVersions, project, priority, resolution, votes, watches, environment, reporter, creator — backed by 10 small types and hand-rolled from_value parsers that mirror the pattern set by #7. Every typed model retains its raw serde_json::Value so --include-json output remains byte-identical (see ADR-0002).
  • Pure rendering. MarkdownConverter is gone. The render path is now the single function compose(&RenderContext) -> String with no &mut, no render-time mutation, and no hidden state. RenderContext<'a> is a plain pub-fields struct, literal-constructible, that borrows everything it needs.
  • src/markdown.rs split (1,447 lines → 5 focused modules). New module tree:
    • markdown/mod.rs — RenderContext, CustomFieldMetadata, compose()
    • markdown/adf.rs — ADF → Markdown
    • markdown/html.rs — HTML → Markdown
    • markdown/attachments.rs — AttachmentIndex + replace_attachment_links
    • markdown/sections.rs — per-section composer functions over &RenderContext
  • IssueExporter trait seam for hierarchical export. Hierarchical traversal no longer depends on the concrete ten-input export function — it depends on a one-method IssueExporter trait. Production uses WorkflowIssueExporter; tests use a recording fake. Three new unit tests cover cycle detection, max-depth cap, and max-issues cap — the first real test coverage of hierarchy.rs.
  • Single-owner Changelog artifact writes. changelog::write_artifacts is now the sole place .changelog.md and .changelog.json get written. Eliminates a class of silent-swallow drift between the per-issue export and bulk paths.
  • Freshness as its own module. The --incremental decision lives in src/freshness.rs (freshness::plan() -> ExportPlan). The CLI handler and the bulk loop both consult one implementation of ADR-0001 Decision 2.
  • Field cache pre-resolution. Custom-field name/schema resolution happens once in src/export.rs before RenderContext is built. The render path never mutates FieldMetadataCache.

Breaking changes

Removed Replacement
pub use markdown::MarkdownConverter; jarkdown::compose(&RenderContext)
MarkdownConverter::new(...) + compose_markdown(&mut self, ...) Literal-construct RenderContext and call compose(&ctx)
pub fn convert_html_to_markdown / pub fn replace_attachment_links / pub fn parse_adf_to_markdown from the crate root All three are now pub(crate) — internal only

CLI users are not affected — flags, output filenames, and byte-level output are unchanged.

Library consumers should construct a RenderContext directly. The new pattern (see src/export.rs for the production usage):

use jarkdown::{compose, AttachmentIndex, CustomFieldMetadata, RenderContext};
use jarkdown::config::FieldFilter;

let field_metadata = CustomFieldMetadata::empty();
let field_filter = FieldFilter::default();
let attachments = AttachmentIndex::build(&downloaded, skipped_slice);

let ctx = RenderContext {
    issue: &issue,
    downloaded: &downloaded,
    skipped_attachments: skipped_slice,
    attachments,
    field_metadata: &field_metadata,
    field_filter: &field_filter,
    child_issues: &[],
    changelog_summary: None,
    base_url: &api_client.base_url,
    domain: &api_client.domain,
};

let markdown = compose(&ctx);

New public API

  • jarkdown::compose(&RenderContext) -> String
  • jarkdown::RenderContext<'a> (plain pub-fields, literal-constructible)
  • jarkdown::AttachmentIndex<'a> with build() / empty()
  • jarkdown::CustomFieldMetadata with empty()
  • jarkdown::exporter::{IssueExporter, WorkflowIssueExporter}
  • jarkdown::{ChangelogEntry, ChangelogItem} (re-exported from issue)
  • 19 newly-typed fields on Issue and supporting nested types: Priority, Resolution, Project, NamedRef, TimeTracking, Progress, Votes, Watches, WorklogPage, WorklogEntry

Internals — what didn't change

  • All Jira HTTP calls and their semantics.
  • --include-json (typed models retain raw: Value; serialization byte-identical).
  • .changelog.md / .changelog.json artifact shape (ADR-0001).
  • Issue::field() and Issue.raw — still present, now reserved for customfield_* access only.
  • --incremental policy (just lives in its own module now).
  • Attachment lookup semantics (id-first, name lowercased+trimmed, dual-name for downloaded, single-name for skipped).

Testing & verification

  • cargo test 34 / 34 passing — up from ~20 at 1.5.0. New coverage:
    • 4 tests for typed Issue / IssueSearchResult / Comment parsing.
    • 2 tests for typed ChangelogEntry parsing (positive + malformed-payload rejection).
    • 2 tests for typed display-field parsing (positive + wrong-typed rejection).
    • 3 tests for hierarchical export traversal via a fake IssueExporter (cycle, max-depth, max-issues).
    • 2 new section unit tests built from a literal RenderContext (comments, frontmatter) — no json!() fixtures.
  • cargo clippy --all-targets -- -D warnings clean.
  • Live regression: every refactor commit was verified byte-identical against a frozen pre-refactor binary (.goal-baseline/jarkdown-main) across full (PSOP-style ticket), hierarchical (epic → children), and bulk scenarios, including the strict --strict-md Markdown byte-equality gate introduced by #8.

Architecture notes / ADRs

Closed issues

# Title Commit
#7 Introduce a typed Issue model at the Jira client seam 33dcfa8
#9 Consolidate Changelog artifact writes into one module 254864a
#10 Extract a freshness module that owns the incremental export decision 4dbd10f
#11 Give hierarchy.rs an IssueExporter seam instead of a direct call c25022a
#12 Type Changelog entries at the fetch_changelog seam 9e2825d
#8 Bundle markdown rendering inputs into a RenderContext and split the module 6acc368
#13 Type the ~19 display-only Issue fields 9dbc088

Install jarkdown 1.6.0

Shell script (macOS / Linux)

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dchuk/jarkdown-rs/releases/download/v1.6.0/jarkdown-installer.sh | sh

Homebrew

brew install dchuk/tap/jarkdown
# or, if already installed:
brew upgrade jarkdown

Cargo

cargo install jarkdown

Download jarkdown 1.6.0

File Platform
jarkdown-aarch64-apple-darwin.tar.xz Apple Silicon macOS
jarkdown-x86_64-apple-darwin.tar.xz Intel macOS
jarkdown-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux (glibc)
jarkdown-x86_64-unknown-linux-gnu.tar.xz x86_64 Linux (glibc)
jarkdown-x86_64-pc-windows-msvc.zip x86_64 Windows

SHA-256 sums for each archive are published alongside the archives (*.sha256) plus the aggregate sha256.sum.

Full diff

v1.5.0...v1.6.0