@hyperfrontend/versioning/commits/format

format/

Pure formatter for commit drafts.

Overview

Renders a CommitDraft (the partial, in-progress shape of a ConventionalCommit) into the exact message string that will land in .git/COMMIT_EDITMSG. Used by the authoring session's preview step and by the live 72-character countdown in the subject step.

Behavior

  • Missing fields render empty. Drafts accumulate field-by-field, so the formatter tolerates any combination of absent values rather than throwing. formatHeader({ type: 'fix' }) returns 'fix: '.
  • Multi-scope joins with ,. Matches the parser format: scope: ['a', 'b'] → (a,b).
  • Breaking marker precedes the colon. breaking: true adds ! after the (optional) scope: feat(core)!: subject.
  • BREAKING CHANGE: footer is synthesized when breaking and breakingDescription are set and no existing BREAKING CHANGE / BREAKING-CHANGE footer is present. The synthesized footer goes at the top of the footer block.
  • Footer separators are respected verbatim. ':' prints as : ; ' #' prints as # (no injected space).

Usage

Render a finished message

import { formatCommitMessage } from '@hyperfrontend/versioning/commits/format'

formatCommitMessage({
  type: 'feat',
  scope: ['versioning', 'questions'],
  subject: 'add x',
  breaking: true,
  breakingDescription: 'removed Y',
  footers: [{ key: 'Closes', value: '#1', separator: ':' }],
})
// => 'feat(versioning,questions)!: add x\n\nBREAKING CHANGE: removed Y\nCloses: #1'

Drive a live header countdown

import { countHeaderLength } from '@hyperfrontend/versioning/commits/format'

const remaining = 72 - countHeaderLength({ type: 'feat', scope: ['core'] }, userInput)

Edit an existing commit

import { parseConventionalCommit } from '@hyperfrontend/versioning'
import { toDraft, formatCommitMessage } from '@hyperfrontend/versioning/commits/format'

const draft = toDraft(parseConventionalCommit('feat: add login'))
formatCommitMessage({ ...draft, subject: 'add sso login' })
// => 'feat: add sso login'

See Also

API Reference§

ƒ Functions

§function

countHeaderLength(draft: CommitDraft, subject: string): number

Computes the character length of the final header including the type(scope)!: prefix and the supplied subject. Powers the live 72-character countdown shown during the subject prompt.
The subject is passed separately (not read from draft.subject) because the caller typically has not yet committed the in-flight input to the draft.
Parameters
NameTypeDescription
§draft
CommitDraftDraft supplying type, scope, and breaking marker
§subject
stringSubject text being typed
Returns
number
Character length the final header will occupy
Example

Counting a header mid-type

countHeaderLength({ type: 'feat', scope: ['core'] }, 'add login')
// => 'feat(core): add login'.length === 21
§function

formatCommitMessage(draft: CommitDraft): string

Renders a draft as the final commit message string: the exact text that would be written to .git/COMMIT_EDITMSG. Layout: header, blank line, body, blank line, footers.
If draft.breaking and draft.breakingDescription are set and no BREAKING CHANGE:-family footer is already present, one is synthesized at the top of the footer block so the preview mirrors what a compliant parser will read back.
Parameters
NameTypeDescription
§draft
CommitDraftCommit draft to render
Returns
string
Full commit message string with \n separators
Examples

Header only

formatCommitMessage({ type: 'feat', subject: 'add login' })
// => 'feat: add login'

Multi-scope breaking change with closing footer

formatCommitMessage({
  type: 'feat',
  scope: ['versioning', 'questions'],
  subject: 'add x',
  breaking: true,
  breakingDescription: 'removed Y',
  footers: [{ key: 'Closes', value: '#1', separator: ':' }],
})
// => 'feat(versioning,questions)!: add x\n\nBREAKING CHANGE: removed Y\nCloses: #1'
§function

formatHeader(draft: CommitDraft): string

Builds the header line (type(scope)!: subject) from a draft.
Missing fields render as empty; this is intentional so the header can be displayed incrementally during authoring (e.g. before the subject has been typed). Scopes are comma-joined to match the parser's multi-scope format.
Parameters
NameTypeDescription
§draft
CommitDraftDraft to render (may be partial)
Returns
string
Header line; never contains a newline
Examples

Rendering a complete header

formatHeader({ type: 'feat', scope: ['core'], subject: 'add login' })
// => 'feat(core): add login'

Rendering a multi-scope breaking-change header

formatHeader({ type: 'feat', scope: ['versioning', 'questions'], subject: 'add x', breaking: true })
// => 'feat(versioning,questions)!: add x'

Rendering a partially-built draft mid-session

formatHeader({ type: 'fix' })
// => 'fix: '
§function

toDraft(commit: ConventionalCommit): CommitDraft

Promotes any ConventionalCommit to a CommitDraft. Useful for editing a parsed commit inside an authoring session.
Parameters
NameTypeDescription
§commit
ConventionalCommitParsed conventional commit
Returns
CommitDraft
A draft carrying the same fields as the commit
Example

Promoting a parsed commit to an editable draft

toDraft(parseConventionalCommit('feat: add login'))
// => { type: 'feat', scope: [], subject: 'add login', footers: [], breaking: false }

◈ Interfaces

§interface

CommitDraft

In-progress version of a ConventionalCommit accumulated field-by-field during an interactive authoring session. Every field is optional so the formatter can render intermediate states (e.g. the header as it exists after only type has been picked).
Properties
§readonly type?:stringCommit type (feat, fix, docs, etc.)
§readonly scope?:unknownScopes in parentheses (empty or omitted = no scope)
§readonly subject?:stringSubject line
§readonly body?:stringFull commit body
§readonly footers?:unknownFooter trailers: caller may leave out BREAKING CHANGE:; the formatter will synthesize it when needed
§readonly breaking?:booleanWhether this is a breaking change: controls the ! subject marker
§readonly breakingDescription?:stringBreaking change description: surfaces as a BREAKING CHANGE: footer when none is already present