@hyperfrontend/ versioning§
Versioning library with changelog parsing, conventional commits, and semver flow orchestration.
What is @hyperfrontend/versioning?
@hyperfrontend/versioning provides a comprehensive toolkit for managing software versioning in JavaScript/TypeScript projects. The library is built on a purely functional architecture with factory functions, immutable data structures, and composable operations.
Key Features
Interactive Commit Author (
cz)npx czlaunches a keystroke-live conventional-commit session (type, scope, subject countdown, body, breaking marker, issues, preview, commit) with clipboard-paste support and terminal-resize redrawCommit Validator (
cl)npx cl <path>plugs into anycommit-msggit hook to enforce your rulesetParse CHANGELOG.md files into structured objects with lossless round-tripping
Parse, validate, format, and classify messages following the Conventional Commits specification
Parse, compare, increment, and validate semantic versions
Query npm registry for published versions and package metadata
Generate platform-specific compare URLs for changelog entries (GitHub, GitLab, Bitbucket, Azure DevOps)
Intelligent commit classification ensures changelogs only include relevant commits
Build complex versioning workflows from simple, pure functions
Zero External Dependencies
Self-contained implementation with no third-party runtime dependencies
Why Use @hyperfrontend/versioning?
Type-Safe Changelog Manipulation
Working with CHANGELOG.md files programmatically typically involves fragile string manipulation. This library parses changelogs into fully typed data structures with factory functions for creating entries, sections, and items. Modify changelog content with confidence using immutable operations and round-trip safely back to markdown.
Unified Versioning Primitives
Version management requires coordinating semver parsing, commit analysis, changelog generation, and registry queries. This library provides all these primitives in one cohesive package with consistent APIs. Query npm for published versions, parse commit history, calculate version bumps, and generate changelog entries, all composable into custom release workflows.
Zero-Dependency CI Integration
Designed for automated pipelines where minimal attack surface matters. Zero external runtime dependencies and state-machine parsing ensure predictable performance on any input. All parsers enforce input length limits to prevent resource exhaustion.
One-Stop Commit Toolchain
The interactive cz and validator cl bins cover the same ground as commitizen + cz-conventional-changelog + @commitlint/cli, in one package, without patch-package workarounds, with a config-driven session (commit.config.{js,mjs,cjs}), a live 72-char header countdown, and scope choices derived from staged files. Acknowledgment to those projects: they shaped the conventions this library now implements natively.
Installation
npm install @hyperfrontend/versioning
Quick Start
Parsing a Changelog
import { parseChangelog } from '@hyperfrontend/versioning'
import fs from 'fs'
// Parse existing changelog content
const content = fs.readFileSync('CHANGELOG.md', 'utf-8')
const changelog = parseChangelog(content)
// Access entries
for (const entry of changelog.entries) {
console.log(`Version ${entry.version} - ${entry.date}`)
for (const section of entry.sections) {
console.log(` ${section.heading}: ${section.items.length} changes`)
}
}
// Access metadata
// Formats: 'keep-a-changelog' (https://keepachangelog.com), 'conventional', etc.
console.log(changelog.metadata.format)
Parsing Conventional Commits
import { parseConventionalCommit } from '@hyperfrontend/versioning'
const commit = parseConventionalCommit('feat(api): add user authentication')
console.log(commit.type) // 'feat'
console.log(commit.scope) // ['api'], an array because `feat(a,b): x` names two
console.log(commit.subject) // 'add user authentication'
console.log(commit.breaking) // false
Checking for Breaking Changes
import { parseConventionalCommit } from '@hyperfrontend/versioning'
// Breaking change via !
const commit1 = parseConventionalCommit('feat(api)!: remove deprecated endpoint')
console.log(commit1.breaking) // true
// Breaking change via footer
const commit2 = parseConventionalCommit(\`fix: update API response format
BREAKING CHANGE: Response structure has changed\`)
console.log(commit2.breaking) // true
console.log(commit2.breakingDescription) // 'Response structure has changed'
API Overview
The package publishes one subpath per concern, and the name of the import is the job it does. commits parses, validates, classifies and authors conventional commit messages. changelog reads and writes CHANGELOG.md. semver parses, compares and increments versions and ranges. git, registry and workspace cover the three things a release has to ask the world outside the process: the repository, the npm registry, and the packages on disk. repository turns a remote URL into the compare links a changelog entry carries. flow composes the rest into a release. The root entry re-exports all of them.
parseConventionalCommit is where most work starts: hand it the raw message and it returns a ConventionalCommit carrying type, subject, breaking, the parsed footers and a scope that is a readonly string[] rather than a string, because feat(a,b): x names two. Its type and breaking go to getSemverBump, which answers major, minor, patch or none, and that answer plus a parsed version goes to increment. All three are pure functions over plain data.
parseChangelog turns a CHANGELOG.md into an addressable tree of entries, sections and items, and serializeChangelog writes that tree back to markdown, so a file read and rewritten unedited comes back as itself. Those parsers, and the commit parsers beside them, are hand-written character state machines rather than regular expressions, and each refuses oversized input before it begins, which is why no pathological changelog can stall a release job.
Two bins ship alongside the API, and neither asks you to import anything: npx cz runs the interactive session that authors a conventional commit, and npx cl <path> validates one message from a commit-msg hook. For unattended releases, createConventionalFlow assembles the ordered steps (fetch the published version, analyse commits, calculate the bump, generate the changelog entry, update package.json, write, commit, tag) and executeFlow runs them against a virtual file system, so a dry run reports the whole diff without touching disk.
Every model, option, step and outcome type is in the API reference.
Compatibility
Runs on
- Node.js>=18.0.0supported
- Browsersnot supported
- Web Workersnot supported
Ships as
- ESMTree-shakeable
- CJSNode and older bundlers
- CLIcz, cl
Security
All parsers use state-machine tokenization with O(n) complexity and enforce input length limits (commit messages: 10KB, changelog files: 1MB) to prevent resource exhaustion. Character-by-character parsing eliminates regex-based vulnerabilities.
Architecture Highlights§
Parsing is bounded and predictable: every parser is a character-by-character state machine rather than a regular expression, so no input pattern can trigger catastrophic backtracking. Each entry point also rejects oversized input before processing it (10,000 characters for a commit message, 1 MB for a changelog file, 256 for a version string, 214 for a package name).
API Reference§
Module Structure
39 modules · 1856 total exports
Version management toolkit with semver, changelog, git operations, and workspace coordination.
ƒ Functions
Parameters
Returns
ProjectExample
Add an internal dependent to a project
import { discoverProject, addDependent } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
const updated = addDependent(project, '@myorg/new-app')
console.log('Dependents now:', updated.internalDependents)
}addEntry( changelog: Changelog, entry: ChangelogEntry, options?: AddEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to add to |
§entry | ChangelogEntry | The entry to add |
§options? | AddEntryOptions | Optional add options |
Returns
ChangelogExample
Adding a new entry to a changelog
const newChangelog = addEntry(changelog, {
version: '1.2.0',
date: '2024-01-15',
unreleased: false,
sections: [...]
})addItemToEntry( changelog: Changelog, version: string, sectionType: string, item: ChangelogItem): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog containing the entry to modify |
§version | string | The version identifier of the entry to update |
§sectionType | string | Category identifier for grouping changes (e.g., 'features', 'fixes') |
§item | ChangelogItem | Description of the change with optional scope and metadata |
Returns
ChangelogExample
Adding a feature item to an entry
const item = { description: 'Add dark mode support', scope: 'ui' }
const updated = addItemToEntry(changelog, '1.2.0', 'features', item)
// Item added to the features section of version 1.2.0Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to extend |
§step | FlowStep | The step to add |
Returns
VersionFlowExample
Adding a custom step to a flow
import { addStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const extended = addStep(flow, createNoopStep('custom', 'Custom Step'))
console.log(extended.steps.length)
// => original steps + 1Parameters
Returns
ChangelogExample
Adding an unreleased entry with sections
const sections = [createChangelogSection('features', 'Added', [item])]
const updated = addUnreleasedEntry(changelog, sections)
// => Changelog with unreleased entry at the topParameters
| Name | Type | Description |
|---|---|---|
§message | string | The new commit message to use |
§options | Omit< | Configuration for the commit operation(default: {}) |
Returns
GitCommitExample
Amend the last commit with a new message
const commit = amendCommit('feat: improved feature')appendChangelog( source: Changelog, target: Changelog, position: "start" | "end"): Changelog
Parameters
Returns
ChangelogExample
Appending entries to a changelog
const combined = appendChangelog(mainChangelog, newChangelog, 'start')
// newChangelog entries appear before mainChangelog entriesapplyBumps( tree: Tree, workspace: Workspace, bumps: unknown, options: BatchUpdateOptions): BatchUpdateResult
Changes are buffered in the tree until
commitChanges() is called, enabling atomic commits and rollback on failure.Parameters
Returns
BatchUpdateResultExample
Apply planned bumps to workspace using VFS Tree
import { createTree, commitChanges } from '@hyperfrontend/project-scope'
import { applyBumps, calculateCascadeBumps } from '@hyperfrontend/versioning'
const tree = createTree(workspaceRoot)
const cascadeResult = calculateCascadeBumps(workspace, directBumps)
const updateResult = applyBumps(tree, workspace, cascadeResult.bumps)
if (updateResult.success) {
commitChanges(tree) // Atomic commit of all changes
console.log(`Updated ${updateResult.updated.length} packages`)
} else {
console.error('Some updates failed:', updateResult.failed)
// No commitChanges() call - changes are discarded
}Parameters
| Name | Type | Description |
|---|---|---|
§projects | unknown | List of projects to analyze |
Returns
DependencyGraphAnalysisExample
Build a complete dependency graph
import { buildDependencyGraph, discoverPackages } from '@hyperfrontend/versioning'
const { projects } = discoverPackages()
const analysis = buildDependencyGraph(projects)
// Get packages that depend on 'lib-utils'
const dependents = analysis.dependencyGraph.get('lib-utils') ?? []
// Get packages in topological order for building
const buildOrder = getTopologicalOrder(analysis)Parameters
| Name | Type | Description |
|---|---|---|
§type | GitRefType | Reference type |
§name | string | Reference name |
§remote? | string | Remote name (for remote type) |
Returns
stringExample
Build full reference names from type and name
buildRefName('branch', 'main') // 'refs/heads/main'
buildRefName('tag', 'v1.0.0') // 'refs/tags/v1.0.0'
buildRefName('remote', 'main', 'origin') // 'refs/remotes/origin/main'Parameters
Returns
stringExample
Build tag names with different formats
buildTagName('@scope/pkg', '1.2.3') // '@scope/pkg@1.2.3'
buildTagName('utils', '1.0.0', 'v${version}') // 'v1.0.0'
buildTagName('pkg', '2.0.0', '${package}-v${version}') // 'pkg-v2.0.0'calculateCascadeBumps( workspace: Workspace, directBumps: unknown, options: CascadeBumpOptions): CascadeBumpResult
When packages are directly bumped (e.g., due to commits), their dependents may also need version bumps. This function calculates all affected packages.
Parameters
| Name | Type | Description |
|---|---|---|
§workspace | Workspace | Workspace containing projects and dependency graph |
§directBumps | unknown | Packages with direct changes |
§options | CascadeBumpOptions | Configuration for cascade bump calculation(default: {}) |
Returns
CascadeBumpResultExample
Calculate cascade bumps for a workspace
import { calculateCascadeBumps } from '@hyperfrontend/versioning'
// If lib-utils is getting a minor bump
const result = calculateCascadeBumps(workspace, [
{ name: 'lib-utils', bumpType: 'minor' }
])
// result.bumps includes lib-utils and all packages that depend on it
for (const bump of result.bumps) {
console.log(`${bump.name}: ${bump.currentVersion} -> ${bump.nextVersion}`)
}calculateCascadeBumpsFromPackage( workspace: Workspace, packageName: string, bumpType: BumpType, options: CascadeBumpOptions): CascadeBumpResult
Parameters
Returns
CascadeBumpResultExample
Calculate cascade bumps from a single package
import { discoverWorkspace, calculateCascadeBumpsFromPackage } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const result = calculateCascadeBumpsFromPackage(workspace, '@myorg/utils', 'minor')
console.log(`${result.totalAffected} packages will be bumped`)
for (const bump of result.bumps) {
console.log(`${bump.name}: ${bump.currentVersion} -> ${bump.nextVersion}`)
}cancelled result with an optional error.Parameters
Returns
StepResultExample
Cancelling after an executor throws
try { await executor() } catch (err) { return cancelled(err as Error) }Parameters
Returns
CompatibilityResultExample
Checking schema compatibility before merge
const result = checkSchemaCompatibility(mainChangelog, branchChangelog)
if (!result.compatible) {
console.log('Schema differences:', result.differences)
}npm view proves absence or leaves the answer unknown. Only a 404 from the registry proves absence, and npm reports both a missing package and a missing version that way. Every other outcome, including a timeout that produces no output at all, leaves the published state unknown and must not be read as "not published".
Parameters
| Name | Type | Description |
|---|---|---|
§error | unknown | The error thrown by the invocation |
Returns
NpmLookupFailureExample
Classifying a lookup failure
try {
execFileSync('npm', ['view', name, 'version'])
} catch (error) {
const failure = classifyNpmError(error)
if (failure.kind === 'absent') return null
throw createRegistryUnavailableError({ ...failure, packageName: name })
}Returns
ChangelogExample
Cloning a changelog
const copy = cloneChangelog(changelog)
// Independent deep copy, safe to mutateParameters
| Name | Type | Description |
|---|---|---|
§input | string | The string to coerce |
Returns
SemVerExample
Coerce partial versions to valid semver
coerceVersion('1') // { major: 1, minor: 0, patch: 0, ... }
coerceVersion('1.2') // { major: 1, minor: 2, patch: 0, ... }
coerceVersion('v2.0') // { major: 2, minor: 0, patch: 0, ... }Parameters
| Name | Type | Description |
|---|---|---|
§changelogs | unknown | Array of changelogs to combine |
§options? | MergeOptions | Optional merge options |
Returns
ChangelogExample
Combining multiple changelogs
const unified = combineChangelogs([pkg1Changelog, pkg2Changelog], {
strategy: 'merge-sections',
})
// All entries from both changelogs merged with conflict resolutionParameters
| Name | Type | Description |
|---|---|---|
§message | string | Commit message (subject line) |
§options | CreateCommitOptions | Create options(default: {}) |
Returns
GitCommitExample
Create a new commit
const commit = createCommit('feat: add new feature')
const commit = createCommit('fix: resolve bug', { body: 'Detailed description' })Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash to check |
§options | Pick< | Additional options(default: {}) |
Returns
booleanExample
Check if a commit exists in the repository
if (commitExists('abc123def')) {
console.log('Commit found in repository')
}Parameters
Returns
ChangelogExample
Compacting a changelog
const compacted = compact(changelog)
// Empty sections and entries removed (unreleased kept)
const strict = compact(changelog, false)
// Even empty unreleased entry is removedReturns
-1 | 0 | 1Example
Compare two semantic versions
compare(parseVersion('1.0.0'), parseVersion('2.0.0')) // -1
compare(parseVersion('1.0.0'), parseVersion('1.0.0')) // 0
compare(parseVersion('2.0.0'), parseVersion('1.0.0')) // 1Returns
numberExample
Sort references alphabetically by name
const refs = [refB, refA, refC]
refs.sort(compareRefsByName) // => [refA, refB, refC]Returns
numberExample
Sort tags by version (newest first)
const tags = [tagV1, tagV2, tagV3]
tags.sort(compareTagsByVersion) // => [tagV3, tagV2, tagV1]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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Draft supplying type, scope, and breaking marker |
§subject | string | Subject text being typed |
Returns
numberExample
Counting a header mid-type
countHeaderLength({ type: 'feat', scope: ['core'] }, 'add login')
// => 'feat(core): add login'.length === 21This step:
- Uses publishedCommit from npm registry (set by fetch-registry step)
- Verifies the commit is reachable from current HEAD
- Gets all commits since that commit (or recent commits if first release/fallback)
- Parses each commit using conventional commit format
- Classifies commits based on scope filtering strategy
- Filters to only release-worthy commits that belong to this project
- effectiveBaseCommit: The verified base commit (null if fallback was used)
- commits: Array of parsed conventional commits (for backward compatibility)
- classificationResult: Full classification result with source attribution
Returns
FlowStepExample
Analyzing commits since last release
import { createAnalyzeCommitsStep, executeStep } from '@hyperfrontend/versioning'
const step = createAnalyzeCommitsStep()
const result = await executeStep(step, context)
// Access analyzed commits
console.log(result.stateUpdates?.commits)
// => [{ type: 'feat', subject: 'add feature' }, ...]Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateAnnotatedTagOptions | Tag creation options |
Returns
GitTagExample
Create an annotated git tag with metadata
const tag = createAnnotatedTag({
name: 'v1.0.0',
commitHash: 'abc123...',
message: 'Release v1.0.0',
taggerName: 'John Doe',
taggerEmail: 'john@example.com',
tagDate: '2026-03-12T10:00:00Z',
})Returns
RangeExample
Create a range that matches any version
const anyRange = createAnyRange()
satisfies(parseVersionStrict('999.999.999'), anyRange) // => trueconfig (defaults filled in) and the effective step list, allowing bin wrappers to introspect before running.Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateAuthorSessionOptions | Optional overrides for steps and partial config(default: {}) |
Returns
AuthorSessionrunAuthorSessionExamples
Using the conventional preset and default config
const session = createAuthorSession()
await runAuthorSession(session)Overriding only the validate ruleset
createAuthorSession({ config: { validateRuleset: customRuleset } })This is a variant that skips commit/tag creation, intended to be used when releasing multiple packages in sequence with a single commit at the end.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Releasing multiple packages in batch
import { createBatchReleaseFlow, executeFlow } from '@hyperfrontend/versioning'
// Release multiple packages without individual commits
for (const pkg of ['lib-a', 'lib-b', 'lib-c']) {
await executeFlow(createBatchReleaseFlow(), pkg, '/workspace')
}
// Then create a single combined commitParameters
| Name | Type | Description |
|---|---|---|
§description? | string | Optional description of the breaking change |
Returns
BreakingChangeExample
Creating a breaking change from subject
createBreakingFromSubject('remove deprecated API')
// => { isBreaking: true, description: 'remove deprecated API', source: 'subject' }Parameters
| Name | Type | Description |
|---|---|---|
§ttl | number | Time-to-live in milliseconds (default: 60000 = 1 minute)(default: 60000) |
Returns
CacheExample
Using a cache with custom TTL
const cache = createCache(30000) // 30 second TTL
cache.set('key', { data: 'value' })
const value = cache.get<{ data: string }>('key')This step:
- Analyzes commit types and breaking changes
- Determines the appropriate bump level
- Calculates the next version
- bumpType: 'major' | 'minor' | 'patch' | 'none'
- nextVersion: Calculated next version string
Returns
FlowStepExample
Running the calculate-bump step
import { createCalculateBumpStep, executeStep } from '@hyperfrontend/versioning'
const step = createCalculateBumpStep()
const result = await executeStep(step, context)
// Get the calculated bump
console.log(result.stateUpdates?.bumpType)
// => 'minor'
console.log(result.stateUpdates?.nextVersion)
// => '1.2.0'This step cascades version updates to packages that depend on the updated package.
Returns
FlowStepExample
Updating dependent packages in a monorepo
import { createCascadeDependenciesStep, executeStep } from '@hyperfrontend/versioning'
const step = createCascadeDependenciesStep()
const result = await executeStep(step, contextWithTrackDeps)
// Dependent packages are updated
console.log(result.message)Parameters
Returns
ChangelogExample
Creating a changelog with custom source
const changelog = createChangelog({ source: 'CHANGELOG.md' })
// => { source: 'CHANGELOG.md', header: { title: '# Changelog', ... }, entries: [] }createChangelogEntry( version: string, options?: Partial< Omit< ChangelogEntry, "version">>): ChangelogEntry
Parameters
| Name | Type | Description |
|---|---|---|
§version | string | The version string (e.g., '1.0.0') |
§options? | Partial< | Optional configuration for date, sections, and other properties |
Returns
ChangelogEntryExample
Creating a changelog entry with date and sections
const entry = createChangelogEntry('1.0.0', {
date: '2024-01-15',
sections: [createChangelogSection('features', 'Added', items)],
})createChangelogItem( description: string, options?: Partial< Omit< ChangelogItem, "description">>): ChangelogItem
Parameters
| Name | Type | Description |
|---|---|---|
§description | string | The description text of the change |
§options? | Partial< | Optional configuration for scope, commits, references, and breaking flag |
Returns
ChangelogItemExample
Creating a changelog item with options
const item = createChangelogItem('Add user authentication', {
scope: 'auth',
breaking: false,
references: [{ number: 42, type: 'issue' }],
})Parameters
Returns
ChangelogLinkExample
Creating a version comparison link
const link = createChangelogLink('1.0.0', 'https://github.com/org/repo/compare/v0.9.0...v1.0.0')
// => { label: '1.0.0', url: 'https://github.com/...' }Only generates changelog, no version bumps or git operations.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a changelog-only flow
import { createChangelogOnlyFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createChangelogOnlyFlow()
const result = await executeFlow(flow, 'my-lib', '/workspace')
// Only changelog is updated, no version bump
console.log(result.state.changelogEntry)createChangelogSection( type: ChangelogSectionType, heading: string, items: unknown): ChangelogSection
Parameters
| Name | Type | Description |
|---|---|---|
§type | ChangelogSectionType | The type of section (features, fixes, breaking, etc.) |
§heading | string | The display heading for the section |
§items | unknown | Optional array of changelog items in this section(default: []) |
Returns
ChangelogSectionExample
Creating a features section with items
const section = createChangelogSection('features', 'Added', [
createChangelogItem('New dashboard widget'),
])In independent versioning, when a dependency is bumped, dependents may also need version bumps.
Returns
FlowStepExample
Checking if dependent packages need bumps
import { createCheckDependentBumpsStep, executeStep } from '@hyperfrontend/versioning'
const step = createCheckDependentBumpsStep()
const result = await executeStep(step, contextWithTrackDeps)
// Result indicates if any dependents need bumps
console.log(result.message)This step prevents redundant releases by checking if the calculated version is already published.
Returns
FlowStepExample
Preventing duplicate releases
import { createCheckIdempotencyStep, executeStep } from '@hyperfrontend/versioning'
const step = createCheckIdempotencyStep()
const result = await executeStep(step, context)
// If version already published, step skips with bumpType: 'none'
if (result.status === 'skipped') {
console.log('Version already published')
}Returns
FlowStepExample
Generating a combined changelog for all packages
import { createCombinedChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createCombinedChangelogStep()
const result = await executeStep(step, context)
// Single changelog for all packages
console.log(result.message)Returns
CommitRefExample
Creating a commit reference
const ref = createCommitRef('abc1234def5678', 'https://github.com/org/repo/commit/abc1234def5678')
// => { hash: 'abc1234def5678', shortHash: 'abc1234', url: 'https://...' }Parameters
| Name | Type | Description |
|---|---|---|
§operator | RangeOperator | The comparison operator |
§version | SemVer | The version to compare against |
Returns
ComparatorExample
Create a comparator for version comparison
createComparator('>=', parseVersionStrict('1.0.0'))
// => { operator: '>=', version: { major: 1, minor: 0, patch: 0, ... } }Parameters
| Name | Type | Description |
|---|---|---|
§comparators | unknown | Array of comparators (AND logic) |
Returns
ComparatorSetExample
Create a comparator set with AND logic
createComparatorSet([gte100, lt200]) // AND: >=1.0.0 AND <2.0.0Each platform has a different URL format:
- GitHub:
{baseUrl}/compare/{fromCommit}...{toCommit}(three dots) - GitLab:
{baseUrl}/-/compare/{fromCommit}...{toCommit}(three dots,/-/prefix) - Bitbucket:
{baseUrl}/compare/{toCommit}..{fromCommit}(two dots, reversed order) - Azure DevOps:
{baseUrl}/compare?version=GT{toCommit}&compareVersion=GT{fromCommit}(query params)
custom platforms, a formatCompareUrl function must be provided in the repository config. For unknown platforms, returns null.Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateCompareUrlOptions | Compare URL options including repository, fromCommit, and toCommit |
Returns
stringExample
Creating platform-specific compare URLs
// GitHub
createCompareUrl({
repository: { platform: 'github', baseUrl: 'https://github.com/owner/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://github.com/owner/repo/compare/abc1234...def5678'
// GitLab
createCompareUrl({
repository: { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://gitlab.com/group/project/-/compare/abc1234...def5678'
// Bitbucket (reversed order)
createCompareUrl({
repository: { platform: 'bitbucket', baseUrl: 'https://bitbucket.org/owner/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://bitbucket.org/owner/repo/compare/def5678..abc1234'
// Azure DevOps
createCompareUrl({
repository: { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://dev.azure.com/org/proj/_git/repo/compare?version=GTdef5678&compareVersion=GTabc1234'
// Custom formatter
createCompareUrl({
repository: {
platform: 'custom',
baseUrl: 'https://my-git.internal/repo',
formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
},
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://my-git.internal/diff/abc1234/def5678'createConventionalCommit( type: string, subject: string, options?: CreateConventionalCommitOptions): ConventionalCommit
Parameters
Returns
ConventionalCommitExample
Creating conventional commits
createConventionalCommit('feat', 'add user authentication')
// => { type: 'feat', subject: 'add user authentication', scope: [], footers: [], breaking: false, raw: 'feat: add user authentication' }
createConventionalCommit('fix', 'resolve memory leak', { scope: ['core'], breaking: true })
// => { type: 'fix', subject: 'resolve memory leak', scope: ['core'], breaking: true, ... }
createConventionalCommit('feat', 'add x', { scope: ['versioning', 'questions'] })
// => { ..., scope: ['versioning', 'questions'], raw: 'feat(versioning,questions): add x' }This flow follows the standard conventional commits workflow:
- Fetch published version from registry
- Resolve repository configuration (for compare URLs)
- Analyze commits since last release
- Calculate version bump based on commit types
- Check if version already published (idempotency)
- Generate changelog entry (with compare URL if repository resolved)
- Update package.json version
- Write changelog to file
- Create git commit (optional)
- Create git tag (optional, typically after publish)
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a conventional versioning flow
import { createConventionalFlow, executeFlow } from '@hyperfrontend/versioning'
// Use defaults
const flow = createConventionalFlow()
// With overrides
const customFlow = createConventionalFlow({
skipTag: false,
releaseTypes: ['feat', 'fix'],
})
const result = await executeFlow(flow, 'lib-utils', '/workspace')No compare URLs will be generated.
Returns
RepositoryResolutionExample
Disabling repository resolution
const config = createDisabledResolution()
// { mode: 'disabled' }Convenience function that creates a flow with dryRun enabled.
Parameters
| Name | Type | Description |
|---|---|---|
§preset | FlowPreset | The preset to use(default: 'conventional') |
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a dry-run flow for preview
import { createDryRunFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createDryRunFlow('conventional')
const result = await executeFlow(flow, 'my-lib', '/workspace')
// Preview changes without modifying files
console.log('Would release:', result.state.nextVersion)Returns
ChangelogExample
Creating an empty changelog with standard header
const changelog = createEmptyChangelog()
// => Changelog with standard Keep a Changelog header and no entriescreateEmptyCommit( message: string, options: Omit< CreateCommitOptions, "allowEmpty">): GitCommit
Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Text for the empty commit |
§options | Omit< | Configuration for the commit operation(default: {}) |
Returns
GitCommitExample
Create an empty commit for CI triggers
const commit = createEmptyCommit('chore: trigger CI')Returns
RangeExample
Create a range that matches exactly one version
const exact = createExactRange(parseVersionStrict('1.2.3'))
satisfies(parseVersionStrict('1.2.3'), exact) // => true
satisfies(parseVersionStrict('1.2.4'), exact) // => falseParameters
| Name | Type | Description |
|---|---|---|
§repository | RepositoryConfig | The repository config to use |
Returns
RepositoryResolutionExample
Using an explicit repository configuration
const config = createExplicitResolution({
platform: 'github',
baseUrl: 'https://github.com/owner/repo'
})Parameters
Returns
FlowStepResultExample
Handling step execution errors
import { createFailedResult } from '@hyperfrontend/versioning'
// In a step handler:
try {
await doOperation()
} catch (err) {
return createFailedResult(err as Error, 'Operation failed')
}This step:
- Queries the registry for the latest published version
- Reads the current version from package.json
- Determines if this is a first release
- publishedVersion: Latest version on registry (null if not published)
- currentVersion: Version from local package.json
- isFirstRelease: True if never published
Returns
FlowStepExample
Fetching published version from registry
import { createFetchRegistryStep, executeStep } from '@hyperfrontend/versioning'
const step = createFetchRegistryStep()
const result = await executeStep(step, context)
// Check if first release
if (result.stateUpdates?.isFirstRelease) {
console.log('First release detected')
}Returns
SemVerExample
Create first 1.0.0 release version
const first = createFirstRelease()
format(first) // => '1.0.0'Similar to synced, but uses a fixed version scheme where the version is explicitly provided rather than calculated from commits.
Parameters
| Name | Type | Description |
|---|---|---|
§version | string | The fixed version to use |
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a fixed version release flow
import { createFixedVersionFlow, executeFlow } from '@hyperfrontend/versioning'
// Release with explicit version, ignoring commit analysis
const flow = createFixedVersionFlow('2.0.0')
const result = await executeFlow(flow, 'workspace', '/workspace')
console.log(result.state.nextVersion)
// => '2.0.0'createFlow( id: string, name: string, steps: unknown, options: CreateFlowOptions): VersionFlow
Parameters
| Name | Type | Description |
|---|---|---|
§id | string | Flow identifier |
§name | string | Human-readable flow name |
§steps | unknown | Ordered steps to execute |
§options | CreateFlowOptions | Optional flow configuration(default: {}) |
Returns
VersionFlowExample
Creating a custom flow
const myFlow = createFlow(
'custom',
'Custom Release Flow',
[fetchStep, analyzeStep, bumpStep],
{ description: 'My custom versioning workflow' }
)This step:
- Groups commits by type/section
- Creates changelog items from commits
- Assembles a complete changelog entry
- changelogEntry: The generated ChangelogEntry
Returns
FlowStepExample
Generating a changelog entry from commits
import { createGenerateChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createGenerateChangelogStep()
const result = await executeStep(step, context)
// Access the generated changelog entry
const entry = result.stateUpdates?.changelogEntry
console.log(entry?.version, entry?.sections.length)
// => '1.2.0', 3Parameters
| Name | Type | Description |
|---|---|---|
§config | GitClientConfig | Client configuration(default: {}) |
Returns
GitClientExample
Create a git client for a repository
const git = createGitClient({ cwd: '/path/to/repo' })
const status = git.getStatus()
const commits = git.getCommitsSince('v1.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateGitCommitOptions | Commit creation options |
Returns
GitCommitExample
Create a git commit model
const commit = createGitCommit({
hash: 'abc123...',
authorName: 'John Doe',
authorEmail: 'john@example.com',
authorDate: '2026-03-12T10:00:00Z',
subject: 'feat: add new feature',
})This step:
- Stages modified files
- Creates a commit with the configured message
- commitHash: Hash of the created commit
Returns
FlowStepExample
Creating and staging a release commit
import { createGitCommitStep, executeStep } from '@hyperfrontend/versioning'
const step = createGitCommitStep()
const result = await executeStep(step, context)
// Get the created commit hash
console.log(result.stateUpdates?.commitHash)
// => 'a1b2c3d4e5f6...'Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateGitRefOptions | Reference creation options |
Returns
GitRefExample
Create a git reference from full name
const ref = createGitRef({
fullName: 'refs/heads/main',
commitHash: 'abc123...',
})This flow is designed for monorepos where each package is versioned independently:
- Fetch published version from registry
- Analyze commits since last release
- Calculate version bump based on commit types
- Check for dependent package bumps (cascade)
- Check if version already published (idempotency)
- Generate changelog entry
- Update package.json version
- Cascade dependency updates
- Write changelog to file
- Create git commit
- Create git tag
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating an independent versioning flow
import { createIndependentFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createIndependentFlow()
const result = await executeFlow(flow, 'lib-utils', '/workspace')
// Inspect the version that was released
console.log(result.state.nextVersion)Parameters
| Name | Type | Description |
|---|---|---|
§inferenceOrder | unknown | Order to try inference sources (default: package-json first)(default: ...) |
Returns
RepositoryResolutionExample
Configuring inference order
// Default order: package.json → git remote
const config = createInferredResolution()
// Custom order: git remote → package.json
const customOrder = createInferredResolution(['git-remote', 'package-json'])Returns
SemVerExample
Create initial 0.0.0 version
const initial = createInitialVersion()
format(initial) // => '0.0.0'Parameters
Returns
IssueRefExample
Creating issue and PR references
const issueRef = createIssueRef(42, 'issue', 'https://github.com/org/repo/issues/42')
// => { number: 42, type: 'issue', url: 'https://...' }
const prRef = createIssueRef(123, 'pull-request')
// => { number: 123, type: 'pull-request', url: undefined }Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateLightweightTagOptions | Tag creation options |
Returns
GitTagExample
Create a lightweight git tag
const tag = createLightweightTag({
name: 'v1.0.0',
commitHash: 'abc123...',
})Skips changelog and tag creation.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a minimal release flow
import { createMinimalFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createMinimalFlow()
const result = await executeFlow(flow, 'my-lib', '/workspace')
// Quick release without changelog or tags
console.log('Released:', result.state.nextVersion)Returns
BreakingChangeExample
Creating a non-breaking change
createNonBreaking()
// => { isBreaking: false, source: 'none' }Parameters
Returns
FlowStepExample
Creating a placeholder step
import { createNoopStep, executeStep } from '@hyperfrontend/versioning'
const step = createNoopStep('placeholder', 'Placeholder Step')
const result = await executeStep(step, context)
console.log(result.status)
// => 'success'Parameters
| Name | Type | Description |
|---|---|---|
§config | RegistryConfig | Registry configuration(default: {}) |
Returns
RegistryExample
Creating an npm registry client
const registry = createNpmRegistry()
const version = await registry.getLatestVersion('@hyperfrontend/utils')Parameters
| Name | Type | Description |
|---|---|---|
§options | CreatePackageInfoOptions | Configuration for the package info |
Returns
PackageInfoExample
Creating package info from registry data
const info = createPackageInfo({
name: '@scope/my-package',
latestVersion: '2.0.0',
versions: ['1.0.0', '1.1.0', '2.0.0'],
license: 'MIT'
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateProjectOptions | Project properties |
Returns
ProjectExample
Create a new Project object
import { createProject, readPackageJson } from '@hyperfrontend/versioning'
const packageJson = readPackageJson('./libs/my-lib/package.json')
const project = createProject({
name: '@myorg/my-lib',
version: '1.0.0',
path: './libs/my-lib',
packageJsonPath: './libs/my-lib/package.json',
packageJson,
changelogPath: './libs/my-lib/CHANGELOG.md',
})Returns
FlowStepExample
Pushing the tag to remote
import { createPushTagStep, executeStep } from '@hyperfrontend/versioning'
const step = createPushTagStep()
const result = await executeStep(step, context)
// Tag is pushed to remote
console.log(result.message)
// => 'Pushed tag: my-lib@1.2.0'Parameters
Returns
RangeExample
Create a range with OR logic between sets
createRange([set1, set2], '>=1.0.0 || >=2.0.0 <3.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§type | "npm" | Type of registry to create(default: 'npm') |
§config | RegistryConfig | Registry configuration(default: {}) |
Returns
RegistryExample
Creating an npm registry client
const registry = createRegistry('npm')
const version = await registry.getLatestVersion('lodash')Normalizes the base URL by stripping trailing slashes and validating that custom platforms have a formatter function.
Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateRepositoryConfigOptions | Repository configuration options |
Returns
RepositoryConfigExample
Creating repository configurations
// GitHub repository
const config = createRepositoryConfig({
platform: 'github',
baseUrl: 'https://github.com/owner/repo'
})
// Custom platform
const customConfig = createRepositoryConfig({
platform: 'custom',
baseUrl: 'https://my-git.internal/repo',
formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
})This is a convenience function that combines
parseRepositoryUrl with createRepositoryConfig to produce a ready-to-use configuration.Parameters
| Name | Type | Description |
|---|---|---|
§gitUrl | string | Git repository URL in any supported format |
Returns
RepositoryConfigExample
Creating config from git URLs
const config = createRepositoryConfigFromUrl('https://github.com/owner/repo'))
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
const config = createRepositoryConfigFromUrl('git@gitlab.com:group/project.git')
// → { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' }Returns
SemVerExample
Create a SemVer object from components
createSemVer({ major: 1, minor: 2, patch: 3 })
// => { major: 1, minor: 2, patch: 3, prerelease: [], build: [] }
createSemVer({ major: 1, minor: 0, patch: 0, prerelease: ['beta', '1'] })
// => 1.0.0-beta.1Parameters
| Name | Type | Description |
|---|---|---|
§config | SessionConfig | Resolved session configuration |
Returns
SessionContextExample
Creating a bare context
createSessionContext(config)
// => { draft: {}, candidateScopes: [], defaultScope: undefined, config }Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Explanation for why the step was skipped |
Returns
FlowStepResultExample
Skipping a step conditionally
import { createSkippedResult } from '@hyperfrontend/versioning'
// In a step handler:
if (!config.enabled) {
return createSkippedResult('Feature disabled in config')
}Returns
stringExample
Creating blank line spacing
createSpacing(2, '\n')
// => '\n\n'createStep( id: string, name: string, execute: StepExecutor, options: CreateStepOptions): FlowStep
Parameters
| Name | Type | Description |
|---|---|---|
§id | string | Unique step identifier |
§name | string | Human-readable step name |
§execute | StepExecutor | Step executor function |
§options | CreateStepOptions | Optional step configuration(default: {}) |
Returns
FlowStepExample
Creating a custom fetch step
const fetchStep = createStep(
'fetch-registry',
'Fetch Registry Version',
async (ctx) => {
const version = await ctx.registry.getLatestVersion(ctx.packageName)
return {
status: 'success',
stateUpdates: { publishedVersion: version },
message: `Found published version: ${version}`
}
}
)Parameters
Returns
FlowStepResultExample
Returning a success result with state updates
import { createSuccessResult } from '@hyperfrontend/versioning'
// In a step handler:
return createSuccessResult('Updated 3 files', {
modifiedFiles: ['/path/a.ts', '/path/b.ts', '/path/c.ts']
})Returns
FlowStepExample
Syncing all packages to the same version
import { createSyncAllPackagesStep, executeStep } from '@hyperfrontend/versioning'
const step = createSyncAllPackagesStep()
const result = await executeStep(step, context)
// All packages updated to same version
console.log(result.stateUpdates?.modifiedFiles)This flow maintains the same version across all packages in a monorepo. When any package changes, all packages get the same new version.
Flow steps:
- Fetch published version from registry
- Analyze commits across all packages
- Calculate version bump (highest needed)
- Check if version already published
- Sync all package versions
- Generate combined changelog
- Write changelog to root
- Create git commit
- Create single git tag
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a synced versioning flow
import { createSyncedFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createSyncedFlow()
const result = await executeFlow(flow, 'workspace', '/workspace')
// All packages now share the same version
console.log(`Released v${result.state.nextVersion}`)Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The name for the new tag |
§options | CreateTagOptions | Configuration including optional message for annotated tags(default: {}) |
Returns
GitTagExample
Create lightweight and annotated tags
// Create lightweight tag
const tag = createTag('v1.0.0')
// Create annotated tag
const tag = createTag('v1.0.0', { message: 'Release v1.0.0' })This step:
- Creates an annotated git tag
- Uses the configured tag format
- tagName: Name of the created tag
Returns
FlowStepExample
Creating an annotated version tag
import { createTagStep, executeStep } from '@hyperfrontend/versioning'
const step = createTagStep()
const result = await executeStep(step, context)
// Get the created tag name
console.log(result.stateUpdates?.tagName)
// => 'my-lib@1.2.0'Parameters
| Name | Type | Description |
|---|---|---|
§sections | unknown | Optional array of changelog sections(default: []) |
Returns
ChangelogEntryExample
Creating an unreleased entry
const unreleased = createUnreleasedEntry([
createChangelogSection('features', 'Added', [item]),
])
// => { version: 'Unreleased', date: null, unreleased: true, sections: [...] }This step:
- Updates the version field in package.json
- Tracks the modified files
- modifiedFiles: Adds package.json to list
Returns
FlowStepExample
Updating the package.json version field
import { createUpdatePackageStep, executeStep } from '@hyperfrontend/versioning'
const step = createUpdatePackageStep()
const result = await executeStep(step, context)
// package.json version field is updated
console.log(result.stateUpdates?.modifiedFiles)
// => ['/workspace/libs/my-lib/package.json']This is the main factory function for creating flows. Use this when you want to create a flow based on a standard preset with optional customization.
Parameters
| Name | Type | Description |
|---|---|---|
§preset | FlowPreset | The preset to use (default: 'conventional')(default: 'conventional') |
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating flows from presets
import { createVersionFlow, executeFlow } from '@hyperfrontend/versioning'
// Create a conventional flow
const flow = createVersionFlow('conventional')
// Create an independent flow with custom config
const customFlow = createVersionFlow('independent', {
skipTag: true,
trackDeps: true,
})
const result = await executeFlow(flow, 'lib-utils', '/workspace')Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateVersionInfoOptions | Configuration for the release info |
Returns
VersionInfoExample
Creating version info from registry data
const release = createVersionInfo({
version: '1.2.3',
publishedAt: '2024-01-15T10:30:00Z',
tarball: 'https://registry.npmjs.org/pkg/-/pkg-1.2.3.tgz'
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateWorkspaceOptions | Workspace properties |
Returns
WorkspaceExample
Create a new workspace object
import { createWorkspace, createWorkspaceConfig, createProject } from '@hyperfrontend/versioning'
const projects = new Map([['@myorg/utils', createProject({ ... })]])
const workspace = createWorkspace({
root: '/path/to/workspace',
type: 'nx',
projects,
config: createWorkspaceConfig(),
dependencyGraph: new Map(),
reverseDependencyGraph: new Map(),
})Parameters
| Name | Type | Description |
|---|---|---|
§options? | Partial< | Partial configuration options |
Returns
WorkspaceConfigExample
Create a workspace configuration with defaults
import { createWorkspaceConfig } from '@hyperfrontend/versioning'
const config = createWorkspaceConfig({
patterns: ['packages/*', 'libs/*'],
exclude: ['node_modules', 'dist'],
})This step writes the generated changelog entry to CHANGELOG.md.
Returns
FlowStepExample
Writing the changelog entry to CHANGELOG.md
import { createWriteChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createWriteChangelogStep()
const result = await executeStep(step, context)
// CHANGELOG.md is updated with the new entry
console.log(result.message)
// => 'Updated CHANGELOG.md with version 1.2.0'Returns
ChangelogExample
Deduplicating items across sections
const deduped = deduplicateItems(changelog)
// Duplicate items (same scope:description) are removedscope step. Algorithm (matches locked decision D3):
- A single candidate wins outright.
- Otherwise, if one candidate's path is a prefix of every other
- Otherwise, there is no default.
Parameters
| Name | Type | Description |
|---|---|---|
§candidates | unknown | Scopes produced by discoverScopes |
Returns
stringExamples
Single owner wins
defaultScope([{ name: 'alpha', path: '/repo/libs/alpha' }])
// => 'alpha'Common ancestor wins
defaultScope([
{ name: 'root', path: '/repo' },
{ name: 'alpha', path: '/repo/libs/alpha' },
])
// => 'root'Disjoint ownership has no default
defaultScope([
{ name: 'alpha', path: '/repo/libs/alpha' },
{ name: 'beta', path: '/repo/libs/beta' },
])
// => undefinedParameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to delete |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Delete a tag by name
const deleted = deleteTag('v1.0.0')Parameters
Returns
booleanExample
Check if one project depends on another
import { discoverWorkspace, dependsOn } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (dependsOn(workspace, '@myorg/app', '@myorg/utils')) {
console.log('@myorg/app directly depends on @myorg/utils')
}First checks for exact match in known platforms, then applies heuristics for self-hosted instances (e.g.,
github.company.com → github).Parameters
| Name | Type | Description |
|---|---|---|
§hostname | string | Hostname to detect platform from (e.g., "github.com") |
Returns
RepositoryPlatformExample
Detecting platform from hostname
detectPlatformFromHostname('github.com') // 'github'
detectPlatformFromHostname('gitlab.mycompany.com') // 'gitlab'
detectPlatformFromHostname('custom-git.internal') // 'unknown'Returns
BumpTypeExample
Calculate the difference type between two versions
diff(parseVersion('1.0.0'), parseVersion('2.0.0')) // 'major'
diff(parseVersion('1.0.0'), parseVersion('1.1.0')) // 'minor'
diff(parseVersion('1.0.0'), parseVersion('1.0.1')) // 'patch'Parameters
Returns
ChangelogDiffExample
Comparing two changelogs
const diff = diffChangelogs(mainChangelog, branchChangelog)
console.log(`Added: ${diff.added.length}, Removed: ${diff.removed.length}`)Parameters
| Name | Type | Description |
|---|---|---|
§source | ChangelogEntry | The source entry |
§target | ChangelogEntry | The target entry |
Returns
EntryDiffExample
Computing diff between two entries
const diff = diffEntries(entryV1, entryV2)
// => { version: '1.0.0', changes: [{ path: ['date'], type: 'changed', ... }], sectionsChanged: true }Parameters
Returns
unknownExample
Discover all changelog files within a workspace
import { discoverAllChangelogs } from '@hyperfrontend/versioning'
const changelogs = discoverAllChangelogs('/path/to/workspace')
for (const changelog of changelogs) {
console.log(`${changelog.projectPath} -> ${changelog.path}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscoveryOptions | Discovery options(default: {}) |
Returns
DiscoveryResultExample
Discover all packages within a workspace
import { discoverPackages } from '@hyperfrontend/versioning'
// Discover all packages in current workspace
const result = discoverPackages()
// Discover with custom patterns
const result = discoverPackages({
patterns: ['packages/*/package.json'],
includeChangelogs: true
})
// Access discovered projects
for (const project of result.projects) {
console.log(`${project.name}@${project.version}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Path to project directory or package.json |
Returns
ProjectExample
Discover a single project by path
import { discoverProject } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
console.log(`Found ${project.name}@${project.version}`)
}
// Also accepts direct package.json path
const project2 = discoverProject('./libs/utils/package.json')Parameters
| Name | Type | Description |
|---|---|---|
§projectName | string | Name of the project to find |
§options | DiscoveryOptions | Discovery options(default: {}) |
Returns
ProjectExample
Discover a project by name within a workspace
import { discoverProjectByName } from '@hyperfrontend/versioning'
const project = discoverProjectByName('@myorg/utils')
if (project) {
console.log(`Found at ${project.path}`)
}
// With custom workspace root
const project2 = discoverProjectByName('@myorg/core', { workspaceRoot: '/custom/path' })project.json / package.json, reads the name field, and returns the unique set as DiscoveredScope entries. Entries inside node_modules or .git are dropped before walking; the caller's scopeFilter is applied after collection.Parameters
| Name | Type | Description |
|---|---|---|
§stagedPaths | unknown | Paths emitted by the staged-paths provider (relative or absolute) |
§options | DiscoverScopesOptions | Filter + cwd configuration |
Returns
unknownExample
Discovering scopes for a set of staged files
discoverScopes(['libs/versioning/src/a.ts', 'libs/questions/src/b.ts'], { cwd: '/repo' })
// => [
// { name: '@hyperfrontend/versioning', path: '/repo/libs/versioning' },
// { name: '@hyperfrontend/questions', path: '/repo/libs/questions' },
// ]done result.Returns
StepResultExample
Returning done from a step
async run(): Promise<StepResult> { return done() }dryRun( flow: VersionFlow, projectName: string, workspaceRoot: string, options: Omit< ExecuteOptions, "dryRun">): Promise< FlowResult>
Convenience wrapper that sets dryRun: true.
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The version flow to execute |
§projectName | string | Name of the project to version |
§workspaceRoot | string | Absolute path to workspace root |
§options | Omit< | Execution options (dryRun forced to true)(default: {}) |
Returns
Promise< FlowResult>Example
Previewing version changes without modifying files
import { dryRun, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const result = await dryRun(flow, 'my-lib', '/workspace')
// Preview what would happen
console.log('Would bump to:', result.state.nextVersion)
// No files modified, no git operations performedReturns
booleanExample
Check if two versions are equal
eq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => true
eq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.1')) // => falseParameters
| Name | Type | Description |
|---|---|---|
§author | string | Author to escape |
Returns
stringExample
Escape an author string for git commit
const safeAuthor = escapeAuthor('John Doe <john@example.com>')
// => 'John Doe <john@example.com>'Parameters
| Name | Type | Description |
|---|---|---|
§path | string | Path to escape |
Returns
stringExample
Escape a file path for git commands
const safePath = escapeFilePath('src/utils/helper.ts')
// => 'src/utils/helper.ts'
escapeFilePath('file$name.ts') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§arg | string | Argument to escape |
Returns
stringExample
Escape a git argument for shell commands
escapeGitArg('John Doe <john@example.com>') // => 'John Doe <john@example.com>'Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Message to escape |
Returns
stringExample
Escape a git commit message
const safeMessage = escapeGitMessage('Release v1.0.0 "stable"')
// => 'Release v1.0.0 \"stable\"'Parameters
| Name | Type | Description |
|---|---|---|
§path | string | Path to escape |
Returns
stringExample
Escape a file path for git commands
escapeGitPath('src/utils/helper.ts') // => 'src/utils/helper.ts'
escapeGitPath('path with spaces/file.ts') // => 'path with spaces/file.ts'Parameters
| Name | Type | Description |
|---|---|---|
§ref | string | Reference to escape |
Returns
stringExample
Escape a git reference for shell commands
escapeGitRef('refs/heads/main') // => 'refs/heads/main'
escapeGitRef('feature/test') // => 'feature/test'
escapeGitRef('ref$invalid') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§pattern | string | Pattern to escape |
Returns
stringExample
Escape a tag pattern for git commands
const safePattern = escapeGitTagPattern('@scope/package@')
// => '@scope/package@'
escapeGitTagPattern('v1.0.*') // throws - '*' is invalidParameters
| Name | Type | Description |
|---|---|---|
§name | string | Package name to escape |
Returns
stringExample
Validating package names for shell safety
escapePackageName('@scope/package') // => '@scope/package'
escapePackageName('simple-pkg') // => 'simple-pkg'
escapePackageName('pkg$invalid') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§version | string | Version string to escape |
Returns
stringExample
Validating version strings for shell safety
escapeVersion('1.2.3') // => '1.2.3'
escapeVersion('1.0.0-beta.1') // => '1.0.0-beta.1'
escapeVersion('1.0.0+build') // => '1.0.0+build'Parameters
Returns
ChangelogExample
Excluding items by scope
const publicChanges = excludeByScope(changelog, ['internal', 'test'])
// Items scoped to 'internal' or 'test' are removedexecuteFlow( flow: VersionFlow, projectName: string, workspaceRoot: string, options: ExecuteOptions): Promise< FlowResult>
This is the main entry point for running a versioning workflow. Steps are executed in order, with state accumulated between steps.
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The version flow to execute |
§projectName | string | Name of the project to version (e.g., 'lib-versioning') |
§workspaceRoot | string | Absolute path to workspace root |
§options | ExecuteOptions | Execution options(default: {}) |
Returns
Promise< FlowResult>Example
Executing a conventional version flow
import { createConventionalFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow({ dryRun: true })
const result = await executeFlow(flow, 'lib-utils', '/path/to/workspace')
console.log(result.summary)
// "Flow success in 234ms: 8 completed, 0 skipped, 0 failed. Version: 1.2.3 → 1.3.0"@scope/package@1.2.3, package@1.2.3, package-v1.2.3 Uses character-by-character parsing (no regex).Parameters
| Name | Type | Description |
|---|---|---|
§tagName | string | Tag name to parse |
Returns
stringExample
Extract package name from tag
extractPackageFromTag('@scope/pkg@1.2.3') // '@scope/pkg'
extractPackageFromTag('lib-utils@1.2.3') // 'lib-utils'
extractPackageFromTag('v1.2.3') // undefinedUnlike
inferRepositoryFromPackageJson, this returns just the URL string without creating a RepositoryConfig. Useful when you need the raw URL.Parameters
| Name | Type | Description |
|---|---|---|
§packageJsonContent | string | Raw JSON string content of package.json |
Returns
stringExample
Extracting raw repository URL
extractRepositoryUrl('{"repository": {"url": "https://github.com/owner/repo"}}')
// → 'https://github.com/owner/repo'
extractRepositoryUrl('{"repository": "github:owner/repo"}')
// → null (shorthand is not a URL)Parameters
| Name | Type | Description |
|---|---|---|
§subject | string | Commit subject line |
Returns
stringExample
Extract scope from conventional commit
extractScope('feat(lib-versioning): add git support') // 'lib-versioning'
extractScope('fix: resolve issue') // undefinedParameters
| Name | Type | Description |
|---|---|---|
§subject | string | Commit subject line |
Returns
stringExample
Extract type from conventional commit
extractType('feat(lib-versioning): add git support') // 'feat'
extractType('fix: resolve issue') // 'fix'
extractType('random message') // undefined@scope/package@1.2.3, package@1.2.3 Uses character-by-character parsing (no regex).Parameters
| Name | Type | Description |
|---|---|---|
§tagName | string | Tag name to parse |
Returns
stringExample
Extract version from various tag formats
extractVersionFromTag('v1.2.3') // '1.2.3'
extractVersionFromTag('@scope/pkg@1.2.3') // '1.2.3'
extractVersionFromTag('release-1.2.3') // '1.2.3'Returns
ChangelogExample
Filtering for breaking changes
const breaking = filterBreakingChanges(changelog)
// Only entries containing breaking changes or items marked as breakingParameters
Returns
ChangelogExample
Filtering entries by date range
const q1 = filterByDateRange(changelog, '2024-01-01', '2024-03-31')
// Entries released in Q1 2024Parameters
Returns
ChangelogExample
Filtering items by scope
const apiChanges = filterByScope(changelog, ['api', 'core'])
// Only items scoped to 'api' or 'core'Parameters
Returns
ChangelogExample
Filtering by semver range
const majors = filterByVersionRange(changelog, '>=1.0.0 <2.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | EntryPredicate | Function that returns true for entries to keep |
Returns
ChangelogExample
Filtering out unreleased entries
const filtered = filterEntries(changelog, (entry) => !entry.unreleased)Parameters
Returns
ChangelogExample
Filtering from a start version
const recent = filterFromVersion(changelog, '2.0.0')
// Only entries for version 2.0.0 and laterParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | ItemPredicate | Function that returns true for items to keep |
Returns
ChangelogExample
Filtering items with references
const withRefs = filterItems(changelog, (item) => item.references.length > 0)
// Only items with issue/PR referencesfilterRecentEntries( changelog: Changelog, count: number, includeUnreleased: boolean): Changelog
Parameters
Returns
ChangelogExample
Getting the most recent entries
const latest = filterRecentEntries(changelog, 5)
// Last 5 released versions (plus unreleased if present)Returns
unknownExample
Filter references by remote
const originRefs = filterRefsByRemote(remoteRefs, 'origin')
const upstreamRefs = filterRefsByRemote(remoteRefs, 'upstream')Parameters
| Name | Type | Description |
|---|---|---|
§refs | unknown | References to filter |
§type | GitRefType | Type to filter by |
Returns
unknownExample
Filter references by type
const branches = filterRefsByType(allRefs, 'branch')
const tags = filterRefsByType(allRefs, 'tag')Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | SectionPredicate | Function that returns true for sections to keep |
Returns
ChangelogExample
Filtering for user-facing sections
const userFacing = filterSections(changelog, (section) =>
['features', 'fixes', 'breaking'].includes(section.type)
)Parameters
Returns
ChangelogExample
Filtering to specific section types
const userChanges = filterSectionTypes(changelog, ['features', 'fixes'])
// Only features and fixes sections remainParameters
Returns
ChangelogExample
Filtering to an end version
const legacy = filterToVersion(changelog, '1.9.9')
// Only entries for versions up to 1.9.9filterVersionRange( changelog: Changelog, startVersion: string, endVersion: string): Changelog
Parameters
Returns
ChangelogExample
Filtering entries within a version range
const range = filterVersionRange(changelog, '1.5.0', '2.0.0')
// Entries from 1.5.0 through 2.0.0Parameters
Returns
Map< string, string>Example
Find changelog files for all packages
import { findChangelogs, discoverPackages } from '@hyperfrontend/versioning'
const { packages } = discoverPackages()
const changelogs = findChangelogs('/workspace', packages)
for (const [projectPath, changelogPath] of changelogs) {
console.log(`${projectPath} -> ${changelogPath}`)
}findInternalDependencies( packageJson: PackageJson, workspacePackageNames: Set< string>): string[ ]
Parameters
| Name | Type | Description |
|---|---|---|
§packageJson | PackageJson | Parsed package.json content |
§workspacePackageNames | Set< | Set of all package names in the workspace |
Returns
string[ ]Example
Find internal dependencies in a package
const internalDeps = findInternalDependencies(packageJson, allPackageNames)
// ['@scope/lib-a', '@scope/lib-b']findInternalDependenciesWithTypes( packageName: string, packageJson: PackageJson, workspacePackageNames: Set< string>): DependencyEdge[ ]
Parameters
| Name | Type | Description |
|---|---|---|
§packageName | string | Name of the package being analyzed |
§packageJson | PackageJson | Parsed package.json content |
§workspacePackageNames | Set< | Set of all package names in the workspace |
Returns
DependencyEdge[ ]Example
Find internal dependencies with type information
import { findInternalDependenciesWithTypes, readPackageJson } from '@hyperfrontend/versioning'
const packageJson = readPackageJson('./libs/my-lib/package.json')
const workspacePackages = new Set(['@myorg/utils', '@myorg/core'])
const edges = findInternalDependenciesWithTypes('@myorg/my-lib', packageJson, workspacePackages)
for (const edge of edges) {
console.log(`${edge.from} -> ${edge.to} (${edge.type})`)
}Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Path to project directory |
Returns
stringExample
Find the changelog for a single project
import { findProjectChangelog } from '@hyperfrontend/versioning'
const changelogPath = findProjectChangelog('./libs/my-lib')
if (changelogPath) {
console.log('Found changelog:', changelogPath)
}Returns
stringExample
Format a version to its canonical string
format(parseVersionStrict('1.2.3')) // => '1.2.3'
format(createSemVer({ major: 1, minor: 0, patch: 0, prerelease: ['beta', '1'] }))
// => '1.0.0-beta.1'.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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Commit draft to render |
Returns
string\n separatorsExamples
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'Parameters
| Name | Type | Description |
|---|---|---|
§comparator | Comparator | The comparator to format |
Returns
stringExample
Format a comparator to its string representation
formatComparator(createComparator('>=', parseVersionStrict('1.0.0')))
// => '>=1.0.0'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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Draft to render (may be partial) |
Returns
stringExamples
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: 'Parameters
Returns
stringExample
Formatting a markdown link
formatLink('1.0.0', 'https://github.com/org/repo/releases/tag/v1.0.0')
// => '[1.0.0](https://github.com/org/repo/releases/tag/v1.0.0)'Returns
stringExample
Format a range to its string representation
formatRange(parseRangeStrict('^1.0.0')) // => '^1.0.0'
formatRange(createAnyRange()) // => '*'Returns
stringExample
Format version without prerelease/build metadata
formatSimple(parseVersionStrict('1.2.3-beta.1+build')) // => '1.2.3'Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
numberExample
Get commits ahead of upstream
const ahead = getAheadCount()
console.log(`${ahead} commits to push`)Returns
unknownExample
Listing available presets
import { getAvailablePresets } from '@hyperfrontend/versioning'
const presets = getAvailablePresets()
// => ['conventional', 'independent', 'synced']Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
numberExample
Get commits behind upstream
const behind = getBehindCount()
console.log(`${behind} commits to pull`)Uses
git diff --name-only base...head which computes the merge-base internally, giving files changed in head since diverging from base.Parameters
| Name | Type | Description |
|---|---|---|
§base | string | Base reference (e.g., 'origin/main', 'v1.0.0') |
§head | string | Head reference (defaults to 'HEAD')(default: 'HEAD') |
§options | DiffOptions | Additional options(default: {}) |
Returns
unknownExample
Get changed files between refs
// Files changed since diverging from main
const files = getChangedFilesBetween('origin/main')
// Files changed between two tags
const files = getChangedFilesBetween('v1.0.0', 'v2.0.0')getChangedFilesBetweenWithStatus( base: string, head: string, options: DiffOptions): unknown
Parameters
| Name | Type | Description |
|---|---|---|
§base | string | Base reference |
§head | string | Head reference (defaults to 'HEAD')(default: 'HEAD') |
§options | DiffOptions | Additional options(default: {}) |
Returns
unknownExample
Get file changes with status information
const changes = getChangedFilesBetweenWithStatus('v1.0.0', 'v2.0.0')
const added = changes.filter(c => c.status === 'added')Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash (full or short) |
§options | Pick< | Additional options(default: {}) |
Returns
GitCommitExample
Get a single commit by hash
const commit = getCommit('abc1234')Parameters
| Name | Type | Description |
|---|---|---|
§options | GitLogOptions | Configuration for retrieving the commit log(default: {}) |
Returns
unknownExample
Get commit log with options
const commits = getCommitLog({ maxCount: 10 })
const recentChanges = getCommitLog({ from: 'v1.0.0', to: 'HEAD' })getCommitsBetween( from: string, to: string, options: Omit< GitLogOptions, "from" | "to">): unknown
Parameters
| Name | Type | Description |
|---|---|---|
§from | string | Starting reference (exclusive) |
§to | string | Ending reference (inclusive, default: HEAD)(default: 'HEAD') |
§options | Omit< | Additional options(default: {}) |
Returns
unknownExample
Get commits between two references
const commits = getCommitsBetween('v1.0.0', 'v1.1.0')Parameters
| Name | Type | Description |
|---|---|---|
§since | string | Reference to start from (exclusive) |
§options | Omit< | Additional options(default: {}) |
Returns
unknownExample
Get commits since a tag
const commits = getCommitsSince('v1.0.0')Uses
git diff-tree to retrieve file changes for a single commit. More efficient than fetching files for all commits when only specific commits need file attribution.Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash (full or abbreviated) |
§options | DiffOptions | Additional options(default: {}) |
Returns
GitCommitWithFilesExample
Get commit with its changed files
const commit = getCommitWithFiles('abc123')
if (commit) {
console.log(`${commit.subject} touched ${commit.files.length} files`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
stringExample
Get the current branch name
const branch = getCurrentBranch()Parameters
Returns
unknownExample
Get projects that the given project depends on
import { discoverWorkspace, getDependencies } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const deps = getDependencies(workspace, '@myorg/app')
console.log(`@myorg/app depends on:`, deps)Returns
numberExample
Get the dependency count for a project
import { discoverProject, getDependencyCount } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project) {
console.log(`${project.name} depends on ${getDependencyCount(project)} internal packages`)
}Returns
numberExample
Get the dependent count for a project
import { discoverProject, getDependentCount } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
console.log(`${project.name} is used by ${getDependentCount(project)} packages`)
}Parameters
Returns
unknownExample
Get projects that depend on a given project
import { discoverWorkspace, getDependents } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const dependents = getDependents(workspace, '@myorg/utils')
console.log(`Packages depending on @myorg/utils:`, dependents)Parameters
Returns
ChangelogEntryExample
Getting an entry by version
const entry = getEntryByVersion(changelog, '1.0.0')
// => ChangelogEntry for 1.0.0 or undefinedParameters
Returns
stringExample
Get expected changelog path for a project
import { getExpectedChangelogPath } from '@hyperfrontend/versioning'
const changelogPath = getExpectedChangelogPath('./libs/my-lib')
// => '/workspace/libs/my-lib/CHANGELOG.md'
const customPath = getExpectedChangelogPath('./libs/my-lib', 'HISTORY.md')
// => '/workspace/libs/my-lib/HISTORY.md'Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Git operation configuration(default: {}) |
Returns
stringExample
Get the current HEAD commit
const head = getHead()Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the current HEAD commit hash
const hash = getHeadHash()
// => 'abc123def456789012345678901234567890abcd'Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the short HEAD commit hash
const shortHash = getHeadShortHash()
// => 'abc123d'Parameters
| Name | Type | Description |
|---|---|---|
§options | ListTagsOptions | Tag options with optional pattern(default: {}) |
Returns
GitTagExample
Get the latest tag by creation date
const latest = getLatestTag()
const latestVersion = getLatestTag({ pattern: 'v' })Parameters
| Name | Type | Description |
|---|---|---|
§useAsterisks | boolean | Whether to use * instead of - |
Returns
stringExample
Getting list markers
getListMarker(false) // => '- '
getListMarker(true) // => '* 'Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of modified files
const modified = getModifiedFiles()
// => ['src/utils.ts', 'README.md']Uses filesystem checks on
.git/ state files rather than git commands. This is more reliable because file existence checks are atomic OS syscalls that work even when the git index is corrupted or git commands would hang. Handles worktrees, submodules, and
$GIT_DIR overrides via git rev-parse --git-dir.Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the state check(default: {}) |
Returns
GitOperationStateExample
Detect if git is mid-operation
const state = getOperationState()
if (state.inProgress) {
console.warn(`Cannot proceed: git ${state.reason} in progress`)
}Parameters
| Name | Type | Description |
|---|---|---|
§preset | FlowPreset | The preset name |
Returns
stringExample
Getting a preset description
import { getPresetDescription } from '@hyperfrontend/versioning'
console.log(getPresetDescription('independent'))
// => 'Version packages independently with dependency tracking'Parameters
Returns
ProjectExample
Get a project by name from the workspace
import { discoverWorkspace, getProject } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const project = getProject(workspace, '@myorg/utils')
if (project) {
console.log(`Version: ${project.version}`)
}Returns
numberExample
Get the count of projects in the workspace
import { discoverWorkspace, getProjectCount } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
console.log(`Workspace contains ${getProjectCount(workspace)} projects`)Returns
unknownExample
Get all project names in the workspace
import { discoverWorkspace, getProjectNames } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const names = getProjectNames(workspace)
console.log(`Found ${names.length} projects:`, names)Returns
stringExample
Get the remote for a reference
getRemote({ type: 'remote', name: 'main', remote: 'origin' }) // => 'origin'
getRemote({ type: 'branch', name: 'main' }) // => undefinedParameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the repository root directory
const root = getRepositoryRoot()getSectionHeading( type: ChangelogSectionType, customHeadings?: Partial< Record< ChangelogSectionType, string>>): string
Parameters
| Name | Type | Description |
|---|---|---|
§type | ChangelogSectionType | The changelog section type to get the heading for |
§customHeadings? | Partial< | Optional map of custom section type to heading overrides |
Returns
stringExample
Getting section headings
getSectionHeading('features')
// => 'Added'
getSectionHeading('features', { features: 'New Features' })
// => 'New Features'Parameters
| Name | Type | Description |
|---|---|---|
§heading | string | The heading string to parse |
Returns
ChangelogSectionTypeExample
Mapping heading strings to section types
getSectionType('Added')
// => 'features'
getSectionType('Bug Fixes')
// => 'fixes'
getSectionType('Custom Section')
// => 'other'Parameters
Returns
"minor" | "patch" | "none" | "major"Example
Determining semver bump level
getSemverBump('feat', false)
// => 'minor'
getSemverBump('fix', true)
// => 'major'
getSemverBump('docs', false)
// => 'none'Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | The full commit hash |
Returns
stringExample
Extracting short hash from full hash
getShortHash('abc1234def5678901234567890')
// => 'abc1234'Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of staged files
const staged = getStagedFiles()
// => ['src/index.ts', 'package.json']git rev-parse --show-toplevel, reads git diff --cached --name-only -z, and returns each staged path as an absolute path anchored at that root. Git emits staged paths relative to the repository root no matter which directory the command runs from, so anchoring here keeps downstream project-root discovery correct when the session cwd is a subdirectory.
Parameters
| Name | Type | Description |
|---|---|---|
§options | StagedPathsOptions | Resolver options (cwd, optional timeout) |
Returns
unknownExample
Reading the current staging area from a subdirectory
getStagedPaths({ cwd: '/repo/apps/demo' })
// => ['/repo/libs/versioning/src/commits/author/index.ts']Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Configuration for the status query(default: {}) |
Returns
RepositoryStatusExample
Get the full repository status
const status = getStatus()
if (!status.clean) {
console.log('Working tree has changes')
}Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to search |
§stepId | string | The step ID to find |
Returns
FlowStepExample
Getting a step by ID
import { getStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const step = getStep(flow, 'analyze-commits')
console.log(step?.name)
// => 'Analyze Commits'Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to look up |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
GitTagExample
Get detailed information about a specific tag
const tag = getTag('v1.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§options | ListTagsOptions | Tag listing options(default: {}) |
Returns
unknownExample
Get all tags from the repository
const tags = getTags()
const versionTags = getTags({ pattern: 'v' })Parameters
| Name | Type | Description |
|---|---|---|
§packageName | string | Package name to match |
§options | ListTagsOptions | Tag options(default: {}) |
Returns
unknownExample
Get tags matching a package name
const tags = getTagsForPackage('@scope/pkg')Parameters
| Name | Type | Description |
|---|---|---|
§analysis | DependencyGraphAnalysis | Dependency graph analysis result |
Returns
unknownExample
Get packages in topological order for building
const buildOrder = getTopologicalOrder(analysis)
for (const pkg of buildOrder) {
await build(pkg)
}Parameters
Returns
Set< string>Example
Get all transitive dependencies of a package
import { discoverWorkspace, getTransitiveDependencies } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const allDeps = getTransitiveDependencies(workspace, '@myorg/app')
console.log(`@myorg/app transitively depends on ${allDeps.size} packages`)
for (const dep of allDeps) {
console.log(` - ${dep}`)
}Parameters
Returns
Set< string>Example
Get all transitive dependents of a package
// If lib-a depends on lib-utils and app-main depends on lib-a
// Then getTransitiveDependents('lib-utils') returns ['lib-a', 'app-main']Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of untracked files
const untracked = getUntrackedFiles()
// => ['new-file.ts', 'temp.log']goto result pointing at the supplied step id.Parameters
| Name | Type | Description |
|---|---|---|
§stepId | string | Target step id the runner should jump to |
Returns
StepResultExample
Sending the runner back to the `type` step
return goto('type')Returns
booleanExample
Check if version a is greater than b
gt(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => true
gt(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => falseReturns
booleanExample
Check if version a is greater than or equal to b
gte(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => true
gte(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => trueParameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Directory containing the project to check |
Returns
booleanExample
Check if a project has a changelog
import { hasChangelog } from '@hyperfrontend/versioning'
if (hasChangelog('./libs/my-lib')) {
console.log('Project has a changelog')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check for merge conflicts
if (hasConflicts()) {
console.log('Resolve merge conflicts before continuing')
}Returns
booleanExample
Check if a project has internal dependencies
import { discoverProject, hasInternalDependencies } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && hasInternalDependencies(project)) {
console.log(`${project.name} depends on:`, project.internalDependencies)
}Returns
booleanExample
Check if a project has internal dependents
import { discoverProject, hasInternalDependents } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project && hasInternalDependents(project)) {
console.log(`${project.name} is used by:`, project.internalDependents)
}Parameters
Returns
booleanExample
Check if a project exists in the workspace
import { discoverWorkspace, hasProject } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (hasProject(workspace, '@myorg/utils')) {
console.log('Package exists in workspace')
}Returns
booleanExample
Check if a project has a changelog
import { discoverProject, hasChangelog } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && !hasChangelog(project)) {
console.log('Warning: No changelog found for', project.name)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for staged changes before committing
if (hasStagedChanges()) { createCommit('...') }Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to check |
§stepId | string | The step ID to look for |
Returns
booleanExample
Checking if a flow has a step
import { hasStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
console.log(hasStep(flow, 'create-commit'))
// => true
console.log(hasStep(flow, 'custom-step'))
// => falseParameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for unstaged changes before staging
if (hasUnstagedChanges()) { stage(['.']) }Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for untracked files
if (hasUntrackedFiles()) {
console.log('New files detected that are not tracked by git')
}Parameters
Returns
booleanExample
Checking if a changelog contains a version
hasVersion(changelog, '2.0.0')
// => true if an entry for version 2.0.0 existsReturns
booleanExample
Checking if changelogs have the same versions
haveSameVersions(changelog1, changelog2)
// => true if both have entries for the same version stringsParameters
Returns
SemVerExample
Increment version by bump type
increment(parseVersion('1.2.3'), 'minor') // 1.3.0
increment(parseVersion('1.2.3'), 'major') // 2.0.0
increment(parseVersion('1.2.3'), 'prerelease', 'alpha') // 1.2.4-alpha.0Parameters
Returns
SemVerExample
Increment the prerelease portion of a version
incrementPrerelease(parseVersionStrict('1.0.0')) // => 1.0.1-alpha.0
incrementPrerelease(parseVersionStrict('1.0.0-alpha.0')) // => 1.0.0-alpha.1
incrementPrerelease(parseVersionStrict('1.0.0'), 'beta') // => 1.0.1-beta.0Handles multiple formats:
- Shorthand:
"github:owner/repo","gitlab:group/project","bitbucket:team/repo" - Bare shorthand:
"owner/repo"(defaults to GitHub) - URL string:
"https://github.com/owner/repo" - Object with URL:
{ "type": "git", "url": "https://..." }
Parameters
| Name | Type | Description |
|---|---|---|
§packageJsonContent | string | Raw JSON string content of package.json |
Returns
RepositoryConfigExample
Inferring repository from package.json content
// Shorthand format
inferRepositoryFromPackageJson('{"repository": "github:owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// URL string
inferRepositoryFromPackageJson('{"repository": "https://github.com/owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Object format
inferRepositoryFromPackageJson('{"repository": {"type": "git", "url": "https://github.com/owner/repo"}}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Bare shorthand (defaults to GitHub)
inferRepositoryFromPackageJson('{"repository": "owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }inferRepositoryFromPackageJsonObject( packageJson: PackageJsonForRepository): RepositoryConfig
This is useful when you already have the parsed object.
Parameters
| Name | Type | Description |
|---|---|---|
§packageJson | PackageJsonForRepository | Parsed package.json object |
Returns
RepositoryConfigExample
Inferring repository from parsed object
const pkg = { repository: 'github:owner/repo' }
inferRepositoryFromPackageJsonObject(pkg)
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§index | number | Position to insert at (0-based) |
Returns
VersionFlowExample
Inserting a step at a specific position
import { insertStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStep(flow, createNoopStep('early', 'Early Step'), 0)
console.log(modified.steps[0].id)
// => 'early'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§afterStepId | string | ID of the step to insert after |
Returns
VersionFlowExample
Inserting a step after another step
import { insertStepAfter, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStepAfter(flow, createNoopStep('validate', 'Validate'), 'analyze-commits'))
// 'validate' step now follows 'analyze-commits'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§beforeStepId | string | ID of the step to insert before |
Returns
VersionFlowExample
Inserting a step before another step
import { insertStepBefore, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStepBefore(flow, createNoopStep('prep', 'Prepare'), 'create-commit'))
// 'prep' step now runs before 'create-commit'Returns
booleanExample
Check if a tag is annotated
const tag = createAnnotatedTag({ name: 'v1.0.0', commitHash: 'abc123', message: 'Release' })
isAnnotatedTag(tag) // => trueReturns
booleanExample
Check if reference is a branch
isBranchRef({ type: 'branch', name: 'main' }) // => trueReturns
booleanExample
Checking changelog equality
if (isChangelogEqual(mainChangelog, branchChangelog)) {
console.log('Changelogs are identical')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Configuration for the status check(default: {}) |
Returns
booleanExample
Check if working tree is clean
if (!isClean()) {
throw new Error('Working tree has changes')
}Returns
booleanExample
Comparing commit references
isCommitRefEqual(commitA, commitB)
// => true if hash, shortHash, and url matchParameters
| Name | Type | Description |
|---|---|---|
§message | string | The commit message to check |
Returns
booleanExample
Checking if a message is a conventional commit
isConventionalCommit('feat(auth): add OAuth login')
// => true
isConventionalCommit('WIP: still working on this')
// => false
isConventionalCommit('fix: resolve bug')
// => trueThe date and the compare URL are stamped when an entry is generated: the date is the day of generation and the compare URL ends at whatever commit was HEAD. Neither is derived from the commits the entry describes, so an entry regenerated later for the same release differs in both while saying exactly the same thing. This comparison reads the version, the unreleased flag, the raw content, and the sections, which is what a release's changelog entry asserts about the commits behind it.
Parameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogEntry | First entry |
§b | ChangelogEntry | Second entry |
Returns
booleanExample
Validating a committed entry against one regenerated today
isEntryContentEqual(committedEntry, regeneratedEntry)
// => true even though the dates and compare URLs differParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogEntry | First entry |
§b | ChangelogEntry | Second entry |
Returns
booleanExample
Comparing changelog entries
isEntryEqual(entryA, entryB)
// => true if version, date, sections, and all nested data matchParameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if directory is a git repository
if (!isGitRepository()) {
throw new Error('Not a git repository')
}Parameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogHeader | First header |
§b | ChangelogHeader | Second header |
Returns
booleanExample
Comparing changelog headers
isHeaderEqual(changelog1.header, changelog2.header)
// => true if titles, descriptions, and links matchReturns
booleanExample
Check if reference points to HEAD
isHeadRef({ type: 'head', name: 'HEAD' }) // => true
isHeadRef({ type: 'branch', name: 'main', isHead: true }) // => trueReturns
booleanExample
Comparing issue references
isIssueRefEqual({ number: 42, type: 'issue' }, { number: 42, type: 'issue' })
// => trueParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogItem | First item |
§b | ChangelogItem | Second item |
Returns
booleanExample
Comparing changelog items
isItemEqual(itemA, itemB)
// => true if description, scope, breaking, commits, and references matchParameters
| Name | Type | Description |
|---|---|---|
§platform | RepositoryPlatform | Platform identifier to check |
Returns
unknownExample
Checking if platform has built-in support
isKnownPlatform('github') // true
isKnownPlatform('gitlab') // true
isKnownPlatform('custom') // false
isKnownPlatform('unknown') // falseReturns
booleanExample
Check if a tag is lightweight
const tag = createLightweightTag({ name: 'v1.0.0', commitHash: 'abc123' })
isLightweightTag(tag) // => trueParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogLink | First link |
§b | ChangelogLink | Second link |
Returns
booleanExample
Comparing changelog links
isLinkEqual({ label: '1.0.0', url: '...' }, { label: '1.0.0', url: '...' })
// => trueReturns
booleanExample
Check if commit is a merge commit
if (isMergeCommit(commit)) {
console.log('Commit has parents:', commit.parents)
}Parameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogMetadata | First metadata |
§b | ChangelogMetadata | Second metadata |
Returns
booleanExample
Comparing changelog metadata
isMetadataEqual(changelog1.metadata, changelog2.metadata)
// => true if format, isConventional, repositoryUrl, and warnings matchConvenience wrapper around
getOperationState() for simple checks. Use getOperationState() directly when you need to know which operation is in progress.Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the state check(default: {}) |
Returns
booleanExample
Check for incomplete operations
if (isOperationInProgress()) {
throw new Error('Complete or abort the current git operation first')
}Returns
booleanExample
Check if version has prerelease identifiers
isPrerelease(parseVersionStrict('1.0.0-beta.1')) // => true
isPrerelease(parseVersionStrict('1.0.0')) // => falseReturns
booleanExample
Check if a project is private
import { discoverProject, isPrivate } from '@hyperfrontend/versioning'
const project = discoverProject('./apps/internal-app')
if (project && isPrivate(project)) {
console.log('Skipping private package')
}Returns
booleanExample
Check if a project is publishable
import { discoverProject, isPublishable } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && isPublishable(project)) {
console.log(`${project.name} can be published to npm`)
}Parameters
| Name | Type | Description |
|---|---|---|
§type | string | The commit type to check |
Returns
booleanExample
Checking for release-triggering types
isReleaseType('feat')
// => true
isReleaseType('chore')
// => falseReturns
booleanExample
Check if reference is a remote tracking branch
isRemoteRef({ type: 'remote', name: 'main', remote: 'origin' }) // => trueParameters
| Name | Type | Description |
|---|---|---|
§value | unknown | Value to check |
Returns
unknownExample
Type-checking repository config values
const config = { platform: 'github', baseUrl: 'https://...' }
if (isRepositoryConfig(config)) {
// config is typed as RepositoryConfig
}Parameters
| Name | Type | Description |
|---|---|---|
§value | unknown | Value to check |
Returns
unknownExample
Type-checking resolution configurations
import { isRepositoryResolution } from '@hyperfrontend/versioning'
const config = loadConfig()
if (isRepositoryResolution(config.repository)) {
console.log('Repository mode:', config.repository.mode)
}Returns
booleanExample
Check if commit is the initial commit
if (isRootCommit(commit)) {
console.log('This is the initial commit')
}Returns
booleanExample
Compare two commits by hash
isSameCommit(commitA, commitB) // => true if commitA.hash === commitB.hashParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogSection | First section |
§b | ChangelogSection | Second section |
Returns
booleanExample
Comparing changelog sections
isSectionEqual(featuresA, featuresB)
// => true if type, heading, and all items matchReturns
booleanExample
Check if version is a stable release
isStable(parseVersionStrict('1.0.0')) // => true
isStable(parseVersionStrict('0.9.0')) // => false (< 1.0.0)
isStable(parseVersionStrict('1.0.0-beta')) // => false (prerelease)Parameters
| Name | Type | Description |
|---|---|---|
§type | string | The commit type to check |
Returns
unknownExample
Checking for standard commit types
isStandardType('feat')
// => true
isStandardType('custom')
// => falseReturns
booleanExample
Check if reference is a tag
isTagRef({ type: 'tag', name: 'v1.0.0' }) // => trueReturns
booleanExample
Check if a range represents a wildcard
isWildcard(parseRangeStrict('*')) // => true
isWildcard(parseRangeStrict('^1.0.0')) // => falseoverridePathwins when set- Otherwise walk upward from
cwdlooking for one ofCONFIG_FILE_NAMES
.git, pnpm-workspace.yaml) is hit - Missing config is not an error:
configbecomes{}
Parameters
| Name | Type | Description |
|---|---|---|
§options | LoadCommitConfigOptions | Search inputs |
Returns
Promise< LoadedCommitConfig>Example
Auto-discovery from cwd
const { config, sourcePath } = await loadCommitConfig({ cwd: process.cwd() })Returns
booleanExample
Check if version a is less than b
lt(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => true
lt(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => falseReturns
booleanExample
Check if version a is less than or equal to b
lte(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => true
lte(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => trueParameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions |
Returns
SemVerExample
Get the maximum version from an array
const versions = [parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')]
max(versions) // => { major: 2, minor: 0, patch: 0, ... }
max([]) // => nullParameters
Returns
SemVerExample
Find the maximum version satisfying a range
const versions = ['1.0.0', '1.5.0', '2.0.0'].map(parseVersionStrict)
maxSatisfying(versions, parseRangeStrict('^1.0.0'))
// => { major: 1, minor: 5, patch: 0, ... }Parameters
Returns
MergeResultExample
Merging two changelogs
const result = mergeChangelogs(mainChangelog, branchChangelog)
console.log(`Merged ${result.stats.merged} entries`)Parameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions |
Returns
SemVerExample
Get the minimum version from an array
const versions = [parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')]
min(versions) // => { major: 1, minor: 0, patch: 0, ... }
min([]) // => nullParameters
Returns
SemVerExample
Find the minimum version satisfying a range
const versions = ['1.0.0', '1.5.0', '2.0.0'].map(parseVersionStrict)
minSatisfying(versions, parseRangeStrict('^1.0.0'))
// => { major: 1, minor: 0, patch: 0, ... }Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if repository needs to be pulled
if (needsPull()) {
console.log('Remote commits need to be pulled')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if repository needs to be pushed
if (needsPush()) {
console.log('Local commits need to be pushed')
}Returns
booleanExample
Check if two versions are not equal
neq(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => true
neq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => falseReturns
ChangelogExample
Normalizing section headings
const normalized = normalizeSectionHeadings(changelog)
// 'New Features' becomes 'Added', 'Bug Fixes' becomes 'Fixed', etc.Parameters
| Name | Type | Description |
|---|---|---|
§raw | string | Subject string as entered by the user |
Returns
stringExample
Stripping whitespace and a trailing period
normalizeSubject(' add login flow. ') // => 'add login flow'The body starts after the first blank line and continues until we encounter a footer (key: value or key #value pattern) or end of message.
Parameters
Returns
ParsedBodyExample
Parsing commit body
const lines = [
'feat: add login',
'',
'Implements OAuth flow.',
'',
'Refs: #123'
]
const result = parseBody(lines, 1)
// => { body: 'Implements OAuth flow.', endIndex: 4 }Parameters
Returns
ChangelogExample
Parsing a changelog markdown string
const markdown = `# Changelog
## [1.0.0] - 2024-01-15
### Added
- Initial release`
const changelog = parseChangelog(markdown, 'CHANGELOG.md')
// => { header: { title: '# Changelog', ... }, entries: [{ version: '1.0.0', ... }] }Parameters
Returns
CommitRef[ ]Example
Parsing commit references from text
parseCommitRefs('Fixed bug (abc1234)', 'https://github.com/org/repo')
// => [{ hash: 'abc1234', shortHash: 'abc1234', url: 'https://github.com/org/repo/commit/abc1234' }]Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The complete commit message |
Returns
ConventionalCommitExample
Parsing a complete conventional commit message
parseConventionalCommit('feat(auth): add login\n\nImplements OAuth.\n\nRefs: #123')
// => {
// type: 'feat',
// scope: ['auth'],
// subject: 'add login',
// body: 'Implements OAuth.',
// footers: [{ key: 'Refs', value: '#123', separator: ':' }],
// breaking: false,
// raw: '...'
// }Supports comma-separated multi-scope headers such as
feat(a,b): x which produce a multi-element scope array. Single-scope headers produce a one-element array, and scopeless headers produce an empty array.Parameters
| Name | Type | Description |
|---|---|---|
§line | string | The first line of the commit message |
Returns
ParsedHeaderExample
Parsing conventional commit headers
parseHeader('feat(auth): add OAuth login')
// => { type: 'feat', scope: ['auth'], subject: 'add OAuth login', breaking: false }
parseHeader('fix!: critical security patch')
// => { type: 'fix', scope: [], subject: 'critical security patch', breaking: true }
parseHeader('feat(versioning,questions): add searchable select')
// => { type: 'feat', scope: ['versioning', 'questions'], subject: 'add searchable select', breaking: false }Parameters
Returns
IssueRef[ ]Example
Parsing issue references from text
parseIssueRefs('Closes #42 and PR #123', 'https://github.com/org/repo')
// => [{ number: 42, type: 'issue', url: '...' }, { number: 123, type: 'pull-request', url: '...' }]Supports:
- Exact: 1.2.3, =1.2.3
- Comparators: >1.0.0, >=1.0.0, <2.0.0, <=2.0.0
- Caret: ^1.2.3 (compatible with version)
- Tilde: ~1.2.3 (approximately equivalent)
- X-ranges: 1.x, 1.2.x, *
- Hyphen ranges: 1.0.0 - 2.0.0
- OR: 1.0.0 || 2.0.0
- AND: >=1.0.0 <2.0.0
Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The range string to parse |
Returns
ParseRangeResultExample
Parse semver range strings
parseRange('^1.0.0') // => { success: true, range: ... }
parseRange('>=1.0.0 <2.0.0') // => { success: true, range: ... }
parseRange('1.0.0 || 2.0.0') // => { success: true, range: ... }Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The range string to parse |
Returns
RangeExample
Parse range with strict validation
const range = parseRangeStrict('^1.0.0')
parseRangeStrict('invalid range!!!') // throws Errorkeyword #number shape.Parameters
| Name | Type | Description |
|---|---|---|
§raw | string | Raw user input from the issues prompt |
Returns
unknownExample
Parsing a mixed references string
parseReferences('fixes #123, re #456')
// => [
// { key: 'Fixes', value: '123', separator: ' #' },
// { key: 'Re', value: '456', separator: ' #' },
// ]Supports multiple URL formats:
https://github.com/owner/repohttps://github.com/owner/repo.gitgit+https://github.com/owner/repo.gitgit://github.com/owner/repo.gitgit@github.com:owner/repo.git(SSH format)
github.mycompany.com→githubgitlab.internal.com→gitlab
https://dev.azure.com/org/project/_git/repohttps://org.visualstudio.com/project/_git/repo
Parameters
| Name | Type | Description |
|---|---|---|
§gitUrl | string | Git repository URL in any supported format |
Returns
ParsedRepositoryExample
Parsing various git URL formats
// GitHub HTTPS
parseRepositoryUrl('https://github.com/owner/repo')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// SSH format
parseRepositoryUrl('git@github.com:owner/repo.git')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Azure DevOps
parseRepositoryUrl('https://dev.azure.com/org/proj/_git/repo')
// → { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' }
// Self-hosted GitLab
parseRepositoryUrl('https://gitlab.mycompany.com/team/project')
// → { platform: 'gitlab', baseUrl: 'https://gitlab.mycompany.com/team/project' }Parameters
| Name | Type | Description |
|---|---|---|
§text | string | The text to parse for scope |
Returns
ScopeFromItemExample
Parsing scope from changelog items
parseScopeFromItem('**api:** Add new endpoint')
// => { scope: 'api', description: 'Add new endpoint' }
parseScopeFromItem('Simple change without scope')
// => { scope: undefined, description: 'Simple change without scope' }Accepts versions in the format: MAJOR.MINOR.PATCH[-prerelease][+build] Optional leading 'v' or '=' prefixes are stripped.
Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The version string to parse |
Returns
ParseVersionResultExample
Parse a semantic version string
parseVersion('1.2.3') // { success: true, version: { major: 1, minor: 2, patch: 3, ... } }
parseVersion('v1.0.0-alpha.1+build.123') // { success: true, ... }
parseVersion('invalid') // { success: false, error: '...' }Parameters
| Name | Type | Description |
|---|---|---|
§heading | string | The heading string to parse |
Returns
ParsedVersionHeadingExample
Parsing version headings
parseVersionFromHeading('[1.2.3] - 2024-01-15')
// => { version: '1.2.3', date: '2024-01-15', compareUrl: undefined }
parseVersionFromHeading('v2.0.0')
// => { version: '2.0.0', date: null, compareUrl: undefined }Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The version string to parse |
Returns
SemVerExample
Parse version with strict validation
const v = parseVersionStrict('1.2.3')
// => { major: 1, minor: 2, patch: 3, prerelease: [], build: [] }
parseVersionStrict('invalid') // throws ErrorParameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag to push to the remote |
§remote | string | Remote name (defaults to 'origin')(default: 'origin') |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Push a tag to remote
pushTag('v1.0.0')
pushTag('v1.0.0', 'upstream')releaseUnreleased( changelog: Changelog, version: string, date?: string, compareUrl?: string): Changelog
Parameters
Returns
ChangelogExample
Releasing unreleased changes as a new version
const released = releaseUnreleased(changelog, '2.0.0', '2024-03-01')
// Unreleased entry becomes version 2.0.0 with the specified dateParameters
Returns
ChangelogExample
Removing empty entries
const cleaned = removeEmptyEntries(changelog)
// Entries with no content are removed (unreleased kept by default)
const strict = removeEmptyEntries(changelog, false)
// Even empty unreleased entry is removedReturns
ChangelogExample
Removing empty sections from all entries
const cleaned = removeEmptySections(changelog)
// Sections with no items are removed from all entriesremoveEntries( changelog: Changelog, versions: unknown, options?: RemoveEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove from |
§versions | unknown | The versions to remove |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing multiple entries
const cleaned = removeEntries(changelog, ['0.1.0', '0.2.0'])
// Pre-release versions removed from changelogremoveEntry( changelog: Changelog, version: string, options?: RemoveEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove from |
§version | string | The version to remove |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing an entry by version
const newChangelog = removeEntry(changelog, '1.0.0')removeItem( changelog: Changelog, version: string, sectionType: string, itemDescription: string, options?: RemoveSectionOptions): Changelog
Parameters
Returns
ChangelogExample
Removing a specific item from a section
const updated = removeItem(changelog, '1.0.0', 'features', 'Add dark mode')
// The 'Add dark mode' item is removed from features in 1.0.0removeSection( changelog: Changelog, version: string, sectionType: string, options?: RemoveSectionOptions): Changelog
Parameters
Returns
ChangelogExample
Removing a deprecated section
const updated = removeSection(changelog, '1.0.0', 'deprecated')
// Version 1.0.0 no longer has a deprecated sectionParameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§stepId | string | The ID of the step to remove |
Returns
VersionFlowExample
Removing a step from a flow
import { removeStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const minimal = removeStep(flow, 'generate-changelog')
// Flow no longer has changelog step
console.log(minimal.steps.find(s => s.id === 'generate-changelog'))
// => undefinedParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove the unreleased entry from |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing the unreleased entry
const released = removeUnreleased(changelog)
// Changelog without the unreleased entry
// Silently ignore if not found
const safe = removeUnreleased(changelog, { throwIfNotFound: false })Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§stepId | string | ID of the step to replace |
§newStep | FlowStep | The replacement step |
Returns
VersionFlowExample
Replacing a step with a custom implementation
import { replaceStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = replaceStep(flow, 'create-tag', createNoopStep('create-tag', 'Custom Tag'))
// 'create-tag' now uses custom implementationParameters
| Name | Type | Description |
|---|---|---|
§options? | SerializeOptions | User-provided options |
Returns
Required< SerializeOptions>Example
Resolving serialize options with defaults
const opts = resolveOptions({ includeCommits: true })
// => { includeCommits: true, includeScope: true, lineEnding: '\n', ... }SessionConfig usable by the runner.Parameters
| Name | Type | Description |
|---|---|---|
§overrides | PartialSessionConfig | Partial config supplied by the caller (may be undefined)(default: {}) |
Returns
SessionConfigExample
Resolving with no overrides
resolveSessionConfig()
// => { types: DEFAULT_SESSION_TYPES, scopeOptional: false, ... }Returns
ChangelogExample
Reversing entry order
const reversed = reverseEntries(changelog)
// Oldest entries now appear firstgoto jumps, surfaces Ctrl-C cancellations as { status: 'cancelled' } outcomes, and (on success) returns the final formatted message alongside the committed status.Parameters
| Name | Type | Description |
|---|---|---|
§session | AuthorSession | Session description produced by createAuthorSession |
Returns
Promise< SessionOutcome>Example
Running a session to completion
const session = createAuthorSession({ config: { skipCommit: true } })
const outcome = await runAuthorSession(session)
// => { status: 'committed', message: 'feat: ...' }Returns
booleanExample
Check if version satisfies a range
satisfies(parseVersion('1.2.3'), parseRange('^1.0.0')) // true
satisfies(parseVersion('2.0.0'), parseRange('^1.0.0')) // falseParameters
| Name | Type | Description |
|---|---|---|
§version | SemVer | Version to check |
§comparator | Comparator | Comparator to test against |
Returns
booleanExample
Check if version satisfies a comparator
const gte100 = createComparator('>=', parseVersionStrict('1.0.0'))
satisfiesComparator(parseVersionStrict('2.0.0'), gte100) // => true
satisfiesComparator(parseVersionStrict('0.9.0'), gte100) // => falseParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to serialize |
§options? | SerializeOptions | Optional serialization options |
Returns
stringExamples
Basic serialization
const markdown = serializeChangelog(changelog)Serializing with custom options
const markdown = serializeChangelog(changelog, {
includeCommits: false,
useAsterisks: true,
})Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog object to convert to JSON |
§options? | JsonSerializeOptions | Optional JSON serialization options |
Returns
stringExamples
Basic JSON serialization
const json = serializeChangelogToJson(changelog)Pretty-printing with custom indentation
const json = serializeChangelogToJson(changelog, {
pretty: true,
indent: 4,
})Parameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions to sort |
Returns
SemVer[ ]Example
Sort versions in ascending order
sort([v2, v1, v3]) // [v1, v2, v3]Parameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions to sort |
Returns
SemVer[ ]Example
Sort versions in descending order
sortDescending([v1, v3, v2]) // [v3, v2, v1]Returns
ChangelogExample
Sorting changelog entries by version
const sorted = sortEntries(changelog)
// Entries ordered: Unreleased, 2.0.0, 1.1.0, 1.0.0, ...Returns
ChangelogExample
Sorting entries by date
const sorted = sortEntriesByDate(changelog)
// Entries ordered by release date, most recent firstParameters
Returns
ChangelogExample
Sorting sections within entries
const sorted = sortSections(changelog)
// Sections in each entry follow: breaking, features, fixes, ...
const custom = sortSections(changelog, ['fixes', 'features', 'breaking'])
// Custom ordering: fixes first, then features, then breakingParameters
| Name | Type | Description |
|---|---|---|
§files | unknown | Array of file paths relative to working directory |
§options | StageOptions | Configuration for the staging operation(default: {}) |
Returns
booleanExample
Stage files for commit
stage(['package.json', 'CHANGELOG.md'])
stage(['.'], { all: true })Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the staging operation(default: {}) |
Returns
booleanExample
Stage all tracked and untracked changes
stageAll() // stages all tracked and untracked changesReturns
SemVerExample
Strip build metadata from version
const v = parseVersionStrict('1.0.0+build.123')
format(stripBuild(v)) // => '1.0.0'Returns
ChangelogExample
Stripping metadata from a changelog
const stripped = stripMetadata(changelog)
// Source and warnings removed, only format and isConventional retainedReturns
SemVerExample
Strip prerelease identifiers from version
const v = parseVersionStrict('1.0.0-beta.1')
format(stripPrerelease(v)) // => '1.0.0'Parameters
| Name | Type | Description |
|---|---|---|
§result | BatchUpdateResult | Result object from batch update operation |
Returns
stringExample
Create a summary of the batch update result
import { batchUpdateVersions, summarizeBatchUpdate } from '@hyperfrontend/versioning'
const result = batchUpdateVersions(workspace, updates)
console.log(summarizeBatchUpdate(result))
// Output:
// Successfully updated 3 package(s)
//
// Updated packages:
// @myorg/utils: 1.0.0 -> 1.1.0
// @myorg/core: 2.0.0 -> 2.1.0Parameters
| Name | Type | Description |
|---|---|---|
§result | CascadeBumpResult | Result object from cascade bump calculation |
Returns
stringExample
Get a summary of cascade bump results
import { calculateCascadeBumps, summarizeCascadeBumps } from '@hyperfrontend/versioning'
const result = calculateCascadeBumps(workspace, [{ name: '@myorg/utils', bumpType: 'patch' }])
console.log(summarizeCascadeBumps(result))
// Output:
// 3 package(s) affected:
// - 1 direct bump(s)
// - 2 cascade bump(s)Parameters
| Name | Type | Description |
|---|---|---|
§diff | ChangelogDiff | The diff to summarize |
Returns
stringExample
Creating human-readable diff summary
const diff = diffChangelogs(oldChangelog, newChangelog)
summarizeDiff(diff)
// => 'Added 2 version(s): 1.2.0, 1.1.0; Modified 1 version(s): 1.0.0'Parameters
| Name | Type | Description |
|---|---|---|
§report | ValidationReport | Report object from workspace validation |
Returns
stringExample
Create a summary of the validation report
import { validateWorkspace, summarizeValidation } from '@hyperfrontend/versioning'
const report = validateWorkspace(workspace)
console.log(summarizeValidation(report))
// Output:
// Workspace validation passed
// 2 warning(s)Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to verify |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Check if a tag exists
if (tagExists('v1.0.0')) { ... }ConventionalCommit to a CommitDraft. Useful for editing a parsed commit inside an authoring session.Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | Parsed conventional commit |
Returns
CommitDraftExample
Promoting a parsed commit to an editable draft
toDraft(parseConventionalCommit('feat: add login'))
// => { type: 'feat', scope: [], subject: 'add login', footers: [], breaking: false }toJsonObject( changelog: Changelog, options?: JsonSerializeOptions): Record< string, unknown>
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to convert |
§options? | JsonSerializeOptions | Optional JSON serialization options |
Returns
Record< string, unknown>Example
Converting changelog to plain object
const obj = toJsonObject(changelog, { includeSource: true })
// => { source: 'CHANGELOG.md', header: { title: '...', ... }, entries: [...] }
// Send as API response
res.json(obj)Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The markdown content to tokenize |
Returns
Token[ ]Example
Tokenizing changelog markdown
const tokens = tokenize('# Changelog\n\n## [1.0.0]\n- Added feature')
// => [{ type: 'heading-1', value: 'Changelog', ... }, { type: 'heading-2', ... }, ...]Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | EntryTransformer | Function to transform each entry |
Returns
ChangelogExample
Transforming all entries
const transformed = transformEntries(changelog, (entry) => ({
...entry,
date: entry.date?.toUpperCase()
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | ItemTransformer | Function to transform each item |
Returns
ChangelogExample
Prefixing item descriptions with scope
const prefixed = transformItems(changelog, (item) => ({
...item,
description: `[${item.scope || 'misc'}] ${item.description}`,
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | SectionTransformer | Function to transform each section |
Returns
ChangelogExample
Uppercasing section headings
const renamed = transformSections(changelog, (section) => ({
...section,
heading: section.heading.toUpperCase(),
}))Parameters
Returns
booleanExample
Check if one package transitively depends on another
import { discoverWorkspace, transitivelyDependsOn } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (transitivelyDependsOn(workspace, '@myorg/app', '@myorg/utils')) {
console.log('Bumping @myorg/utils will affect @myorg/app')
}SessionType to a Choice usable by the select prompt.Parameters
| Name | Type | Description |
|---|---|---|
§type | SessionType | Session type entry to render as a selectable choice |
Returns
Choice< string>Example
Rendering the feat type as a select choice
typeToChoice({ name: 'feat', description: 'A new feature' })
// => { label: 'feat', value: 'feat', hint: 'A new feature' }Parameters
| Name | Type | Description |
|---|---|---|
§files | unknown | Array of file paths to remove from staging area |
§options | GitCommitOptions | Configuration for the unstage operation(default: {}) |
Returns
booleanExample
Unstage files from the index
unstage(['package.json'])updateEntry( changelog: Changelog, version: string, updates: Partial< ChangelogEntry> | ( entry: ChangelogEntry) => ChangelogEntry): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§version | string | Version of entry to update |
§updates | Partial< | Partial entry updates or transformer function |
Returns
ChangelogExample
Updating a specific entry
const updated = updateEntry(changelog, '1.0.0', { date: '2024-01-15' })
// Or with transformer function
const modified = updateEntry(changelog, '1.0.0', (entry) => ({
...entry,
compareUrl: `https://github.com/org/repo/compare/v0.9.0...v${entry.version}`,
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§updates | Partial< | Partial header updates |
Returns
ChangelogExample
Updating the changelog header
const updated = updateHeader(changelog, { title: '# Release Notes' })Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§updates | Partial< | Partial metadata updates |
Returns
ChangelogExample
Updating changelog metadata
const updated = updateMetadata(changelog, { repositoryUrl: 'https://github.com/org/repo' })Parameters
Example
Update a package.json version using VFS Tree
import { updatePackageVersionInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function bumpVersion(tree: Tree) {
updatePackageVersionInTree(tree, 'libs/my-lib/package.json', '2.0.0')
}Parameters
| Name | Type | Description |
|---|---|---|
§changelog | unknown | The changelog object to validate |
Returns
ValidationResultExample
Validating a changelog object
const result = validateChangelog(myChangelog)
if (!result.valid) {
console.log('Validation errors:', result.errors)
}Rules whose config is
'off' (or absent from the ruleset) are skipped. Unknown rule names in the ruleset are ignored: consumers extend the engine by calling validateCommitWithRules() and supplying additional rules.Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | Parsed conventional commit to validate |
§ruleset | Ruleset | Configuration map from rule name to [level, options?] |
Returns
ValidationResultExample
Validating a commit against a preset ruleset
validateCommit(parseConventionalCommit('feat: add login'), conventionalPreset)
// => { valid: true, warnings: [], errors: [] }cl bin and anywhere else validation must start from a string rather than a parsed commit.Parameters
Returns
ValidationResultExample
Validating a raw commit message
validateCommitMessage('feat: add login', conventionalPreset)
// => { valid: true, warnings: [], errors: [] }
validateCommitMessage('feat: added login', conventionalPreset).warnings
// => [{ level: 'warn', ruleName: 'imperative-mood', message: ... }]validateCommitWithRules( commit: ConventionalCommit, ruleset: Ruleset, rules: Readonly< Record< string, Rule< never>>>): ValidationResult
validateCommit that accepts a caller-supplied rule registry. Useful for consumers that want to add project-specific rules without forking the built-in set.Parameters
Returns
ValidationResultExample
Extending the built-in rules with a custom rule
const customRules = { ...BUILT_IN_RULES, 'no-wip': noWipRule }
validateCommitWithRules(commit, { 'no-wip': ['error'] }, customRules)Checks for:
- Duplicate step IDs
- Invalid dependency references
- Circular dependencies
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to validate |
Returns
unknownExample
Validating a flow before execution
import { validateFlow, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const errors = validateFlow(flow)
if (errors.length > 0) {
console.error('Invalid flow:', errors)
}
// => []Returns
ValidationResultExample
Validate a single project
import { discoverProject, validateProject } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project) {
const result = validateProject(project)
if (!result.valid) {
console.error('Validation failed:', result.error)
}
}Returns
ValidationReportExample
Validate a workspace for common issues
import { validateWorkspace } from '@hyperfrontend/versioning'
const report = validateWorkspace(workspace)
if (!report.valid) {
console.error(`${report.errorCount} error(s) found`)
for (const result of report.results) {
if (!result.result.valid) {
console.error(` ${result.checkName}: ${result.result.error}`)
}
}
}Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to configure |
§config | Partial< | Configuration updates to merge |
Returns
VersionFlowExample
Updating flow configuration
import { withConfig, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const dryRunFlow = withConfig(flow, { dryRun: true })
// Flow now runs in dry-run modeParameters
Returns
ProjectExample
Create a project copy with updated dependents
import { discoverProject, withDependents } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
const updated = withDependents(project, ['@myorg/app', '@myorg/web'])
console.log('Updated dependents:', updated.internalDependents)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscoveryOptions | Discovery configuration options(default: {}) |
Returns
WorkspaceExample
Create a complete workspace object from disk
import { createWorkspaceFromDisk } from '@hyperfrontend/versioning'
const workspace = createWorkspaceFromDisk()
// Access projects
for (const project of workspace.projectList) {
console.log(`${project.name}@${project.version}`)
}
// Get dependents of a package
const dependents = workspace.dependencyGraph.get('lib-utils')◈ Interfaces
Properties
Properties
runAuthorSession. Contains the fully resolved config and the ordered step list that will be executed.Properties
Properties
Properties
Properties
Properties
cascadeBumpType?:BumpTypeBump type for cascaded dependents. Default is 'patch' - dependents get a patch bump when their dependencies change.includeDevDependencies?:booleanWhether to include dev dependencies in cascade. Default is false - only production dependencies trigger cascades.includePeerDependencies?:booleanWhether to include peer dependencies in cascade. Default is true - peer dependency updates should cascade.Properties
Complete representation of a CHANGELOG.md file. Designed for lossless round-tripping: parse -> modify -> serialize.
Properties
Properties
Represents a single version entry in a changelog.
Properties
The header section of a changelog file.
Properties
Represents an individual change within a changelog section.
Properties
Represents a link defined in the changelog header or elsewhere.
Additional information extracted during parsing.
Properties
Represents a category of changes (Features, Bug Fixes, etc.)
Properties
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 breakingDescription?:stringBreaking change description: surfaces as a BREAKING CHANGE: footer when none is already presentRepresents a reference to a git commit in a changelog item.
Properties
Properties
Examples:
- >=1.2.3 -> { operator: '>=', version: { major: 1, minor: 2, patch: 3 } }
- ^1.2.0 -> { operator: '^', version: { major: 1, minor: 2, patch: 0 } }
Properties
Example: ">=1.0.0 <2.0.0" -> two comparators in one set
Properties
Properties
Properties
Properties
createAuthorSession: the caller may supply their own step list; anything missing is resolved against the defaults used by the conventional preset.Properties
Properties
Properties
Properties
Properties
Properties
Properties
readonly baseUrl:stringBase URL for the repository. Trailing slashes will be automatically stripped.readonly formatCompareUrl?:CompareUrlFormatterCustom compare URL formatter. Required when platform is 'custom'.Properties
requiresDiskFlush?:booleanWhether pending file writes must reach disk before this step runs. Steps that shell out to another tool, such as staging files for a commit, observe the real filesystem rather than the in-memory tree, so they see nothing until the tree is flushed.
Properties
Properties
Properties
Properties
Properties
Properties
Properties
discoverScopes.Properties
readonly scopeFilter?:( context: ScopeFilterContext) => booleanOptional caller-provided filter; applied after built-in exclusionsProperties
Properties
Properties
Properties
projectRoot?:stringProject root path (relative to workspace root, e.g., 'libs/utils/immutable-api'). If provided, this is used directly instead of deriving from project name. This is the recommended approach when calling from the Nx executor.rollbackOnFailure?:booleanWhether to rollback all pending changes on step failure. When true (default), pending VFS changes are discarded when a step fails and
continueOnError is false. This ensures no partial state remains.Properties
Properties
Properties
Properties
readonly backupChangelog?:booleanCreate a backup of the existing changelog before modification. When enabled:
- Existing
CHANGELOG.mdis renamed toCHANGELOG.backup.md - New changelog is written
- Backup is deleted on success
readonly commitTypeToSection?:Partial< Record< string, ChangelogSectionType | null>>Custom mapping from commit type to changelog section. Merged with defaults; use null to exclude a type from changelog.readonly maxCommitFallback?:numberMaximum commits to analyze when no base commit is available. Used for first releases, history rewrites, and path-filtered queries. Set higher if you expect >500 commits between releases.
readonly releaseAs?:"minor" | "patch" | "major"Force a specific bump type. The bump no longer comes from the commits, but they are still analyzed, and every commit type is admitted so the changelog lists what changed since the last release rather than only what could have caused a bump.readonly repository?:RepositoryConfig | "inferred" | "disabled" | RepositoryResolutionRepository resolution configuration for compare URL generation. Controls how repository information is resolved:
'disabled': No compare URLs generated (default, backward compatible)'inferred': Auto-detect from package.json or git remoteRepositoryResolution: Fine-grained control with explicit mode and optionsRepositoryConfig: Direct repository configuration
readonly scopeFiltering?:ScopeFilteringConfigCommit scope filtering configuration. Controls how commits are attributed to projects in changelogs. By default, hybrid filtering ensures commits are included based on conventional commit scope OR file changes within the project.
Note:
state is mutable within context to allow accumulation, but individual FlowState objects are immutable.Properties
Properties
readonly diffs?:unknownDetailed diffs for pending changes. Populated when showDiff: true is passed to executeFlow. Provides unified diff format for programmatic access.Properties
readonly classificationResult?:ClassificationResultClassification result with source attribution (when scope filtering enabled)readonly effectiveBaseCommit?:stringThe verified base commit used for commit scoping and changelog generation. This will be publishedCommit if that commit is reachable from HEAD, or null if a fallback was used (e.g., history was rewritten). When null, compare URLs are omitted from the changelog.
readonly isPendingPublication?:booleanWhether this is a pending publication scenario. True when currentVersion > publishedVersion, meaning the version was already bumped but not yet published to the registry. When true: - nextVersion is calculated from publishedVersion (not currentVersion)
- Changelog entries > publishedVersion should be cleaned up
- The correct changelog entry replaces any stacked ones
Steps are pure functions that:
- Read from context (state, config, services)
- Perform work (possibly with side effects via services)
- Return state updates
Properties
readonly requiresDiskFlush?:booleanWhether pending file writes must reach disk before this step runs. Steps that shell out to another tool, such as staging files for a commit, observe the real filesystem rather than the in-memory tree, so they see nothing until the tree is flushed.
Properties
Properties
Provides a unified interface for all git operations within a specific working directory.
Properties
Properties
Properties
Properties
Properties
readonly maxCount?:numberMaximum number of commits to retrieve. Zero retrieves every matching commit.Properties
readonly details:GitOperationStateDetailsDetailed state for each check performed. Useful for diagnostics or when multiple states could theoretically overlap.readonly reason:GitOperationStateReasonWhich operation is in progress, if any. Returns the first detected state (exit-early pattern).Properties
Properties
Properties
Properties
Properties
Properties
Represents a reference to an issue or pull request.
Properties
Properties
Properties
Properties
loadCommitConfig.Properties
loadCommitConfig.Properties
readonly sourcePath?:stringAbsolute path of the config file that was loaded (undefined when nothing was found)Properties
timedAsync:TimedAsyncFnWraps a promise-returning call with timing. Dumps stack trace on error to debug log.Properties
Properties
Properties
Properties
Properties
Package.json supports multiple formats for the repository field:
- Shorthand:
"github:owner/repo" - URL string:
"https://github.com/owner/repo" - Object:
{ "type": "git", "url": "..." }
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Example: "^1.0.0 || ^2.0.0" -> two comparator sets
Properties
Properties
Properties
Supports both standard platforms and self-hosted instances. When using a known platform, compare URLs are generated automatically. For custom or unknown platforms, provide a
formatCompareUrl function.Properties
readonly baseUrl:stringBase URL for the repository. This is the URL without any trailing slashes or paths like
/compare.readonly formatCompareUrl?:CompareUrlFormatterCustom compare URL formatter. Required when
platform is 'custom'. Optional for known platforms (overrides built-in formatter). Receives the from and to tags, returns the full compare URL.
Provides fine-grained control over repository resolution behavior.
Properties
readonly inferenceOrder?:unknownInference source order when mode is 'inferred'. Sources are tried in order until one succeeds. Default:
['package-json', 'git-remote']readonly repository?:RepositoryConfigExplicit repository config. Required when
mode is 'explicit'. Ignored when mode is 'inferred' or 'disabled'.Properties
ConventionalCommit and return zero or more violation messages. The engine applies severity from the ruleset config and aggregates RuleMessages across all rules.Properties
Properties
Properties
Properties
Properties
SessionConfig.scopeFilter.Properties
Properties
A version number in the format MAJOR.MINOR.PATCH with optional prerelease and build metadata.
Properties
Properties
readonly sectionHeadings?:Partial< Record< ChangelogSectionType, string>>Custom section headings (overrides defaults)createAuthorSession) fills the gaps with defaults before handing the result to the runner.Properties
readonly imperativeWordlist:Readonly< Record< string, string>>Wordlist used by the imperative-mood rule in inline validationreadonly scopeFilter?:( context: ScopeFilterContext) => booleanOptional filter applied to discovered scopes; receives { path, name }readonly scopeOptional:booleanWhen true, the scope step may be skipped even if no candidates are foundreadonly skipCommit:booleanWhen true, the final commit step is a no-op (session returns the message only)readonly stagedPathsProvider:StagedPathsProviderResolver for the staged file list; defaults to git diff --cached --name-only -zdraft field accumulates the in-progress commit message; candidateScopes/defaultScope are filled by the resolve-scope step and consumed by later steps. config is the fully resolved SessionConfig shared across the session.Properties
runAuthorSession.Properties
readonly message?:stringFormatted commit message (populated whenever the draft reached the preview step)type step's choice list.Properties
Properties
Properties
Properties
Properties
Properties
readonly cases:unknownOne or more accepted case forms. A subject passing any accepted form is valid.Properties
Properties
Properties
Properties
Properties
Flows are immutable configurations that define:
- What steps to execute
- In what order
- With what configuration
Properties
Properties
readonly gitHead?:stringGit commit hash at publish time. Used to determine commit range for changelog generation. NOTE: This value comes from npm registry, making it immutable and independent of local git state.
Properties
readonly reverseDependencyGraph:ReadonlyMap< string, unknown>Reverse dependency graph (project name -> dependencies)Properties
Properties
◆ Types
type BumpReason = "direct" | "cascade" | "sync"type BumpType = "major" | "minor" | "patch" | "premajor" | "preminor" | "prepatch" | "prerelease" | "none"Detected format/style of the changelog file.
type ChangelogFormat = "keep-a-changelog" | "conventional" | "custom" | "unknown"Categories for grouping changes in a changelog entry.
type ChangelogSectionType = "breaking" | "features" | "fixes" | "performance" | "documentation" | "deprecations" | "refactoring" | "tests" | "build" | "ci" | "chores" | "other"type CommitExecutor = ( message: string) => void | Promise< void>type CommitType = "feat" | "fix" | "docs" | "style" | "refactor" | "perf" | "test" | "build" | "ci" | "chore" | "revert" | stringtype CompareUrlFormatter = ( fromCommit: string, toCommit: string) => stringtype DependencyGraph = ReadonlyMap< string, unknown>type DependencyType = "dependencies" | "devDependencies" | "peerDependencies" | "optionalDependencies"type EntryPredicate = ( entry: ChangelogEntry, index: number) => booleantype EntryTransformer = ( entry: ChangelogEntry, index: number) => ChangelogEntrytype FileChangeStatus = "added" | "modified" | "deleted" | "renamed" | "copied"type FileStatus = "modified" | "added" | "deleted" | "renamed" | "copied" | "untracked" | "ignored" | "unmerged"type FlowPreset = "conventional" | "independent" | "synced"type FlowStatus = "success" | "partial" | "failed" | "skipped"type GitOperationStateOptions = GitCommitOptionsThese correspond to state files that git creates during multi-step operations:
'rebase-interactive':.git/rebase-mergeexists (interactive rebase paused)'rebase-apply':.git/rebase-applyexists (mailbox-based rebase orgit am)'merge-in-progress':.git/MERGE_HEADexists (merge awaiting conflict resolution)
type GitOperationStateReason = "rebase-interactive" | "rebase-apply" | "merge-in-progress"type GitRefType = "branch" | "tag" | "remote" | "head" | "stash"type GitTagType = "lightweight" | "annotated"type ItemPredicate = ( item: ChangelogItem, section: ChangelogSection, entry: ChangelogEntry) => booleantype ItemTransformer = ( item: ChangelogItem, section: ChangelogSection, entry: ChangelogEntry) => ChangelogItemEach platform has a specific URL format for compare links:
github:{baseUrl}/compare/{fromCommit}...{toCommit}gitlab:{baseUrl}/-/compare/{fromCommit}...{toCommit}bitbucket:{baseUrl}/compare/{toCommit}..{fromCommit}(reversed order)azure-devops:{baseUrl}/compare?version=GT{toCommit}&compareVersion=GT{fromCommit}
type KnownPlatform = "github" | "gitlab" | "bitbucket" | "azure-devops"type MergeStrategy = "source" | "target" | "union" | "latest"npm view invocation.type NpmLookupFailure = AbsentLookupFailure | UnavailableLookupFailurecreateAuthorSession/loadCommitConfig.type PartialSessionConfig = Partial< SessionConfig>type RangeOperator = "=" | ">" | ">=" | "< " | "< =" | "^" | "~"Every value here means the answer is unknown, never that the package or version is absent. An absent package is a successful lookup with a negative answer, and is reported through the normal return value instead.
type RegistryFailureReason = "network" | "authentication" | "rate-limit" | "server" | "timeout" | "unknown"package-json: Read fromrepositoryfield in package.jsongit-remote: Read fromgit remote get-url origin
type RepositoryInferenceSource = "package-json" | "git-remote"- Known platforms have built-in compare URL formatters
customrequires a customformatCompareUrlfunctionunknownindicates platform could not be determined (no URL generation)
type RepositoryPlatform = KnownPlatform | "custom" | "unknown"explicit: Use only the providedRepositoryConfig; error if missinginferred: Auto-detect from package.json repository field or git remotedisabled: Do not generate compare URLs (default for backward compatibility)
type RepositoryResolutionMode = "explicit" | "inferred" | "disabled"A bare
[level] entry disables the rule when level is 'off', or enables it with no options otherwise. The [level, options] form passes rule-specific options through to the rule's check() implementation.type RuleConfig = unknown | unknowntype RuleLevel = "off" | "warn" | "error"type RuleResult = unknown'off' by the engine.type Ruleset = Readonly< Record< string, RuleConfig>>type SectionPredicate = ( section: ChangelogSection, entry: ChangelogEntry) => booleantype SectionTransformer = ( section: ChangelogSection, entry: ChangelogEntry) => ChangelogSectiontype SessionStatus = Readonly< { Cancelled: "cancelled"; Committed: "committed" }>type StagedPathsProvider = ( options: StagedPathsProviderOptions) => unknowntype StepCondition = ( context: FlowContext) => booleantype StepExecutor = ( context: FlowContext) => Promise< FlowStepResult>type StepStatus = Readonly< { Cancelled: "cancelled"; Done: "done"; Goto: "goto" }>type SubjectCase = "sentence-case" | "lower-case" | "upper-case" | "kebab-case" | "snake-case" | "start-case"type TokenType = "heading-1" | "heading-2" | "heading-3" | "heading-4" | "list-item" | "link-text" | "link-url" | "text" | "newline" | "blank-line" | "bold" | "code" | "eof"type WorkspaceType = "nx" | "turbo" | "lerna" | "pnpm" | "npm" | "yarn" | "rush" | "unknown"● Variables
draft.body unset (the formatter then omits the body section).draft.breakingDescription and is eventually synthesized into a BREAKING CHANGE: footer by formatCommitMessage.config.skipCommit is true the step short-circuits so the caller receives the formatted message without touching the working tree.@commitlint/config-conventional's baseline. | Rule | Level | Options | | ------------------ | ----- | ------------------------ | | type-enum | error |
CONVENTIONAL_TYPES | | subject-empty | error | — | | scope-enum | off | — | | subject-case | off | — | | header-max-length | warn | { maxLength: 72 } | | imperative-mood | warn | — |createAuthorSession when the caller does not supply their own sequence. Matches decision D8: resolve-scope → type → scope → subject → body → breaking → issues → preview → commit.These represent repository-level or infrastructure changes that typically don't belong in individual project changelogs.
type step when the caller does not supply their own list. Each conventional type is paired with a short hint.type(scope)!: subject) exceeds the configured maximum length.fixes #123, re #456) and appends them to draft.footers using the # separator form. The step is additive: existing footers stay, and any breaking-change footer synthesized later by the formatter is unaffected.
Includes both standard SaaS domains and common patterns for self-hosted instances.
type step while keeping the draft intact for round-trip editing. Validation warnings are printed before the prompt.Calls
config.stagedPathsProvider() → discoverScopes → defaultScope and writes the results into the context. Cancels the session when the staging area is empty (decision E1a).resolve-scope. Respects scopeOptional (cancels with an actionable error when no candidates exist and scope is required) and scopeMulti (multiselect vs. single-select).headerMaxLength is set, a live countdown re-renders on every keystroke (green → yellow → red as the remaining budget shrinks).ctx.draft.type (so a preview → cancel restart re-opens on the previous choice).Comprehensive Keep-a-Changelog module with parsing, serialization, comparison, and operations.
ƒ Functions
addEntry( changelog: Changelog, entry: ChangelogEntry, options?: AddEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to add to |
§entry | ChangelogEntry | The entry to add |
§options? | AddEntryOptions | Optional add options |
Returns
ChangelogExample
Adding a new entry to a changelog
const newChangelog = addEntry(changelog, {
version: '1.2.0',
date: '2024-01-15',
unreleased: false,
sections: [...]
})addItemToEntry( changelog: Changelog, version: string, sectionType: string, item: ChangelogItem): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog containing the entry to modify |
§version | string | The version identifier of the entry to update |
§sectionType | string | Category identifier for grouping changes (e.g., 'features', 'fixes') |
§item | ChangelogItem | Description of the change with optional scope and metadata |
Returns
ChangelogExample
Adding a feature item to an entry
const item = { description: 'Add dark mode support', scope: 'ui' }
const updated = addItemToEntry(changelog, '1.2.0', 'features', item)
// Item added to the features section of version 1.2.0Parameters
Returns
ChangelogExample
Adding an unreleased entry with sections
const sections = [createChangelogSection('features', 'Added', [item])]
const updated = addUnreleasedEntry(changelog, sections)
// => Changelog with unreleased entry at the topappendChangelog( source: Changelog, target: Changelog, position: "start" | "end"): Changelog
Parameters
Returns
ChangelogExample
Appending entries to a changelog
const combined = appendChangelog(mainChangelog, newChangelog, 'start')
// newChangelog entries appear before mainChangelog entriesParameters
Returns
CompatibilityResultExample
Checking schema compatibility before merge
const result = checkSchemaCompatibility(mainChangelog, branchChangelog)
if (!result.compatible) {
console.log('Schema differences:', result.differences)
}Returns
ChangelogExample
Cloning a changelog
const copy = cloneChangelog(changelog)
// Independent deep copy, safe to mutateParameters
| Name | Type | Description |
|---|---|---|
§changelogs | unknown | Array of changelogs to combine |
§options? | MergeOptions | Optional merge options |
Returns
ChangelogExample
Combining multiple changelogs
const unified = combineChangelogs([pkg1Changelog, pkg2Changelog], {
strategy: 'merge-sections',
})
// All entries from both changelogs merged with conflict resolutionParameters
Returns
ChangelogExample
Compacting a changelog
const compacted = compact(changelog)
// Empty sections and entries removed (unreleased kept)
const strict = compact(changelog, false)
// Even empty unreleased entry is removedParameters
Returns
ChangelogExample
Creating a changelog with custom source
const changelog = createChangelog({ source: 'CHANGELOG.md' })
// => { source: 'CHANGELOG.md', header: { title: '# Changelog', ... }, entries: [] }createChangelogEntry( version: string, options?: Partial< Omit< ChangelogEntry, "version">>): ChangelogEntry
Parameters
| Name | Type | Description |
|---|---|---|
§version | string | The version string (e.g., '1.0.0') |
§options? | Partial< | Optional configuration for date, sections, and other properties |
Returns
ChangelogEntryExample
Creating a changelog entry with date and sections
const entry = createChangelogEntry('1.0.0', {
date: '2024-01-15',
sections: [createChangelogSection('features', 'Added', items)],
})createChangelogItem( description: string, options?: Partial< Omit< ChangelogItem, "description">>): ChangelogItem
Parameters
| Name | Type | Description |
|---|---|---|
§description | string | The description text of the change |
§options? | Partial< | Optional configuration for scope, commits, references, and breaking flag |
Returns
ChangelogItemExample
Creating a changelog item with options
const item = createChangelogItem('Add user authentication', {
scope: 'auth',
breaking: false,
references: [{ number: 42, type: 'issue' }],
})Parameters
Returns
ChangelogLinkExample
Creating a version comparison link
const link = createChangelogLink('1.0.0', 'https://github.com/org/repo/compare/v0.9.0...v1.0.0')
// => { label: '1.0.0', url: 'https://github.com/...' }createChangelogSection( type: ChangelogSectionType, heading: string, items: unknown): ChangelogSection
Parameters
| Name | Type | Description |
|---|---|---|
§type | ChangelogSectionType | The type of section (features, fixes, breaking, etc.) |
§heading | string | The display heading for the section |
§items | unknown | Optional array of changelog items in this section(default: []) |
Returns
ChangelogSectionExample
Creating a features section with items
const section = createChangelogSection('features', 'Added', [
createChangelogItem('New dashboard widget'),
])Returns
CommitRefExample
Creating a commit reference
const ref = createCommitRef('abc1234def5678', 'https://github.com/org/repo/commit/abc1234def5678')
// => { hash: 'abc1234def5678', shortHash: 'abc1234', url: 'https://...' }Returns
ChangelogExample
Creating an empty changelog with standard header
const changelog = createEmptyChangelog()
// => Changelog with standard Keep a Changelog header and no entriesParameters
Returns
IssueRefExample
Creating issue and PR references
const issueRef = createIssueRef(42, 'issue', 'https://github.com/org/repo/issues/42')
// => { number: 42, type: 'issue', url: 'https://...' }
const prRef = createIssueRef(123, 'pull-request')
// => { number: 123, type: 'pull-request', url: undefined }Returns
stringExample
Creating blank line spacing
createSpacing(2, '\n')
// => '\n\n'Parameters
| Name | Type | Description |
|---|---|---|
§sections | unknown | Optional array of changelog sections(default: []) |
Returns
ChangelogEntryExample
Creating an unreleased entry
const unreleased = createUnreleasedEntry([
createChangelogSection('features', 'Added', [item]),
])
// => { version: 'Unreleased', date: null, unreleased: true, sections: [...] }Returns
ChangelogExample
Deduplicating items across sections
const deduped = deduplicateItems(changelog)
// Duplicate items (same scope:description) are removedParameters
Returns
ChangelogDiffExample
Comparing two changelogs
const diff = diffChangelogs(mainChangelog, branchChangelog)
console.log(`Added: ${diff.added.length}, Removed: ${diff.removed.length}`)Parameters
| Name | Type | Description |
|---|---|---|
§source | ChangelogEntry | The source entry |
§target | ChangelogEntry | The target entry |
Returns
EntryDiffExample
Computing diff between two entries
const diff = diffEntries(entryV1, entryV2)
// => { version: '1.0.0', changes: [{ path: ['date'], type: 'changed', ... }], sectionsChanged: true }Parameters
Returns
ChangelogExample
Excluding items by scope
const publicChanges = excludeByScope(changelog, ['internal', 'test'])
// Items scoped to 'internal' or 'test' are removedReturns
ChangelogExample
Filtering for breaking changes
const breaking = filterBreakingChanges(changelog)
// Only entries containing breaking changes or items marked as breakingParameters
Returns
ChangelogExample
Filtering entries by date range
const q1 = filterByDateRange(changelog, '2024-01-01', '2024-03-31')
// Entries released in Q1 2024Parameters
Returns
ChangelogExample
Filtering items by scope
const apiChanges = filterByScope(changelog, ['api', 'core'])
// Only items scoped to 'api' or 'core'Parameters
Returns
ChangelogExample
Filtering by semver range
const majors = filterByVersionRange(changelog, '>=1.0.0 <2.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | EntryPredicate | Function that returns true for entries to keep |
Returns
ChangelogExample
Filtering out unreleased entries
const filtered = filterEntries(changelog, (entry) => !entry.unreleased)Parameters
Returns
ChangelogExample
Filtering from a start version
const recent = filterFromVersion(changelog, '2.0.0')
// Only entries for version 2.0.0 and laterParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | ItemPredicate | Function that returns true for items to keep |
Returns
ChangelogExample
Filtering items with references
const withRefs = filterItems(changelog, (item) => item.references.length > 0)
// Only items with issue/PR referencesfilterRecentEntries( changelog: Changelog, count: number, includeUnreleased: boolean): Changelog
Parameters
Returns
ChangelogExample
Getting the most recent entries
const latest = filterRecentEntries(changelog, 5)
// Last 5 released versions (plus unreleased if present)Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | SectionPredicate | Function that returns true for sections to keep |
Returns
ChangelogExample
Filtering for user-facing sections
const userFacing = filterSections(changelog, (section) =>
['features', 'fixes', 'breaking'].includes(section.type)
)Parameters
Returns
ChangelogExample
Filtering to specific section types
const userChanges = filterSectionTypes(changelog, ['features', 'fixes'])
// Only features and fixes sections remainParameters
Returns
ChangelogExample
Filtering to an end version
const legacy = filterToVersion(changelog, '1.9.9')
// Only entries for versions up to 1.9.9filterVersionRange( changelog: Changelog, startVersion: string, endVersion: string): Changelog
Parameters
Returns
ChangelogExample
Filtering entries within a version range
const range = filterVersionRange(changelog, '1.5.0', '2.0.0')
// Entries from 1.5.0 through 2.0.0Parameters
Returns
stringExample
Formatting a markdown link
formatLink('1.0.0', 'https://github.com/org/repo/releases/tag/v1.0.0')
// => '[1.0.0](https://github.com/org/repo/releases/tag/v1.0.0)'Parameters
Returns
ChangelogEntryExample
Getting an entry by version
const entry = getEntryByVersion(changelog, '1.0.0')
// => ChangelogEntry for 1.0.0 or undefinedParameters
| Name | Type | Description |
|---|---|---|
§useAsterisks | boolean | Whether to use * instead of - |
Returns
stringExample
Getting list markers
getListMarker(false) // => '- '
getListMarker(true) // => '* 'getSectionHeading( type: ChangelogSectionType, customHeadings?: Partial< Record< ChangelogSectionType, string>>): string
Parameters
| Name | Type | Description |
|---|---|---|
§type | ChangelogSectionType | The changelog section type to get the heading for |
§customHeadings? | Partial< | Optional map of custom section type to heading overrides |
Returns
stringExample
Getting section headings
getSectionHeading('features')
// => 'Added'
getSectionHeading('features', { features: 'New Features' })
// => 'New Features'Parameters
| Name | Type | Description |
|---|---|---|
§heading | string | The heading string to parse |
Returns
ChangelogSectionTypeExample
Mapping heading strings to section types
getSectionType('Added')
// => 'features'
getSectionType('Bug Fixes')
// => 'fixes'
getSectionType('Custom Section')
// => 'other'Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | The full commit hash |
Returns
stringExample
Extracting short hash from full hash
getShortHash('abc1234def5678901234567890')
// => 'abc1234'Parameters
Returns
booleanExample
Checking if a changelog contains a version
hasVersion(changelog, '2.0.0')
// => true if an entry for version 2.0.0 existsReturns
booleanExample
Checking if changelogs have the same versions
haveSameVersions(changelog1, changelog2)
// => true if both have entries for the same version stringsReturns
booleanExample
Checking changelog equality
if (isChangelogEqual(mainChangelog, branchChangelog)) {
console.log('Changelogs are identical')
}Returns
booleanExample
Comparing commit references
isCommitRefEqual(commitA, commitB)
// => true if hash, shortHash, and url matchThe date and the compare URL are stamped when an entry is generated: the date is the day of generation and the compare URL ends at whatever commit was HEAD. Neither is derived from the commits the entry describes, so an entry regenerated later for the same release differs in both while saying exactly the same thing. This comparison reads the version, the unreleased flag, the raw content, and the sections, which is what a release's changelog entry asserts about the commits behind it.
Parameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogEntry | First entry |
§b | ChangelogEntry | Second entry |
Returns
booleanExample
Validating a committed entry against one regenerated today
isEntryContentEqual(committedEntry, regeneratedEntry)
// => true even though the dates and compare URLs differParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogEntry | First entry |
§b | ChangelogEntry | Second entry |
Returns
booleanExample
Comparing changelog entries
isEntryEqual(entryA, entryB)
// => true if version, date, sections, and all nested data matchParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogHeader | First header |
§b | ChangelogHeader | Second header |
Returns
booleanExample
Comparing changelog headers
isHeaderEqual(changelog1.header, changelog2.header)
// => true if titles, descriptions, and links matchReturns
booleanExample
Comparing issue references
isIssueRefEqual({ number: 42, type: 'issue' }, { number: 42, type: 'issue' })
// => trueParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogItem | First item |
§b | ChangelogItem | Second item |
Returns
booleanExample
Comparing changelog items
isItemEqual(itemA, itemB)
// => true if description, scope, breaking, commits, and references matchParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogLink | First link |
§b | ChangelogLink | Second link |
Returns
booleanExample
Comparing changelog links
isLinkEqual({ label: '1.0.0', url: '...' }, { label: '1.0.0', url: '...' })
// => trueParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogMetadata | First metadata |
§b | ChangelogMetadata | Second metadata |
Returns
booleanExample
Comparing changelog metadata
isMetadataEqual(changelog1.metadata, changelog2.metadata)
// => true if format, isConventional, repositoryUrl, and warnings matchParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogSection | First section |
§b | ChangelogSection | Second section |
Returns
booleanExample
Comparing changelog sections
isSectionEqual(featuresA, featuresB)
// => true if type, heading, and all items matchParameters
Returns
MergeResultExample
Merging two changelogs
const result = mergeChangelogs(mainChangelog, branchChangelog)
console.log(`Merged ${result.stats.merged} entries`)Returns
ChangelogExample
Normalizing section headings
const normalized = normalizeSectionHeadings(changelog)
// 'New Features' becomes 'Added', 'Bug Fixes' becomes 'Fixed', etc.Parameters
Returns
ChangelogExample
Parsing a changelog markdown string
const markdown = `# Changelog
## [1.0.0] - 2024-01-15
### Added
- Initial release`
const changelog = parseChangelog(markdown, 'CHANGELOG.md')
// => { header: { title: '# Changelog', ... }, entries: [{ version: '1.0.0', ... }] }Parameters
Returns
CommitRef[ ]Example
Parsing commit references from text
parseCommitRefs('Fixed bug (abc1234)', 'https://github.com/org/repo')
// => [{ hash: 'abc1234', shortHash: 'abc1234', url: 'https://github.com/org/repo/commit/abc1234' }]Parameters
Returns
IssueRef[ ]Example
Parsing issue references from text
parseIssueRefs('Closes #42 and PR #123', 'https://github.com/org/repo')
// => [{ number: 42, type: 'issue', url: '...' }, { number: 123, type: 'pull-request', url: '...' }]Parameters
| Name | Type | Description |
|---|---|---|
§text | string | The text to parse for scope |
Returns
ScopeFromItemExample
Parsing scope from changelog items
parseScopeFromItem('**api:** Add new endpoint')
// => { scope: 'api', description: 'Add new endpoint' }
parseScopeFromItem('Simple change without scope')
// => { scope: undefined, description: 'Simple change without scope' }Parameters
| Name | Type | Description |
|---|---|---|
§heading | string | The heading string to parse |
Returns
ParsedVersionHeadingExample
Parsing version headings
parseVersionFromHeading('[1.2.3] - 2024-01-15')
// => { version: '1.2.3', date: '2024-01-15', compareUrl: undefined }
parseVersionFromHeading('v2.0.0')
// => { version: '2.0.0', date: null, compareUrl: undefined }releaseUnreleased( changelog: Changelog, version: string, date?: string, compareUrl?: string): Changelog
Parameters
Returns
ChangelogExample
Releasing unreleased changes as a new version
const released = releaseUnreleased(changelog, '2.0.0', '2024-03-01')
// Unreleased entry becomes version 2.0.0 with the specified dateParameters
Returns
ChangelogExample
Removing empty entries
const cleaned = removeEmptyEntries(changelog)
// Entries with no content are removed (unreleased kept by default)
const strict = removeEmptyEntries(changelog, false)
// Even empty unreleased entry is removedReturns
ChangelogExample
Removing empty sections from all entries
const cleaned = removeEmptySections(changelog)
// Sections with no items are removed from all entriesremoveEntries( changelog: Changelog, versions: unknown, options?: RemoveEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove from |
§versions | unknown | The versions to remove |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing multiple entries
const cleaned = removeEntries(changelog, ['0.1.0', '0.2.0'])
// Pre-release versions removed from changelogremoveEntry( changelog: Changelog, version: string, options?: RemoveEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove from |
§version | string | The version to remove |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing an entry by version
const newChangelog = removeEntry(changelog, '1.0.0')removeItem( changelog: Changelog, version: string, sectionType: string, itemDescription: string, options?: RemoveSectionOptions): Changelog
Parameters
Returns
ChangelogExample
Removing a specific item from a section
const updated = removeItem(changelog, '1.0.0', 'features', 'Add dark mode')
// The 'Add dark mode' item is removed from features in 1.0.0removeSection( changelog: Changelog, version: string, sectionType: string, options?: RemoveSectionOptions): Changelog
Parameters
Returns
ChangelogExample
Removing a deprecated section
const updated = removeSection(changelog, '1.0.0', 'deprecated')
// Version 1.0.0 no longer has a deprecated sectionParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove the unreleased entry from |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing the unreleased entry
const released = removeUnreleased(changelog)
// Changelog without the unreleased entry
// Silently ignore if not found
const safe = removeUnreleased(changelog, { throwIfNotFound: false })Parameters
| Name | Type | Description |
|---|---|---|
§options? | SerializeOptions | User-provided options |
Returns
Required< SerializeOptions>Example
Resolving serialize options with defaults
const opts = resolveOptions({ includeCommits: true })
// => { includeCommits: true, includeScope: true, lineEnding: '\n', ... }Returns
ChangelogExample
Reversing entry order
const reversed = reverseEntries(changelog)
// Oldest entries now appear firstParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to serialize |
§options? | SerializeOptions | Optional serialization options |
Returns
stringExamples
Basic serialization
const markdown = serializeChangelog(changelog)Serializing with custom options
const markdown = serializeChangelog(changelog, {
includeCommits: false,
useAsterisks: true,
})Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog object to convert to JSON |
§options? | JsonSerializeOptions | Optional JSON serialization options |
Returns
stringExamples
Basic JSON serialization
const json = serializeChangelogToJson(changelog)Pretty-printing with custom indentation
const json = serializeChangelogToJson(changelog, {
pretty: true,
indent: 4,
})Returns
ChangelogExample
Sorting changelog entries by version
const sorted = sortEntries(changelog)
// Entries ordered: Unreleased, 2.0.0, 1.1.0, 1.0.0, ...Returns
ChangelogExample
Sorting entries by date
const sorted = sortEntriesByDate(changelog)
// Entries ordered by release date, most recent firstParameters
Returns
ChangelogExample
Sorting sections within entries
const sorted = sortSections(changelog)
// Sections in each entry follow: breaking, features, fixes, ...
const custom = sortSections(changelog, ['fixes', 'features', 'breaking'])
// Custom ordering: fixes first, then features, then breakingReturns
ChangelogExample
Stripping metadata from a changelog
const stripped = stripMetadata(changelog)
// Source and warnings removed, only format and isConventional retainedParameters
| Name | Type | Description |
|---|---|---|
§diff | ChangelogDiff | The diff to summarize |
Returns
stringExample
Creating human-readable diff summary
const diff = diffChangelogs(oldChangelog, newChangelog)
summarizeDiff(diff)
// => 'Added 2 version(s): 1.2.0, 1.1.0; Modified 1 version(s): 1.0.0'toJsonObject( changelog: Changelog, options?: JsonSerializeOptions): Record< string, unknown>
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to convert |
§options? | JsonSerializeOptions | Optional JSON serialization options |
Returns
Record< string, unknown>Example
Converting changelog to plain object
const obj = toJsonObject(changelog, { includeSource: true })
// => { source: 'CHANGELOG.md', header: { title: '...', ... }, entries: [...] }
// Send as API response
res.json(obj)Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The markdown content to tokenize |
Returns
Token[ ]Example
Tokenizing changelog markdown
const tokens = tokenize('# Changelog\n\n## [1.0.0]\n- Added feature')
// => [{ type: 'heading-1', value: 'Changelog', ... }, { type: 'heading-2', ... }, ...]Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | EntryTransformer | Function to transform each entry |
Returns
ChangelogExample
Transforming all entries
const transformed = transformEntries(changelog, (entry) => ({
...entry,
date: entry.date?.toUpperCase()
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | ItemTransformer | Function to transform each item |
Returns
ChangelogExample
Prefixing item descriptions with scope
const prefixed = transformItems(changelog, (item) => ({
...item,
description: `[${item.scope || 'misc'}] ${item.description}`,
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | SectionTransformer | Function to transform each section |
Returns
ChangelogExample
Uppercasing section headings
const renamed = transformSections(changelog, (section) => ({
...section,
heading: section.heading.toUpperCase(),
}))updateEntry( changelog: Changelog, version: string, updates: Partial< ChangelogEntry> | ( entry: ChangelogEntry) => ChangelogEntry): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§version | string | Version of entry to update |
§updates | Partial< | Partial entry updates or transformer function |
Returns
ChangelogExample
Updating a specific entry
const updated = updateEntry(changelog, '1.0.0', { date: '2024-01-15' })
// Or with transformer function
const modified = updateEntry(changelog, '1.0.0', (entry) => ({
...entry,
compareUrl: `https://github.com/org/repo/compare/v0.9.0...v${entry.version}`,
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§updates | Partial< | Partial header updates |
Returns
ChangelogExample
Updating the changelog header
const updated = updateHeader(changelog, { title: '# Release Notes' })Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§updates | Partial< | Partial metadata updates |
Returns
ChangelogExample
Updating changelog metadata
const updated = updateMetadata(changelog, { repositoryUrl: 'https://github.com/org/repo' })Parameters
| Name | Type | Description |
|---|---|---|
§changelog | unknown | The changelog object to validate |
Returns
ValidationResultExample
Validating a changelog object
const result = validateChangelog(myChangelog)
if (!result.valid) {
console.log('Validation errors:', result.errors)
}◈ Interfaces
Properties
Properties
Complete representation of a CHANGELOG.md file. Designed for lossless round-tripping: parse -> modify -> serialize.
Properties
Properties
Represents a single version entry in a changelog.
Properties
The header section of a changelog file.
Properties
Represents an individual change within a changelog section.
Properties
Represents a link defined in the changelog header or elsewhere.
Additional information extracted during parsing.
Properties
Represents a category of changes (Features, Bug Fixes, etc.)
Properties
Represents a reference to a git commit in a changelog item.
Properties
Properties
Properties
Properties
Represents a reference to an issue or pull request.
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
readonly sectionHeadings?:Partial< Record< ChangelogSectionType, string>>Custom section headings (overrides defaults)Properties
◆ Types
Detected format/style of the changelog file.
type ChangelogFormat = "keep-a-changelog" | "conventional" | "custom" | "unknown"Categories for grouping changes in a changelog entry.
type ChangelogSectionType = "breaking" | "features" | "fixes" | "performance" | "documentation" | "deprecations" | "refactoring" | "tests" | "build" | "ci" | "chores" | "other"type EntryPredicate = ( entry: ChangelogEntry, index: number) => booleantype EntryTransformer = ( entry: ChangelogEntry, index: number) => ChangelogEntrytype ItemPredicate = ( item: ChangelogItem, section: ChangelogSection, entry: ChangelogEntry) => booleantype ItemTransformer = ( item: ChangelogItem, section: ChangelogSection, entry: ChangelogEntry) => ChangelogItemtype MergeStrategy = "source" | "target" | "union" | "latest"type SectionPredicate = ( section: ChangelogSection, entry: ChangelogEntry) => booleantype SectionTransformer = ( section: ChangelogSection, entry: ChangelogEntry) => ChangelogSectiontype TokenType = "heading-1" | "heading-2" | "heading-3" | "heading-4" | "list-item" | "link-text" | "link-url" | "text" | "newline" | "blank-line" | "bold" | "code" | "eof"● Variables
Changelog equality checks and diff utilities for comparing changelogs, entries, and sections.
ƒ Functions
Parameters
Returns
ChangelogDiffExample
Comparing two changelogs
const diff = diffChangelogs(mainChangelog, branchChangelog)
console.log(`Added: ${diff.added.length}, Removed: ${diff.removed.length}`)Parameters
| Name | Type | Description |
|---|---|---|
§source | ChangelogEntry | The source entry |
§target | ChangelogEntry | The target entry |
Returns
EntryDiffExample
Computing diff between two entries
const diff = diffEntries(entryV1, entryV2)
// => { version: '1.0.0', changes: [{ path: ['date'], type: 'changed', ... }], sectionsChanged: true }Parameters
Returns
ChangelogEntryExample
Getting an entry by version
const entry = getEntryByVersion(changelog, '1.0.0')
// => ChangelogEntry for 1.0.0 or undefinedParameters
Returns
booleanExample
Checking if a changelog contains a version
hasVersion(changelog, '2.0.0')
// => true if an entry for version 2.0.0 existsReturns
booleanExample
Checking if changelogs have the same versions
haveSameVersions(changelog1, changelog2)
// => true if both have entries for the same version stringsReturns
booleanExample
Checking changelog equality
if (isChangelogEqual(mainChangelog, branchChangelog)) {
console.log('Changelogs are identical')
}Returns
booleanExample
Comparing commit references
isCommitRefEqual(commitA, commitB)
// => true if hash, shortHash, and url matchThe date and the compare URL are stamped when an entry is generated: the date is the day of generation and the compare URL ends at whatever commit was HEAD. Neither is derived from the commits the entry describes, so an entry regenerated later for the same release differs in both while saying exactly the same thing. This comparison reads the version, the unreleased flag, the raw content, and the sections, which is what a release's changelog entry asserts about the commits behind it.
Parameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogEntry | First entry |
§b | ChangelogEntry | Second entry |
Returns
booleanExample
Validating a committed entry against one regenerated today
isEntryContentEqual(committedEntry, regeneratedEntry)
// => true even though the dates and compare URLs differParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogEntry | First entry |
§b | ChangelogEntry | Second entry |
Returns
booleanExample
Comparing changelog entries
isEntryEqual(entryA, entryB)
// => true if version, date, sections, and all nested data matchParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogHeader | First header |
§b | ChangelogHeader | Second header |
Returns
booleanExample
Comparing changelog headers
isHeaderEqual(changelog1.header, changelog2.header)
// => true if titles, descriptions, and links matchReturns
booleanExample
Comparing issue references
isIssueRefEqual({ number: 42, type: 'issue' }, { number: 42, type: 'issue' })
// => trueParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogItem | First item |
§b | ChangelogItem | Second item |
Returns
booleanExample
Comparing changelog items
isItemEqual(itemA, itemB)
// => true if description, scope, breaking, commits, and references matchParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogLink | First link |
§b | ChangelogLink | Second link |
Returns
booleanExample
Comparing changelog links
isLinkEqual({ label: '1.0.0', url: '...' }, { label: '1.0.0', url: '...' })
// => trueParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogMetadata | First metadata |
§b | ChangelogMetadata | Second metadata |
Returns
booleanExample
Comparing changelog metadata
isMetadataEqual(changelog1.metadata, changelog2.metadata)
// => true if format, isConventional, repositoryUrl, and warnings matchParameters
| Name | Type | Description |
|---|---|---|
§a | ChangelogSection | First section |
§b | ChangelogSection | Second section |
Returns
booleanExample
Comparing changelog sections
isSectionEqual(featuresA, featuresB)
// => true if type, heading, and all items matchParameters
| Name | Type | Description |
|---|---|---|
§diff | ChangelogDiff | The diff to summarize |
Returns
stringExample
Creating human-readable diff summary
const diff = diffChangelogs(oldChangelog, newChangelog)
summarizeDiff(diff)
// => 'Added 2 version(s): 1.2.0, 1.1.0; Modified 1 version(s): 1.0.0'◈ Interfaces
Properties
Properties
Properties
Properties
Properties
Properties
Changelog data structures and factory functions for entries, sections, and items.
ƒ Functions
Parameters
Returns
CompatibilityResultExample
Checking schema compatibility before merge
const result = checkSchemaCompatibility(mainChangelog, branchChangelog)
if (!result.compatible) {
console.log('Schema differences:', result.differences)
}Parameters
Returns
ChangelogExample
Creating a changelog with custom source
const changelog = createChangelog({ source: 'CHANGELOG.md' })
// => { source: 'CHANGELOG.md', header: { title: '# Changelog', ... }, entries: [] }createChangelogEntry( version: string, options?: Partial< Omit< ChangelogEntry, "version">>): ChangelogEntry
Parameters
| Name | Type | Description |
|---|---|---|
§version | string | The version string (e.g., '1.0.0') |
§options? | Partial< | Optional configuration for date, sections, and other properties |
Returns
ChangelogEntryExample
Creating a changelog entry with date and sections
const entry = createChangelogEntry('1.0.0', {
date: '2024-01-15',
sections: [createChangelogSection('features', 'Added', items)],
})createChangelogItem( description: string, options?: Partial< Omit< ChangelogItem, "description">>): ChangelogItem
Parameters
| Name | Type | Description |
|---|---|---|
§description | string | The description text of the change |
§options? | Partial< | Optional configuration for scope, commits, references, and breaking flag |
Returns
ChangelogItemExample
Creating a changelog item with options
const item = createChangelogItem('Add user authentication', {
scope: 'auth',
breaking: false,
references: [{ number: 42, type: 'issue' }],
})Parameters
Returns
ChangelogLinkExample
Creating a version comparison link
const link = createChangelogLink('1.0.0', 'https://github.com/org/repo/compare/v0.9.0...v1.0.0')
// => { label: '1.0.0', url: 'https://github.com/...' }createChangelogSection( type: ChangelogSectionType, heading: string, items: unknown): ChangelogSection
Parameters
| Name | Type | Description |
|---|---|---|
§type | ChangelogSectionType | The type of section (features, fixes, breaking, etc.) |
§heading | string | The display heading for the section |
§items | unknown | Optional array of changelog items in this section(default: []) |
Returns
ChangelogSectionExample
Creating a features section with items
const section = createChangelogSection('features', 'Added', [
createChangelogItem('New dashboard widget'),
])Returns
CommitRefExample
Creating a commit reference
const ref = createCommitRef('abc1234def5678', 'https://github.com/org/repo/commit/abc1234def5678')
// => { hash: 'abc1234def5678', shortHash: 'abc1234', url: 'https://...' }Returns
ChangelogExample
Creating an empty changelog with standard header
const changelog = createEmptyChangelog()
// => Changelog with standard Keep a Changelog header and no entriesParameters
Returns
IssueRefExample
Creating issue and PR references
const issueRef = createIssueRef(42, 'issue', 'https://github.com/org/repo/issues/42')
// => { number: 42, type: 'issue', url: 'https://...' }
const prRef = createIssueRef(123, 'pull-request')
// => { number: 123, type: 'pull-request', url: undefined }Parameters
| Name | Type | Description |
|---|---|---|
§sections | unknown | Optional array of changelog sections(default: []) |
Returns
ChangelogEntryExample
Creating an unreleased entry
const unreleased = createUnreleasedEntry([
createChangelogSection('features', 'Added', [item]),
])
// => { version: 'Unreleased', date: null, unreleased: true, sections: [...] }Parameters
| Name | Type | Description |
|---|---|---|
§heading | string | The heading string to parse |
Returns
ChangelogSectionTypeExample
Mapping heading strings to section types
getSectionType('Added')
// => 'features'
getSectionType('Bug Fixes')
// => 'fixes'
getSectionType('Custom Section')
// => 'other'Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | The full commit hash |
Returns
stringExample
Extracting short hash from full hash
getShortHash('abc1234def5678901234567890')
// => 'abc1234'Parameters
| Name | Type | Description |
|---|---|---|
§changelog | unknown | The changelog object to validate |
Returns
ValidationResultExample
Validating a changelog object
const result = validateChangelog(myChangelog)
if (!result.valid) {
console.log('Validation errors:', result.errors)
}◈ Interfaces
Complete representation of a CHANGELOG.md file. Designed for lossless round-tripping: parse -> modify -> serialize.
Properties
Represents a single version entry in a changelog.
Properties
The header section of a changelog file.
Properties
Represents an individual change within a changelog section.
Properties
Represents a link defined in the changelog header or elsewhere.
Additional information extracted during parsing.
Properties
Represents a category of changes (Features, Bug Fixes, etc.)
Properties
Represents a reference to a git commit in a changelog item.
Properties
Properties
Represents a reference to an issue or pull request.
Properties
Properties
◆ Types
Detected format/style of the changelog file.
type ChangelogFormat = "keep-a-changelog" | "conventional" | "custom" | "unknown"Categories for grouping changes in a changelog entry.
type ChangelogSectionType = "breaking" | "features" | "fixes" | "performance" | "documentation" | "deprecations" | "refactoring" | "tests" | "build" | "ci" | "chores" | "other"● Variables
Immutable changelog operations for adding, removing, filtering, merging, and transforming.
ƒ Functions
addEntry( changelog: Changelog, entry: ChangelogEntry, options?: AddEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to add to |
§entry | ChangelogEntry | The entry to add |
§options? | AddEntryOptions | Optional add options |
Returns
ChangelogExample
Adding a new entry to a changelog
const newChangelog = addEntry(changelog, {
version: '1.2.0',
date: '2024-01-15',
unreleased: false,
sections: [...]
})addItemToEntry( changelog: Changelog, version: string, sectionType: string, item: ChangelogItem): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog containing the entry to modify |
§version | string | The version identifier of the entry to update |
§sectionType | string | Category identifier for grouping changes (e.g., 'features', 'fixes') |
§item | ChangelogItem | Description of the change with optional scope and metadata |
Returns
ChangelogExample
Adding a feature item to an entry
const item = { description: 'Add dark mode support', scope: 'ui' }
const updated = addItemToEntry(changelog, '1.2.0', 'features', item)
// Item added to the features section of version 1.2.0Parameters
Returns
ChangelogExample
Adding an unreleased entry with sections
const sections = [createChangelogSection('features', 'Added', [item])]
const updated = addUnreleasedEntry(changelog, sections)
// => Changelog with unreleased entry at the topappendChangelog( source: Changelog, target: Changelog, position: "start" | "end"): Changelog
Parameters
Returns
ChangelogExample
Appending entries to a changelog
const combined = appendChangelog(mainChangelog, newChangelog, 'start')
// newChangelog entries appear before mainChangelog entriesReturns
ChangelogExample
Cloning a changelog
const copy = cloneChangelog(changelog)
// Independent deep copy, safe to mutateParameters
| Name | Type | Description |
|---|---|---|
§changelogs | unknown | Array of changelogs to combine |
§options? | MergeOptions | Optional merge options |
Returns
ChangelogExample
Combining multiple changelogs
const unified = combineChangelogs([pkg1Changelog, pkg2Changelog], {
strategy: 'merge-sections',
})
// All entries from both changelogs merged with conflict resolutionParameters
Returns
ChangelogExample
Compacting a changelog
const compacted = compact(changelog)
// Empty sections and entries removed (unreleased kept)
const strict = compact(changelog, false)
// Even empty unreleased entry is removedReturns
ChangelogExample
Deduplicating items across sections
const deduped = deduplicateItems(changelog)
// Duplicate items (same scope:description) are removedParameters
Returns
ChangelogExample
Excluding items by scope
const publicChanges = excludeByScope(changelog, ['internal', 'test'])
// Items scoped to 'internal' or 'test' are removedReturns
ChangelogExample
Filtering for breaking changes
const breaking = filterBreakingChanges(changelog)
// Only entries containing breaking changes or items marked as breakingParameters
Returns
ChangelogExample
Filtering entries by date range
const q1 = filterByDateRange(changelog, '2024-01-01', '2024-03-31')
// Entries released in Q1 2024Parameters
Returns
ChangelogExample
Filtering items by scope
const apiChanges = filterByScope(changelog, ['api', 'core'])
// Only items scoped to 'api' or 'core'Parameters
Returns
ChangelogExample
Filtering by semver range
const majors = filterByVersionRange(changelog, '>=1.0.0 <2.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | EntryPredicate | Function that returns true for entries to keep |
Returns
ChangelogExample
Filtering out unreleased entries
const filtered = filterEntries(changelog, (entry) => !entry.unreleased)Parameters
Returns
ChangelogExample
Filtering from a start version
const recent = filterFromVersion(changelog, '2.0.0')
// Only entries for version 2.0.0 and laterParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | ItemPredicate | Function that returns true for items to keep |
Returns
ChangelogExample
Filtering items with references
const withRefs = filterItems(changelog, (item) => item.references.length > 0)
// Only items with issue/PR referencesfilterRecentEntries( changelog: Changelog, count: number, includeUnreleased: boolean): Changelog
Parameters
Returns
ChangelogExample
Getting the most recent entries
const latest = filterRecentEntries(changelog, 5)
// Last 5 released versions (plus unreleased if present)Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to filter |
§predicate | SectionPredicate | Function that returns true for sections to keep |
Returns
ChangelogExample
Filtering for user-facing sections
const userFacing = filterSections(changelog, (section) =>
['features', 'fixes', 'breaking'].includes(section.type)
)Parameters
Returns
ChangelogExample
Filtering to specific section types
const userChanges = filterSectionTypes(changelog, ['features', 'fixes'])
// Only features and fixes sections remainParameters
Returns
ChangelogExample
Filtering to an end version
const legacy = filterToVersion(changelog, '1.9.9')
// Only entries for versions up to 1.9.9filterVersionRange( changelog: Changelog, startVersion: string, endVersion: string): Changelog
Parameters
Returns
ChangelogExample
Filtering entries within a version range
const range = filterVersionRange(changelog, '1.5.0', '2.0.0')
// Entries from 1.5.0 through 2.0.0Parameters
Returns
MergeResultExample
Merging two changelogs
const result = mergeChangelogs(mainChangelog, branchChangelog)
console.log(`Merged ${result.stats.merged} entries`)Returns
ChangelogExample
Normalizing section headings
const normalized = normalizeSectionHeadings(changelog)
// 'New Features' becomes 'Added', 'Bug Fixes' becomes 'Fixed', etc.releaseUnreleased( changelog: Changelog, version: string, date?: string, compareUrl?: string): Changelog
Parameters
Returns
ChangelogExample
Releasing unreleased changes as a new version
const released = releaseUnreleased(changelog, '2.0.0', '2024-03-01')
// Unreleased entry becomes version 2.0.0 with the specified dateParameters
Returns
ChangelogExample
Removing empty entries
const cleaned = removeEmptyEntries(changelog)
// Entries with no content are removed (unreleased kept by default)
const strict = removeEmptyEntries(changelog, false)
// Even empty unreleased entry is removedReturns
ChangelogExample
Removing empty sections from all entries
const cleaned = removeEmptySections(changelog)
// Sections with no items are removed from all entriesremoveEntries( changelog: Changelog, versions: unknown, options?: RemoveEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove from |
§versions | unknown | The versions to remove |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing multiple entries
const cleaned = removeEntries(changelog, ['0.1.0', '0.2.0'])
// Pre-release versions removed from changelogremoveEntry( changelog: Changelog, version: string, options?: RemoveEntryOptions): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove from |
§version | string | The version to remove |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing an entry by version
const newChangelog = removeEntry(changelog, '1.0.0')removeItem( changelog: Changelog, version: string, sectionType: string, itemDescription: string, options?: RemoveSectionOptions): Changelog
Parameters
Returns
ChangelogExample
Removing a specific item from a section
const updated = removeItem(changelog, '1.0.0', 'features', 'Add dark mode')
// The 'Add dark mode' item is removed from features in 1.0.0removeSection( changelog: Changelog, version: string, sectionType: string, options?: RemoveSectionOptions): Changelog
Parameters
Returns
ChangelogExample
Removing a deprecated section
const updated = removeSection(changelog, '1.0.0', 'deprecated')
// Version 1.0.0 no longer has a deprecated sectionParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to remove the unreleased entry from |
§options? | RemoveEntryOptions | Optional removal options |
Returns
ChangelogExample
Removing the unreleased entry
const released = removeUnreleased(changelog)
// Changelog without the unreleased entry
// Silently ignore if not found
const safe = removeUnreleased(changelog, { throwIfNotFound: false })Returns
ChangelogExample
Reversing entry order
const reversed = reverseEntries(changelog)
// Oldest entries now appear firstReturns
ChangelogExample
Sorting changelog entries by version
const sorted = sortEntries(changelog)
// Entries ordered: Unreleased, 2.0.0, 1.1.0, 1.0.0, ...Returns
ChangelogExample
Sorting entries by date
const sorted = sortEntriesByDate(changelog)
// Entries ordered by release date, most recent firstParameters
Returns
ChangelogExample
Sorting sections within entries
const sorted = sortSections(changelog)
// Sections in each entry follow: breaking, features, fixes, ...
const custom = sortSections(changelog, ['fixes', 'features', 'breaking'])
// Custom ordering: fixes first, then features, then breakingReturns
ChangelogExample
Stripping metadata from a changelog
const stripped = stripMetadata(changelog)
// Source and warnings removed, only format and isConventional retainedParameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | EntryTransformer | Function to transform each entry |
Returns
ChangelogExample
Transforming all entries
const transformed = transformEntries(changelog, (entry) => ({
...entry,
date: entry.date?.toUpperCase()
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | ItemTransformer | Function to transform each item |
Returns
ChangelogExample
Prefixing item descriptions with scope
const prefixed = transformItems(changelog, (item) => ({
...item,
description: `[${item.scope || 'misc'}] ${item.description}`,
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to transform |
§transformer | SectionTransformer | Function to transform each section |
Returns
ChangelogExample
Uppercasing section headings
const renamed = transformSections(changelog, (section) => ({
...section,
heading: section.heading.toUpperCase(),
}))updateEntry( changelog: Changelog, version: string, updates: Partial< ChangelogEntry> | ( entry: ChangelogEntry) => ChangelogEntry): Changelog
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§version | string | Version of entry to update |
§updates | Partial< | Partial entry updates or transformer function |
Returns
ChangelogExample
Updating a specific entry
const updated = updateEntry(changelog, '1.0.0', { date: '2024-01-15' })
// Or with transformer function
const modified = updateEntry(changelog, '1.0.0', (entry) => ({
...entry,
compareUrl: `https://github.com/org/repo/compare/v0.9.0...v${entry.version}`,
}))Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§updates | Partial< | Partial header updates |
Returns
ChangelogExample
Updating the changelog header
const updated = updateHeader(changelog, { title: '# Release Notes' })Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to update |
§updates | Partial< | Partial metadata updates |
Returns
ChangelogExample
Updating changelog metadata
const updated = updateMetadata(changelog, { repositoryUrl: 'https://github.com/org/repo' })◈ Interfaces
Properties
Properties
Properties
Properties
Properties
Properties
◆ Types
type EntryPredicate = ( entry: ChangelogEntry, index: number) => booleantype EntryTransformer = ( entry: ChangelogEntry, index: number) => ChangelogEntrytype ItemPredicate = ( item: ChangelogItem, section: ChangelogSection, entry: ChangelogEntry) => booleantype ItemTransformer = ( item: ChangelogItem, section: ChangelogSection, entry: ChangelogEntry) => ChangelogItemtype MergeStrategy = "source" | "target" | "union" | "latest"type SectionPredicate = ( section: ChangelogSection, entry: ChangelogEntry) => booleantype SectionTransformer = ( section: ChangelogSection, entry: ChangelogEntry) => ChangelogSection● Variables
Tokenizer and parser for converting Keep-a-Changelog markdown into structured objects.
ƒ Functions
Recognized openers, case-insensitive and repeatable:
BREAKING, BREAKING:, [BREAKING], ⚠️ BREAKING:, a bare BREAKING:, and the BREAKING CHANGE/BREAKING CHANGES spelling of each. A bare mention needs its colon to count, so prose such as Breaking apart the parser is left alone. Scanning is character-by-character to stay ReDoS-safe.Parameters
| Name | Type | Description |
|---|---|---|
§text | string | The changelog item text, without its list marker |
Returns
BreakingFromItemExample
Lifting markers out of item text
parseBreakingFromItem('**BREAKING** **BREAKING:** drop the sync open')
// => { breaking: true, text: 'drop the sync open' }
parseBreakingFromItem('add a retry budget')
// => { breaking: false, text: 'add a retry budget' }Parameters
Returns
ChangelogExample
Parsing a changelog markdown string
const markdown = `# Changelog
## [1.0.0] - 2024-01-15
### Added
- Initial release`
const changelog = parseChangelog(markdown, 'CHANGELOG.md')
// => { header: { title: '# Changelog', ... }, entries: [{ version: '1.0.0', ... }] }Parameters
Returns
CommitRef[ ]Example
Parsing commit references from text
parseCommitRefs('Fixed bug (abc1234)', 'https://github.com/org/repo')
// => [{ hash: 'abc1234', shortHash: 'abc1234', url: 'https://github.com/org/repo/commit/abc1234' }]Parameters
Returns
IssueRef[ ]Example
Parsing issue references from text
parseIssueRefs('Closes #42 and PR #123', 'https://github.com/org/repo')
// => [{ number: 42, type: 'issue', url: '...' }, { number: 123, type: 'pull-request', url: '...' }]Parameters
| Name | Type | Description |
|---|---|---|
§text | string | The text to parse for scope |
Returns
ScopeFromItemExample
Parsing scope from changelog items
parseScopeFromItem('**api:** Add new endpoint')
// => { scope: 'api', description: 'Add new endpoint' }
parseScopeFromItem('Simple change without scope')
// => { scope: undefined, description: 'Simple change without scope' }Parameters
| Name | Type | Description |
|---|---|---|
§heading | string | The heading string to parse |
Returns
ParsedVersionHeadingExample
Parsing version headings
parseVersionFromHeading('[1.2.3] - 2024-01-15')
// => { version: '1.2.3', date: '2024-01-15', compareUrl: undefined }
parseVersionFromHeading('v2.0.0')
// => { version: '2.0.0', date: null, compareUrl: undefined }Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The markdown content to tokenize |
Returns
Token[ ]Example
Tokenizing changelog markdown
const tokens = tokenize('# Changelog\n\n## [1.0.0]\n- Added feature')
// => [{ type: 'heading-1', value: 'Changelog', ... }, { type: 'heading-2', ... }, ...]◈ Interfaces
Properties
◆ Types
type TokenType = "heading-1" | "heading-2" | "heading-3" | "heading-4" | "list-item" | "link-text" | "link-url" | "text" | "newline" | "blank-line" | "bold" | "code" | "eof"Serializers for converting Changelog objects to markdown strings or JSON.
ƒ Functions
Returns
stringExample
Creating blank line spacing
createSpacing(2, '\n')
// => '\n\n'Parameters
Returns
stringExample
Formatting a markdown link
formatLink('1.0.0', 'https://github.com/org/repo/releases/tag/v1.0.0')
// => '[1.0.0](https://github.com/org/repo/releases/tag/v1.0.0)'Parameters
| Name | Type | Description |
|---|---|---|
§useAsterisks | boolean | Whether to use * instead of - |
Returns
stringExample
Getting list markers
getListMarker(false) // => '- '
getListMarker(true) // => '* 'getSectionHeading( type: ChangelogSectionType, customHeadings?: Partial< Record< ChangelogSectionType, string>>): string
Parameters
| Name | Type | Description |
|---|---|---|
§type | ChangelogSectionType | The changelog section type to get the heading for |
§customHeadings? | Partial< | Optional map of custom section type to heading overrides |
Returns
stringExample
Getting section headings
getSectionHeading('features')
// => 'Added'
getSectionHeading('features', { features: 'New Features' })
// => 'New Features'Parameters
| Name | Type | Description |
|---|---|---|
§options? | SerializeOptions | User-provided options |
Returns
Required< SerializeOptions>Example
Resolving serialize options with defaults
const opts = resolveOptions({ includeCommits: true })
// => { includeCommits: true, includeScope: true, lineEnding: '\n', ... }Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to serialize |
§options? | SerializeOptions | Optional serialization options |
Returns
stringExamples
Basic serialization
const markdown = serializeChangelog(changelog)Serializing with custom options
const markdown = serializeChangelog(changelog, {
includeCommits: false,
useAsterisks: true,
})Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog object to convert to JSON |
§options? | JsonSerializeOptions | Optional JSON serialization options |
Returns
stringExamples
Basic JSON serialization
const json = serializeChangelogToJson(changelog)Pretty-printing with custom indentation
const json = serializeChangelogToJson(changelog, {
pretty: true,
indent: 4,
})toJsonObject( changelog: Changelog, options?: JsonSerializeOptions): Record< string, unknown>
Parameters
| Name | Type | Description |
|---|---|---|
§changelog | Changelog | The changelog to convert |
§options? | JsonSerializeOptions | Optional JSON serialization options |
Returns
Record< string, unknown>Example
Converting changelog to plain object
const obj = toJsonObject(changelog, { includeSource: true })
// => { source: 'CHANGELOG.md', header: { title: '...', ... }, entries: [...] }
// Send as API response
res.json(obj)◈ Interfaces
Properties
Properties
readonly sectionHeadings?:Partial< Record< ChangelogSectionType, string>>Custom section headings (overrides defaults)● Variables
Conventional commit parsing and classification with scope derivation and infrastructure matching.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§...matchers | unknown | Matchers to combine |
Returns
InfrastructureMatcherExample
Combining matchers with AND logic
const combined = allOf(
scopeMatcher(['deps']),
messageMatcher(['security'])
)Parameters
| Name | Type | Description |
|---|---|---|
§...matchers | unknown | Matchers to combine |
Returns
InfrastructureMatcherExample
Combining matchers with OR logic
const combined = anyOf(
scopeMatcher(['ci', 'build']),
messageMatcher(['[infra]']),
custom((ctx) => ctx.scope.some((s) => s.startsWith('tool-')))
)Combines scope-based matching with any custom matcher using OR logic. Path-based matching is handled separately via git queries.
Parameters
| Name | Type | Description |
|---|---|---|
§config | InfrastructureConfig | Infrastructure configuration |
Returns
InfrastructureMatcherExample
Building an infrastructure matcher
const matcher = buildInfrastructureMatcher({
scopes: ['ci', 'build'],
matcher: (ctx) => ctx.scope.some((s) => s.startsWith('tool-'))
})Implements the hybrid classification strategy:
- Check scope match (fast path)
- Check file touch (validation/catch-all)
- Check dependency touch (indirect)
- Fallback to excluded
Parameters
| Name | Type | Description |
|---|---|---|
§input | CommitWithRaw | The commit to classify |
§context | ClassificationContext | Classification context with project info |
Returns
ClassifiedCommitExample
Classifying a single commit
const classified = classifyCommit(
{ commit: parsedCommit, raw: gitCommit },
{ projectScopes: ['versioning'], fileCommitHashes: new Set(['abc123']) }
)Parameters
| Name | Type | Description |
|---|---|---|
§commits | unknown | Array of commits to classify |
§context | ClassificationContext | Classification context with project info |
Returns
ClassificationResultExample
Classifying multiple commits
const commits = [{ commit: parsedCommit, raw: gitCommit }]
const context = createClassificationContext(['auth'], fileHashes)
const result = classifyCommits(commits, context)
// => { commits: [...], included: [...], excluded: [...], summary: { total: 1, ... } }createClassificationContext( projectScopes: unknown, fileCommitHashes: ReadonlySet< string>, options?: ClassificationContextOptions): ClassificationContext
Parameters
| Name | Type | Description |
|---|---|---|
§projectScopes | unknown | Scopes that match the project |
§fileCommitHashes | ReadonlySet< | Set of commit hashes that touched project files |
§options? | ClassificationContextOptions | Additional context options |
Returns
ClassificationContextExample
Creating a classification context
const context = createClassificationContext(
['auth', 'lib-auth'],
new Set(['abc123', 'def456']),
{ excludeScopes: ['release'] }
)createClassifiedCommit( commit: ConventionalCommit, raw: GitCommit, source: CommitSource, options?: CreateClassifiedCommitOptions): ClassifiedCommit
Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | The parsed conventional commit |
§raw | GitCommit | The raw git commit |
§source | CommitSource | How the commit relates to the project |
§options? | CreateClassifiedCommitOptions | Additional classification options |
Returns
ClassifiedCommitExample
Creating a classified commit
const commit = { type: 'feat', subject: 'add feature', footers: [], breaking: false, raw: '...' }
const raw = { hash: 'abc123', subject: 'feat: add feature', message: '...' }
const classified = createClassifiedCommit(commit, raw, 'direct-scope')
// => { commit, raw, source: 'direct-scope', include: true, preserveScope: false, ... }Returns
ClassificationSummaryExample
Creating an empty classification summary
const summary = createEmptyClassificationSummary()
// => { total: 0, included: 0, excluded: 0, bySource: { 'direct-scope': 0, ... } }Extracts scope from conventional commit message if present.
Parameters
Returns
InfrastructureMatchContextExample
Creating match context from a git commit
const commit = { hash: 'abc123', subject: 'chore(ci): update workflow', message: 'chore(ci): update workflow' }
createMatchContext(commit, ['ci'])
// => { commit, scope: ['ci'], subject: 'chore(ci): update workflow', message: 'chore(ci): update workflow' }Given a project named 'lib-versioning' with package '@hyperfrontend/versioning', this generates variations like:
- 'lib-versioning' (full project name)
- 'versioning' (without lib- prefix)
Parameters
| Name | Type | Description |
|---|---|---|
§options | DeriveProjectScopesOptions | Project identification options |
Returns
unknownExamples
Deriving scopes for a library project
deriveProjectScopes({ projectName: 'lib-versioning', packageName: '@hyperfrontend/versioning' })
// Returns: ['lib-versioning', 'versioning']Deriving scopes for an app project
deriveProjectScopes({ projectName: 'app-demo', packageName: 'demo-app' })
// Returns: ['app-demo', 'demo']evaluateInfrastructure( commit: GitCommit, matcher: InfrastructureMatcher, scope?: unknown): boolean
Parameters
| Name | Type | Description |
|---|---|---|
§commit | GitCommit | Git commit to evaluate |
§matcher | InfrastructureMatcher | Matcher function to apply |
§scope? | unknown | Pre-parsed scope (optional) |
Returns
booleanExample
Evaluating a commit against infrastructure matcher
const commit = { hash: 'abc123', subject: 'chore(ci): update workflow', message: '...' }
const ciMatcher = (ctx) => ctx.scope.includes('ci')
evaluateInfrastructure(commit, ciMatcher, ['ci'])
// => trueParameters
| Name | Type | Description |
|---|---|---|
§commits | unknown | Array of classified commits |
Returns
unknownExample
Extracting conventional commits
const classified = classifyCommits(commits, context)
const conventional = extractConventionalCommits(classified.included)
// => [{ type: 'feat', subject: 'add login', ... }]Parameters
| Name | Type | Description |
|---|---|---|
§commits | unknown | Array of classified commits |
Returns
unknownExample
Filtering for included commits
const classified = classifyCommits(commits, context)
const included = filterIncluded(classified.commits)
// => [{ commit, raw, source: 'direct-scope', include: true, ... }]Parameters
| Name | Type | Description |
|---|---|---|
§patterns | unknown | Patterns to search for in commit message (case-insensitive) |
Returns
InfrastructureMatcherExample
Matching message patterns
const matcher = messageMatcher(['[infra]', '[ci skip]'])Parameters
| Name | Type | Description |
|---|---|---|
§matcher | InfrastructureMatcher | Matcher to negate |
Returns
InfrastructureMatcherExample
Negating a matcher
const notRelease = not(scopeMatcher(['release']))A commit is excluded only when every scope entry matches an exclude scope. Commits with no scope entries are never considered excluded here.
Parameters
Returns
booleanExample
Checking if a scope should be excluded
scopeIsExcluded(['release'], ['release', 'deps'])
// => true
scopeIsExcluded(['auth'], ['release', 'deps'])
// => false
scopeIsExcluded([], ['release'])
// => false
scopeIsExcluded(['release', 'auth'], ['release'])
// => false (not every scope is excluded)Parameters
| Name | Type | Description |
|---|---|---|
§scopes | unknown | Scopes to match against (case-insensitive) |
Returns
InfrastructureMatcherExample
Matching against specific scopes
const matcher = scopeMatcher(['ci', 'build', 'tooling'])
matcher({ scope: ['CI'], ... }) // true
matcher({ scope: ['feat'], ... }) // false
matcher({ scope: ['feat', 'ci'], ... }) // true (any element matches)A commit matches the project when any of its scope entries matches a project scope. Empty commit scopes never match.
Parameters
Returns
booleanExample
Matching scope to project
scopeMatchesProject(['versioning'], ['lib-versioning', 'versioning']) // true
scopeMatchesProject(['logging'], ['lib-versioning', 'versioning']) // false
scopeMatchesProject(['versioning', 'questions'], ['lib-questions']) // true
scopeMatchesProject([], ['lib-versioning']) // falseParameters
| Name | Type | Description |
|---|---|---|
§prefixes | unknown | Scope prefixes to match (case-insensitive) |
Returns
InfrastructureMatcherExample
Matching prefixed scopes
const matcher = scopePrefixMatcher(['tool-', 'infra-'])
matcher({ scope: ['tool-package'], ... }) // true
matcher({ scope: ['lib-utils'], ... }) // falseReturns
InfrastructureMatcherExample
Matching with regex pattern
const matcher = scopeRegexMatcher(/^(ci|build|tool)-.+/)For direct commits, the scope is removed (redundant in project changelog). For indirect commits, the scope is preserved (provides context).
Parameters
| Name | Type | Description |
|---|---|---|
§classified | ClassifiedCommit | Commit with classification metadata determining scope display |
Returns
ConventionalCommitExample
Converting classified commit for changelog
const classified = { commit: { type: 'feat', scope: ['auth'], subject: 'add login', ... }, source: 'direct-scope', preserveScope: false, ... }
toChangelogCommit(classified)
// => { type: 'feat', scope: [], subject: 'add login', ... }cancelled result with an optional error.Parameters
Returns
StepResultExample
Cancelling after an executor throws
try { await executor() } catch (err) { return cancelled(err as Error) }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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Draft supplying type, scope, and breaking marker |
§subject | string | Subject text being typed |
Returns
numberExample
Counting a header mid-type
countHeaderLength({ type: 'feat', scope: ['core'] }, 'add login')
// => 'feat(core): add login'.length === 21config (defaults filled in) and the effective step list, allowing bin wrappers to introspect before running.Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateAuthorSessionOptions | Optional overrides for steps and partial config(default: {}) |
Returns
AuthorSessionrunAuthorSessionExamples
Using the conventional preset and default config
const session = createAuthorSession()
await runAuthorSession(session)Overriding only the validate ruleset
createAuthorSession({ config: { validateRuleset: customRuleset } })Parameters
| Name | Type | Description |
|---|---|---|
§description? | string | Optional description of the breaking change |
Returns
BreakingChangeExample
Creating a breaking change from subject
createBreakingFromSubject('remove deprecated API')
// => { isBreaking: true, description: 'remove deprecated API', source: 'subject' }createConventionalCommit( type: string, subject: string, options?: CreateConventionalCommitOptions): ConventionalCommit
Parameters
Returns
ConventionalCommitExample
Creating conventional commits
createConventionalCommit('feat', 'add user authentication')
// => { type: 'feat', subject: 'add user authentication', scope: [], footers: [], breaking: false, raw: 'feat: add user authentication' }
createConventionalCommit('fix', 'resolve memory leak', { scope: ['core'], breaking: true })
// => { type: 'fix', subject: 'resolve memory leak', scope: ['core'], breaking: true, ... }
createConventionalCommit('feat', 'add x', { scope: ['versioning', 'questions'] })
// => { ..., scope: ['versioning', 'questions'], raw: 'feat(versioning,questions): add x' }Returns
BreakingChangeExample
Creating a non-breaking change
createNonBreaking()
// => { isBreaking: false, source: 'none' }Parameters
| Name | Type | Description |
|---|---|---|
§config | SessionConfig | Resolved session configuration |
Returns
SessionContextExample
Creating a bare context
createSessionContext(config)
// => { draft: {}, candidateScopes: [], defaultScope: undefined, config }scope step. Algorithm (matches locked decision D3):
- A single candidate wins outright.
- Otherwise, if one candidate's path is a prefix of every other
- Otherwise, there is no default.
Parameters
| Name | Type | Description |
|---|---|---|
§candidates | unknown | Scopes produced by discoverScopes |
Returns
stringExamples
Single owner wins
defaultScope([{ name: 'alpha', path: '/repo/libs/alpha' }])
// => 'alpha'Common ancestor wins
defaultScope([
{ name: 'root', path: '/repo' },
{ name: 'alpha', path: '/repo/libs/alpha' },
])
// => 'root'Disjoint ownership has no default
defaultScope([
{ name: 'alpha', path: '/repo/libs/alpha' },
{ name: 'beta', path: '/repo/libs/beta' },
])
// => undefinedproject.json / package.json, reads the name field, and returns the unique set as DiscoveredScope entries. Entries inside node_modules or .git are dropped before walking; the caller's scopeFilter is applied after collection.Parameters
| Name | Type | Description |
|---|---|---|
§stagedPaths | unknown | Paths emitted by the staged-paths provider (relative or absolute) |
§options | DiscoverScopesOptions | Filter + cwd configuration |
Returns
unknownExample
Discovering scopes for a set of staged files
discoverScopes(['libs/versioning/src/a.ts', 'libs/questions/src/b.ts'], { cwd: '/repo' })
// => [
// { name: '@hyperfrontend/versioning', path: '/repo/libs/versioning' },
// { name: '@hyperfrontend/questions', path: '/repo/libs/questions' },
// ]done result.Returns
StepResultExample
Returning done from a step
async run(): Promise<StepResult> { return done() }.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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Commit draft to render |
Returns
string\n separatorsExamples
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'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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Draft to render (may be partial) |
Returns
stringExamples
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: 'Parameters
Returns
"minor" | "patch" | "none" | "major"Example
Determining semver bump level
getSemverBump('feat', false)
// => 'minor'
getSemverBump('fix', true)
// => 'major'
getSemverBump('docs', false)
// => 'none'git rev-parse --show-toplevel, reads git diff --cached --name-only -z, and returns each staged path as an absolute path anchored at that root. Git emits staged paths relative to the repository root no matter which directory the command runs from, so anchoring here keeps downstream project-root discovery correct when the session cwd is a subdirectory.
Parameters
| Name | Type | Description |
|---|---|---|
§options | StagedPathsOptions | Resolver options (cwd, optional timeout) |
Returns
unknownExample
Reading the current staging area from a subdirectory
getStagedPaths({ cwd: '/repo/apps/demo' })
// => ['/repo/libs/versioning/src/commits/author/index.ts']goto result pointing at the supplied step id.Parameters
| Name | Type | Description |
|---|---|---|
§stepId | string | Target step id the runner should jump to |
Returns
StepResultExample
Sending the runner back to the `type` step
return goto('type')Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The commit message to check |
Returns
booleanExample
Checking if a message is a conventional commit
isConventionalCommit('feat(auth): add OAuth login')
// => true
isConventionalCommit('WIP: still working on this')
// => false
isConventionalCommit('fix: resolve bug')
// => trueParameters
| Name | Type | Description |
|---|---|---|
§type | string | The commit type to check |
Returns
booleanExample
Checking for release-triggering types
isReleaseType('feat')
// => true
isReleaseType('chore')
// => falseParameters
| Name | Type | Description |
|---|---|---|
§type | string | The commit type to check |
Returns
unknownExample
Checking for standard commit types
isStandardType('feat')
// => true
isStandardType('custom')
// => falseoverridePathwins when set- Otherwise walk upward from
cwdlooking for one ofCONFIG_FILE_NAMES
.git, pnpm-workspace.yaml) is hit - Missing config is not an error:
configbecomes{}
Parameters
| Name | Type | Description |
|---|---|---|
§options | LoadCommitConfigOptions | Search inputs |
Returns
Promise< LoadedCommitConfig>Example
Auto-discovery from cwd
const { config, sourcePath } = await loadCommitConfig({ cwd: process.cwd() })Parameters
| Name | Type | Description |
|---|---|---|
§raw | string | Subject string as entered by the user |
Returns
stringExample
Stripping whitespace and a trailing period
normalizeSubject(' add login flow. ') // => 'add login flow'The body starts after the first blank line and continues until we encounter a footer (key: value or key #value pattern) or end of message.
Parameters
Returns
ParsedBodyExample
Parsing commit body
const lines = [
'feat: add login',
'',
'Implements OAuth flow.',
'',
'Refs: #123'
]
const result = parseBody(lines, 1)
// => { body: 'Implements OAuth flow.', endIndex: 4 }Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The complete commit message |
Returns
ConventionalCommitExample
Parsing a complete conventional commit message
parseConventionalCommit('feat(auth): add login\n\nImplements OAuth.\n\nRefs: #123')
// => {
// type: 'feat',
// scope: ['auth'],
// subject: 'add login',
// body: 'Implements OAuth.',
// footers: [{ key: 'Refs', value: '#123', separator: ':' }],
// breaking: false,
// raw: '...'
// }Supports comma-separated multi-scope headers such as
feat(a,b): x which produce a multi-element scope array. Single-scope headers produce a one-element array, and scopeless headers produce an empty array.Parameters
| Name | Type | Description |
|---|---|---|
§line | string | The first line of the commit message |
Returns
ParsedHeaderExample
Parsing conventional commit headers
parseHeader('feat(auth): add OAuth login')
// => { type: 'feat', scope: ['auth'], subject: 'add OAuth login', breaking: false }
parseHeader('fix!: critical security patch')
// => { type: 'fix', scope: [], subject: 'critical security patch', breaking: true }
parseHeader('feat(versioning,questions): add searchable select')
// => { type: 'feat', scope: ['versioning', 'questions'], subject: 'add searchable select', breaking: false }keyword #number shape.Parameters
| Name | Type | Description |
|---|---|---|
§raw | string | Raw user input from the issues prompt |
Returns
unknownExample
Parsing a mixed references string
parseReferences('fixes #123, re #456')
// => [
// { key: 'Fixes', value: '123', separator: ' #' },
// { key: 'Re', value: '456', separator: ' #' },
// ]SessionConfig usable by the runner.Parameters
| Name | Type | Description |
|---|---|---|
§overrides | PartialSessionConfig | Partial config supplied by the caller (may be undefined)(default: {}) |
Returns
SessionConfigExample
Resolving with no overrides
resolveSessionConfig()
// => { types: DEFAULT_SESSION_TYPES, scopeOptional: false, ... }goto jumps, surfaces Ctrl-C cancellations as { status: 'cancelled' } outcomes, and (on success) returns the final formatted message alongside the committed status.Parameters
| Name | Type | Description |
|---|---|---|
§session | AuthorSession | Session description produced by createAuthorSession |
Returns
Promise< SessionOutcome>Example
Running a session to completion
const session = createAuthorSession({ config: { skipCommit: true } })
const outcome = await runAuthorSession(session)
// => { status: 'committed', message: 'feat: ...' }ConventionalCommit to a CommitDraft. Useful for editing a parsed commit inside an authoring session.Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | Parsed conventional commit |
Returns
CommitDraftExample
Promoting a parsed commit to an editable draft
toDraft(parseConventionalCommit('feat: add login'))
// => { type: 'feat', scope: [], subject: 'add login', footers: [], breaking: false }SessionType to a Choice usable by the select prompt.Parameters
| Name | Type | Description |
|---|---|---|
§type | SessionType | Session type entry to render as a selectable choice |
Returns
Choice< string>Example
Rendering the feat type as a select choice
typeToChoice({ name: 'feat', description: 'A new feature' })
// => { label: 'feat', value: 'feat', hint: 'A new feature' }Rules whose config is
'off' (or absent from the ruleset) are skipped. Unknown rule names in the ruleset are ignored: consumers extend the engine by calling validateCommitWithRules() and supplying additional rules.Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | Parsed conventional commit to validate |
§ruleset | Ruleset | Configuration map from rule name to [level, options?] |
Returns
ValidationResultExample
Validating a commit against a preset ruleset
validateCommit(parseConventionalCommit('feat: add login'), conventionalPreset)
// => { valid: true, warnings: [], errors: [] }cl bin and anywhere else validation must start from a string rather than a parsed commit.Parameters
Returns
ValidationResultExample
Validating a raw commit message
validateCommitMessage('feat: add login', conventionalPreset)
// => { valid: true, warnings: [], errors: [] }
validateCommitMessage('feat: added login', conventionalPreset).warnings
// => [{ level: 'warn', ruleName: 'imperative-mood', message: ... }]validateCommitWithRules( commit: ConventionalCommit, ruleset: Ruleset, rules: Readonly< Record< string, Rule< never>>>): ValidationResult
validateCommit that accepts a caller-supplied rule registry. Useful for consumers that want to add project-specific rules without forking the built-in set.Parameters
Returns
ValidationResultExample
Extending the built-in rules with a custom rule
const customRules = { ...BUILT_IN_RULES, 'no-wip': noWipRule }
validateCommitWithRules(commit, { 'no-wip': ['error'] }, customRules)◈ Interfaces
Properties
readonly dependencyCommitMap?:ReadonlyMap< string, ReadonlySet< string>>Map of dependency name to set of commit hashes touching that dependencyreadonly infrastructureCommitHashes?:ReadonlySet< string>Set of commit hashes that touched infrastructure paths. These commits will be classified as 'indirect-infra'.Properties
Properties
Contains the original commit data plus attribution information that determines inclusion and presentation in changelogs.
Properties
readonly dependencyPath?:unknownDependency chain if indirect (e.g., ['lib-utils'] for lib-app → lib-utils)Properties
Properties
Supports multiple detection methods that can be combined:
- Path-based: Commits touching specific file paths
- Scope-based: Commits with specific conventional scopes
- Custom matcher: User-provided matching logic
Properties
readonly matcher?:InfrastructureMatcherCustom matcher function for complex logic. Combined with paths/scopes using OR logic.readonly paths?:unknownFile/directory paths that indicate infrastructure changes. Used to query git for commits touching these paths.readonly scopes?:unknownConventional commit scopes that indicate infrastructure. Matched against parsed commit scope.Contains information about a commit that helps determine whether it should be classified as infrastructure-related.
Properties
runAuthorSession. Contains the fully resolved config and the ordered step list that will be executed.Properties
Properties
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 breakingDescription?:stringBreaking change description: surfaces as a BREAKING CHANGE: footer when none is already presentProperties
createAuthorSession: the caller may supply their own step list; anything missing is resolved against the defaults used by the conventional preset.Properties
Properties
discoverScopes.Properties
readonly scopeFilter?:( context: ScopeFilterContext) => booleanOptional caller-provided filter; applied after built-in exclusionsProperties
Properties
loadCommitConfig.Properties
loadCommitConfig.Properties
readonly sourcePath?:stringAbsolute path of the config file that was loaded (undefined when nothing was found)Properties
Properties
ConventionalCommit and return zero or more violation messages. The engine applies severity from the ruleset config and aggregates RuleMessages across all rules.Properties
Properties
Properties
Properties
SessionConfig.scopeFilter.Properties
createAuthorSession) fills the gaps with defaults before handing the result to the runner.Properties
readonly imperativeWordlist:Readonly< Record< string, string>>Wordlist used by the imperative-mood rule in inline validationreadonly scopeFilter?:( context: ScopeFilterContext) => booleanOptional filter applied to discovered scopes; receives { path, name }readonly scopeOptional:booleanWhen true, the scope step may be skipped even if no candidates are foundreadonly skipCommit:booleanWhen true, the final commit step is a no-op (session returns the message only)readonly stagedPathsProvider:StagedPathsProviderResolver for the staged file list; defaults to git diff --cached --name-only -zdraft field accumulates the in-progress commit message; candidateScopes/defaultScope are filled by the resolve-scope step and consumed by later steps. config is the fully resolved SessionConfig shared across the session.Properties
runAuthorSession.Properties
readonly message?:stringFormatted commit message (populated whenever the draft reached the preview step)type step's choice list.Properties
Properties
Properties
Properties
Properties
readonly cases:unknownOne or more accepted case forms. A subject passing any accepted form is valid.Properties
Properties
◆ Types
Classification determines whether a commit should appear in a project's changelog and how its scope should be displayed.
type CommitSource = "direct-scope" | "direct-file" | "unscoped-file" | "indirect-dependency" | "indirect-infra" | "unscoped-global" | "excluded"Returns true if the commit should be classified as infrastructure-related. Matchers are composable - combine them using
anyOf, allOf, and not.type InfrastructureMatcher = ( context: InfrastructureMatchContext) => booleantype CommitExecutor = ( message: string) => void | Promise< void>type CommitType = "feat" | "fix" | "docs" | "style" | "refactor" | "perf" | "test" | "build" | "ci" | "chore" | "revert" | stringcreateAuthorSession/loadCommitConfig.type PartialSessionConfig = Partial< SessionConfig>A bare
[level] entry disables the rule when level is 'off', or enables it with no options otherwise. The [level, options] form passes rule-specific options through to the rule's check() implementation.type RuleConfig = unknown | unknowntype RuleLevel = "off" | "warn" | "error"type RuleResult = unknown'off' by the engine.type Ruleset = Readonly< Record< string, RuleConfig>>type SessionStatus = Readonly< { Cancelled: "cancelled"; Committed: "committed" }>type StagedPathsProvider = ( options: StagedPathsProviderOptions) => unknowntype StepStatus = Readonly< { Cancelled: "cancelled"; Done: "done"; Goto: "goto" }>type SubjectCase = "sentence-case" | "lower-case" | "upper-case" | "kebab-case" | "snake-case" | "start-case"● Variables
Matches: ci, cd, build, pipeline, workflow, actions
Combines CI, tooling, and tool-prefix matchers.
Matches: tooling, workspace, monorepo, nx, root
draft.body unset (the formatter then omits the body section).draft.breakingDescription and is eventually synthesized into a BREAKING CHANGE: footer by formatCommitMessage.config.skipCommit is true the step short-circuits so the caller receives the formatted message without touching the working tree.@commitlint/config-conventional's baseline. | Rule | Level | Options | | ------------------ | ----- | ------------------------ | | type-enum | error |
CONVENTIONAL_TYPES | | subject-empty | error | — | | scope-enum | off | — | | subject-case | off | — | | header-max-length | warn | { maxLength: 72 } | | imperative-mood | warn | — |createAuthorSession when the caller does not supply their own sequence. Matches decision D8: resolve-scope → type → scope → subject → body → breaking → issues → preview → commit.These represent repository-level or infrastructure changes that typically don't belong in individual project changelogs.
type step when the caller does not supply their own list. Each conventional type is paired with a short hint.type(scope)!: subject) exceeds the configured maximum length.fixes #123, re #456) and appends them to draft.footers using the # separator form. The step is additive: existing footers stay, and any breaking-change footer synthesized later by the formatter is unaffected.
type step while keeping the draft intact for round-trip editing. Validation warnings are printed before the prompt.Calls
config.stagedPathsProvider() → discoverScopes → defaultScope and writes the results into the context. Cancels the session when the staging area is empty (decision E1a).resolve-scope. Respects scopeOptional (cancels with an actionable error when no candidates exist and scope is required) and scopeMulti (multiselect vs. single-select).headerMaxLength is set, a live countdown re-renders on every keystroke (green → yellow → red as the remaining budget shrinks).ctx.draft.type (so a preview → cancel restart re-opens on the previous choice).Interactive commit authoring session: prompt-driven step sequence that produces a validated conventional commit message and (by default) runs `git commit`. See `./README.md` for architecture overview.
Commit classification logic for matching commits to project scopes and infrastructure changes.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§...matchers | unknown | Matchers to combine |
Returns
InfrastructureMatcherExample
Combining matchers with AND logic
const combined = allOf(
scopeMatcher(['deps']),
messageMatcher(['security'])
)Parameters
| Name | Type | Description |
|---|---|---|
§...matchers | unknown | Matchers to combine |
Returns
InfrastructureMatcherExample
Combining matchers with OR logic
const combined = anyOf(
scopeMatcher(['ci', 'build']),
messageMatcher(['[infra]']),
custom((ctx) => ctx.scope.some((s) => s.startsWith('tool-')))
)Combines scope-based matching with any custom matcher using OR logic. Path-based matching is handled separately via git queries.
Parameters
| Name | Type | Description |
|---|---|---|
§config | InfrastructureConfig | Infrastructure configuration |
Returns
InfrastructureMatcherExample
Building an infrastructure matcher
const matcher = buildInfrastructureMatcher({
scopes: ['ci', 'build'],
matcher: (ctx) => ctx.scope.some((s) => s.startsWith('tool-'))
})Implements the hybrid classification strategy:
- Check scope match (fast path)
- Check file touch (validation/catch-all)
- Check dependency touch (indirect)
- Fallback to excluded
Parameters
| Name | Type | Description |
|---|---|---|
§input | CommitWithRaw | The commit to classify |
§context | ClassificationContext | Classification context with project info |
Returns
ClassifiedCommitExample
Classifying a single commit
const classified = classifyCommit(
{ commit: parsedCommit, raw: gitCommit },
{ projectScopes: ['versioning'], fileCommitHashes: new Set(['abc123']) }
)Parameters
| Name | Type | Description |
|---|---|---|
§commits | unknown | Array of commits to classify |
§context | ClassificationContext | Classification context with project info |
Returns
ClassificationResultExample
Classifying multiple commits
const commits = [{ commit: parsedCommit, raw: gitCommit }]
const context = createClassificationContext(['auth'], fileHashes)
const result = classifyCommits(commits, context)
// => { commits: [...], included: [...], excluded: [...], summary: { total: 1, ... } }createClassificationContext( projectScopes: unknown, fileCommitHashes: ReadonlySet< string>, options?: ClassificationContextOptions): ClassificationContext
Parameters
| Name | Type | Description |
|---|---|---|
§projectScopes | unknown | Scopes that match the project |
§fileCommitHashes | ReadonlySet< | Set of commit hashes that touched project files |
§options? | ClassificationContextOptions | Additional context options |
Returns
ClassificationContextExample
Creating a classification context
const context = createClassificationContext(
['auth', 'lib-auth'],
new Set(['abc123', 'def456']),
{ excludeScopes: ['release'] }
)createClassifiedCommit( commit: ConventionalCommit, raw: GitCommit, source: CommitSource, options?: CreateClassifiedCommitOptions): ClassifiedCommit
Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | The parsed conventional commit |
§raw | GitCommit | The raw git commit |
§source | CommitSource | How the commit relates to the project |
§options? | CreateClassifiedCommitOptions | Additional classification options |
Returns
ClassifiedCommitExample
Creating a classified commit
const commit = { type: 'feat', subject: 'add feature', footers: [], breaking: false, raw: '...' }
const raw = { hash: 'abc123', subject: 'feat: add feature', message: '...' }
const classified = createClassifiedCommit(commit, raw, 'direct-scope')
// => { commit, raw, source: 'direct-scope', include: true, preserveScope: false, ... }Returns
ClassificationSummaryExample
Creating an empty classification summary
const summary = createEmptyClassificationSummary()
// => { total: 0, included: 0, excluded: 0, bySource: { 'direct-scope': 0, ... } }Extracts scope from conventional commit message if present.
Parameters
Returns
InfrastructureMatchContextExample
Creating match context from a git commit
const commit = { hash: 'abc123', subject: 'chore(ci): update workflow', message: 'chore(ci): update workflow' }
createMatchContext(commit, ['ci'])
// => { commit, scope: ['ci'], subject: 'chore(ci): update workflow', message: 'chore(ci): update workflow' }Given a project named 'lib-versioning' with package '@hyperfrontend/versioning', this generates variations like:
- 'lib-versioning' (full project name)
- 'versioning' (without lib- prefix)
Parameters
| Name | Type | Description |
|---|---|---|
§options | DeriveProjectScopesOptions | Project identification options |
Returns
unknownExamples
Deriving scopes for a library project
deriveProjectScopes({ projectName: 'lib-versioning', packageName: '@hyperfrontend/versioning' })
// Returns: ['lib-versioning', 'versioning']Deriving scopes for an app project
deriveProjectScopes({ projectName: 'app-demo', packageName: 'demo-app' })
// Returns: ['app-demo', 'demo']evaluateInfrastructure( commit: GitCommit, matcher: InfrastructureMatcher, scope?: unknown): boolean
Parameters
| Name | Type | Description |
|---|---|---|
§commit | GitCommit | Git commit to evaluate |
§matcher | InfrastructureMatcher | Matcher function to apply |
§scope? | unknown | Pre-parsed scope (optional) |
Returns
booleanExample
Evaluating a commit against infrastructure matcher
const commit = { hash: 'abc123', subject: 'chore(ci): update workflow', message: '...' }
const ciMatcher = (ctx) => ctx.scope.includes('ci')
evaluateInfrastructure(commit, ciMatcher, ['ci'])
// => trueParameters
| Name | Type | Description |
|---|---|---|
§commits | unknown | Array of classified commits |
Returns
unknownExample
Extracting conventional commits
const classified = classifyCommits(commits, context)
const conventional = extractConventionalCommits(classified.included)
// => [{ type: 'feat', subject: 'add login', ... }]Parameters
| Name | Type | Description |
|---|---|---|
§commits | unknown | Array of classified commits |
Returns
unknownExample
Filtering for included commits
const classified = classifyCommits(commits, context)
const included = filterIncluded(classified.commits)
// => [{ commit, raw, source: 'direct-scope', include: true, ... }]Parameters
| Name | Type | Description |
|---|---|---|
§patterns | unknown | Patterns to search for in commit message (case-insensitive) |
Returns
InfrastructureMatcherExample
Matching message patterns
const matcher = messageMatcher(['[infra]', '[ci skip]'])Parameters
| Name | Type | Description |
|---|---|---|
§matcher | InfrastructureMatcher | Matcher to negate |
Returns
InfrastructureMatcherExample
Negating a matcher
const notRelease = not(scopeMatcher(['release']))A commit is excluded only when every scope entry matches an exclude scope. Commits with no scope entries are never considered excluded here.
Parameters
Returns
booleanExample
Checking if a scope should be excluded
scopeIsExcluded(['release'], ['release', 'deps'])
// => true
scopeIsExcluded(['auth'], ['release', 'deps'])
// => false
scopeIsExcluded([], ['release'])
// => false
scopeIsExcluded(['release', 'auth'], ['release'])
// => false (not every scope is excluded)Parameters
| Name | Type | Description |
|---|---|---|
§scopes | unknown | Scopes to match against (case-insensitive) |
Returns
InfrastructureMatcherExample
Matching against specific scopes
const matcher = scopeMatcher(['ci', 'build', 'tooling'])
matcher({ scope: ['CI'], ... }) // true
matcher({ scope: ['feat'], ... }) // false
matcher({ scope: ['feat', 'ci'], ... }) // true (any element matches)A commit matches the project when any of its scope entries matches a project scope. Empty commit scopes never match.
Parameters
Returns
booleanExample
Matching scope to project
scopeMatchesProject(['versioning'], ['lib-versioning', 'versioning']) // true
scopeMatchesProject(['logging'], ['lib-versioning', 'versioning']) // false
scopeMatchesProject(['versioning', 'questions'], ['lib-questions']) // true
scopeMatchesProject([], ['lib-versioning']) // falseParameters
| Name | Type | Description |
|---|---|---|
§prefixes | unknown | Scope prefixes to match (case-insensitive) |
Returns
InfrastructureMatcherExample
Matching prefixed scopes
const matcher = scopePrefixMatcher(['tool-', 'infra-'])
matcher({ scope: ['tool-package'], ... }) // true
matcher({ scope: ['lib-utils'], ... }) // falseReturns
InfrastructureMatcherExample
Matching with regex pattern
const matcher = scopeRegexMatcher(/^(ci|build|tool)-.+/)For direct commits, the scope is removed (redundant in project changelog). For indirect commits, the scope is preserved (provides context).
Parameters
| Name | Type | Description |
|---|---|---|
§classified | ClassifiedCommit | Commit with classification metadata determining scope display |
Returns
ConventionalCommitExample
Converting classified commit for changelog
const classified = { commit: { type: 'feat', scope: ['auth'], subject: 'add login', ... }, source: 'direct-scope', preserveScope: false, ... }
toChangelogCommit(classified)
// => { type: 'feat', scope: [], subject: 'add login', ... }◈ Interfaces
Properties
readonly dependencyCommitMap?:ReadonlyMap< string, ReadonlySet< string>>Map of dependency name to set of commit hashes touching that dependencyreadonly infrastructureCommitHashes?:ReadonlySet< string>Set of commit hashes that touched infrastructure paths. These commits will be classified as 'indirect-infra'.Properties
Properties
Contains the original commit data plus attribution information that determines inclusion and presentation in changelogs.
Properties
readonly dependencyPath?:unknownDependency chain if indirect (e.g., ['lib-utils'] for lib-app → lib-utils)Properties
Properties
Supports multiple detection methods that can be combined:
- Path-based: Commits touching specific file paths
- Scope-based: Commits with specific conventional scopes
- Custom matcher: User-provided matching logic
Properties
readonly matcher?:InfrastructureMatcherCustom matcher function for complex logic. Combined with paths/scopes using OR logic.readonly paths?:unknownFile/directory paths that indicate infrastructure changes. Used to query git for commits touching these paths.readonly scopes?:unknownConventional commit scopes that indicate infrastructure. Matched against parsed commit scope.Contains information about a commit that helps determine whether it should be classified as infrastructure-related.
Properties
◆ Types
Classification determines whether a commit should appear in a project's changelog and how its scope should be displayed.
type CommitSource = "direct-scope" | "direct-file" | "unscoped-file" | "indirect-dependency" | "indirect-infra" | "unscoped-global" | "excluded"Returns true if the commit should be classified as infrastructure-related. Matchers are composable - combine them using
anyOf, allOf, and not.type InfrastructureMatcher = ( context: InfrastructureMatchContext) => boolean● Variables
Matches: ci, cd, build, pipeline, workflow, actions
These represent repository-level or infrastructure changes that typically don't belong in individual project changelogs.
Combines CI, tooling, and tool-prefix matchers.
Matches: tooling, workspace, monorepo, nx, root
Pure formatter rendering a `CommitDraft` into the final message string: the exact text that would land in `.git/COMMIT_EDITMSG`.
ƒ Functions
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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Draft supplying type, scope, and breaking marker |
§subject | string | Subject text being typed |
Returns
numberExample
Counting a header mid-type
countHeaderLength({ type: 'feat', scope: ['core'] }, 'add login')
// => 'feat(core): add login'.length === 21.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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Commit draft to render |
Returns
string\n separatorsExamples
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'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
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Draft to render (may be partial) |
Returns
stringExamples
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: 'ConventionalCommit to a CommitDraft. Useful for editing a parsed commit inside an authoring session.Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | Parsed conventional commit |
Returns
CommitDraftExample
Promoting a parsed commit to an editable draft
toDraft(parseConventionalCommit('feat: add login'))
// => { type: 'feat', scope: [], subject: 'add login', footers: [], breaking: false }◈ Interfaces
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 breakingDescription?:stringBreaking change description: surfaces as a BREAKING CHANGE: footer when none is already presentConventional commit types, factories, and semver bump derivation.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§description? | string | Optional description of the breaking change |
Returns
BreakingChangeExample
Creating a breaking change from subject
createBreakingFromSubject('remove deprecated API')
// => { isBreaking: true, description: 'remove deprecated API', source: 'subject' }createConventionalCommit( type: string, subject: string, options?: CreateConventionalCommitOptions): ConventionalCommit
Parameters
Returns
ConventionalCommitExample
Creating conventional commits
createConventionalCommit('feat', 'add user authentication')
// => { type: 'feat', subject: 'add user authentication', scope: [], footers: [], breaking: false, raw: 'feat: add user authentication' }
createConventionalCommit('fix', 'resolve memory leak', { scope: ['core'], breaking: true })
// => { type: 'fix', subject: 'resolve memory leak', scope: ['core'], breaking: true, ... }
createConventionalCommit('feat', 'add x', { scope: ['versioning', 'questions'] })
// => { ..., scope: ['versioning', 'questions'], raw: 'feat(versioning,questions): add x' }Returns
BreakingChangeExample
Creating a non-breaking change
createNonBreaking()
// => { isBreaking: false, source: 'none' }Parameters
Returns
"minor" | "patch" | "none" | "major"Example
Determining semver bump level
getSemverBump('feat', false)
// => 'minor'
getSemverBump('fix', true)
// => 'major'
getSemverBump('docs', false)
// => 'none'Parameters
| Name | Type | Description |
|---|---|---|
§type | string | The commit type to check |
Returns
booleanExample
Checking for release-triggering types
isReleaseType('feat')
// => true
isReleaseType('chore')
// => falseParameters
| Name | Type | Description |
|---|---|---|
§type | string | The commit type to check |
Returns
unknownExample
Checking for standard commit types
isStandardType('feat')
// => true
isStandardType('custom')
// => false◈ Interfaces
Properties
Properties
◆ Types
type CommitType = "feat" | "fix" | "docs" | "style" | "refactor" | "perf" | "test" | "build" | "ci" | "chore" | "revert" | string● Variables
Conventional commit message parsing for headers, bodies, and footers.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The commit message to check |
Returns
booleanExample
Checking if a message is a conventional commit
isConventionalCommit('feat(auth): add OAuth login')
// => true
isConventionalCommit('WIP: still working on this')
// => false
isConventionalCommit('fix: resolve bug')
// => trueThe body starts after the first blank line and continues until we encounter a footer (key: value or key #value pattern) or end of message.
Parameters
Returns
ParsedBodyExample
Parsing commit body
const lines = [
'feat: add login',
'',
'Implements OAuth flow.',
'',
'Refs: #123'
]
const result = parseBody(lines, 1)
// => { body: 'Implements OAuth flow.', endIndex: 4 }Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The complete commit message |
Returns
ConventionalCommitExample
Parsing a complete conventional commit message
parseConventionalCommit('feat(auth): add login\n\nImplements OAuth.\n\nRefs: #123')
// => {
// type: 'feat',
// scope: ['auth'],
// subject: 'add login',
// body: 'Implements OAuth.',
// footers: [{ key: 'Refs', value: '#123', separator: ':' }],
// breaking: false,
// raw: '...'
// }Supports comma-separated multi-scope headers such as
feat(a,b): x which produce a multi-element scope array. Single-scope headers produce a one-element array, and scopeless headers produce an empty array.Parameters
| Name | Type | Description |
|---|---|---|
§line | string | The first line of the commit message |
Returns
ParsedHeaderExample
Parsing conventional commit headers
parseHeader('feat(auth): add OAuth login')
// => { type: 'feat', scope: ['auth'], subject: 'add OAuth login', breaking: false }
parseHeader('fix!: critical security patch')
// => { type: 'fix', scope: [], subject: 'critical security patch', breaking: true }
parseHeader('feat(versioning,questions): add searchable select')
// => { type: 'feat', scope: ['versioning', 'questions'], subject: 'add searchable select', breaking: false }◈ Interfaces
Properties
Pure rule engine judging parsed conventional commits against a configurable ruleset, with commitlint's semantics.
ƒ Functions
Rules whose config is
'off' (or absent from the ruleset) are skipped. Unknown rule names in the ruleset are ignored: consumers extend the engine by calling validateCommitWithRules() and supplying additional rules.Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | Parsed conventional commit to validate |
§ruleset | Ruleset | Configuration map from rule name to [level, options?] |
Returns
ValidationResultExample
Validating a commit against a preset ruleset
validateCommit(parseConventionalCommit('feat: add login'), conventionalPreset)
// => { valid: true, warnings: [], errors: [] }cl bin and anywhere else validation must start from a string rather than a parsed commit.Parameters
Returns
ValidationResultExample
Validating a raw commit message
validateCommitMessage('feat: add login', conventionalPreset)
// => { valid: true, warnings: [], errors: [] }
validateCommitMessage('feat: added login', conventionalPreset).warnings
// => [{ level: 'warn', ruleName: 'imperative-mood', message: ... }]validateCommitWithRules( commit: ConventionalCommit, ruleset: Ruleset, rules: Readonly< Record< string, Rule< never>>>): ValidationResult
validateCommit that accepts a caller-supplied rule registry. Useful for consumers that want to add project-specific rules without forking the built-in set.Parameters
Returns
ValidationResultExample
Extending the built-in rules with a custom rule
const customRules = { ...BUILT_IN_RULES, 'no-wip': noWipRule }
validateCommitWithRules(commit, { 'no-wip': ['error'] }, customRules)◈ Interfaces
Properties
Properties
ConventionalCommit and return zero or more violation messages. The engine applies severity from the ruleset config and aggregates RuleMessages across all rules.Properties
Properties
Properties
Properties
Properties
readonly cases:unknownOne or more accepted case forms. A subject passing any accepted form is valid.Properties
Properties
◆ Types
A bare
[level] entry disables the rule when level is 'off', or enables it with no options otherwise. The [level, options] form passes rule-specific options through to the rule's check() implementation.type RuleConfig = unknown | unknowntype RuleLevel = "off" | "warn" | "error"type RuleResult = unknown'off' by the engine.type Ruleset = Readonly< Record< string, RuleConfig>>type SubjectCase = "sentence-case" | "lower-case" | "upper-case" | "kebab-case" | "snake-case" | "start-case"● Variables
@commitlint/config-conventional's baseline. | Rule | Level | Options | | ------------------ | ----- | ------------------------ | | type-enum | error |
CONVENTIONAL_TYPES | | subject-empty | error | — | | scope-enum | off | — | | subject-case | off | — | | header-max-length | warn | { maxLength: 72 } | | imperative-mood | warn | — |type(scope)!: subject) exceeds the configured maximum length.Version flow orchestration with step management, presets, and execution for release workflows.
ƒ Functions
This step resolves repository configuration for compare URL generation. It supports multiple resolution modes:
undefinedor'disabled': No-op, backward compatible default'inferred': Auto-detect from package.json or git remoteRepositoryConfig: Direct repository configuration providedRepositoryResolution: Fine-grained control with mode and options
- repositoryConfig: Resolved repository configuration (if successful)
Returns
FlowStepExample
Configuring repository resolution modes
// Auto-detect repository
const flow = createFlow({
repository: 'inferred'
})
// Explicit repository
const flow = createFlow({
repository: {
platform: 'github',
baseUrl: 'https://github.com/owner/repo'
}
})Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to extend |
§step | FlowStep | The step to add |
Returns
VersionFlowExample
Adding a custom step to a flow
import { addStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const extended = addStep(flow, createNoopStep('custom', 'Custom Step'))
console.log(extended.steps.length)
// => original steps + 1This step:
- Uses publishedCommit from npm registry (set by fetch-registry step)
- Verifies the commit is reachable from current HEAD
- Gets all commits since that commit (or recent commits if first release/fallback)
- Parses each commit using conventional commit format
- Classifies commits based on scope filtering strategy
- Filters to only release-worthy commits that belong to this project
- effectiveBaseCommit: The verified base commit (null if fallback was used)
- commits: Array of parsed conventional commits (for backward compatibility)
- classificationResult: Full classification result with source attribution
Returns
FlowStepExample
Analyzing commits since last release
import { createAnalyzeCommitsStep, executeStep } from '@hyperfrontend/versioning'
const step = createAnalyzeCommitsStep()
const result = await executeStep(step, context)
// Access analyzed commits
console.log(result.stateUpdates?.commits)
// => [{ type: 'feat', subject: 'add feature' }, ...]This is a variant that skips commit/tag creation, intended to be used when releasing multiple packages in sequence with a single commit at the end.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Releasing multiple packages in batch
import { createBatchReleaseFlow, executeFlow } from '@hyperfrontend/versioning'
// Release multiple packages without individual commits
for (const pkg of ['lib-a', 'lib-b', 'lib-c']) {
await executeFlow(createBatchReleaseFlow(), pkg, '/workspace')
}
// Then create a single combined commitThis step:
- Analyzes commit types and breaking changes
- Determines the appropriate bump level
- Calculates the next version
- bumpType: 'major' | 'minor' | 'patch' | 'none'
- nextVersion: Calculated next version string
Returns
FlowStepExample
Running the calculate-bump step
import { createCalculateBumpStep, executeStep } from '@hyperfrontend/versioning'
const step = createCalculateBumpStep()
const result = await executeStep(step, context)
// Get the calculated bump
console.log(result.stateUpdates?.bumpType)
// => 'minor'
console.log(result.stateUpdates?.nextVersion)
// => '1.2.0'This step cascades version updates to packages that depend on the updated package.
Returns
FlowStepExample
Updating dependent packages in a monorepo
import { createCascadeDependenciesStep, executeStep } from '@hyperfrontend/versioning'
const step = createCascadeDependenciesStep()
const result = await executeStep(step, contextWithTrackDeps)
// Dependent packages are updated
console.log(result.message)Only generates changelog, no version bumps or git operations.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a changelog-only flow
import { createChangelogOnlyFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createChangelogOnlyFlow()
const result = await executeFlow(flow, 'my-lib', '/workspace')
// Only changelog is updated, no version bump
console.log(result.state.changelogEntry)In independent versioning, when a dependency is bumped, dependents may also need version bumps.
Returns
FlowStepExample
Checking if dependent packages need bumps
import { createCheckDependentBumpsStep, executeStep } from '@hyperfrontend/versioning'
const step = createCheckDependentBumpsStep()
const result = await executeStep(step, contextWithTrackDeps)
// Result indicates if any dependents need bumps
console.log(result.message)This step prevents redundant releases by checking if the calculated version is already published.
Returns
FlowStepExample
Preventing duplicate releases
import { createCheckIdempotencyStep, executeStep } from '@hyperfrontend/versioning'
const step = createCheckIdempotencyStep()
const result = await executeStep(step, context)
// If version already published, step skips with bumpType: 'none'
if (result.status === 'skipped') {
console.log('Version already published')
}Returns
FlowStepExample
Generating a combined changelog for all packages
import { createCombinedChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createCombinedChangelogStep()
const result = await executeStep(step, context)
// Single changelog for all packages
console.log(result.message)This flow follows the standard conventional commits workflow:
- Fetch published version from registry
- Resolve repository configuration (for compare URLs)
- Analyze commits since last release
- Calculate version bump based on commit types
- Check if version already published (idempotency)
- Generate changelog entry (with compare URL if repository resolved)
- Update package.json version
- Write changelog to file
- Create git commit (optional)
- Create git tag (optional, typically after publish)
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a conventional versioning flow
import { createConventionalFlow, executeFlow } from '@hyperfrontend/versioning'
// Use defaults
const flow = createConventionalFlow()
// With overrides
const customFlow = createConventionalFlow({
skipTag: false,
releaseTypes: ['feat', 'fix'],
})
const result = await executeFlow(flow, 'lib-utils', '/workspace')Convenience function that creates a flow with dryRun enabled.
Parameters
| Name | Type | Description |
|---|---|---|
§preset | FlowPreset | The preset to use(default: 'conventional') |
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a dry-run flow for preview
import { createDryRunFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createDryRunFlow('conventional')
const result = await executeFlow(flow, 'my-lib', '/workspace')
// Preview changes without modifying files
console.log('Would release:', result.state.nextVersion)Parameters
Returns
FlowStepResultExample
Handling step execution errors
import { createFailedResult } from '@hyperfrontend/versioning'
// In a step handler:
try {
await doOperation()
} catch (err) {
return createFailedResult(err as Error, 'Operation failed')
}This step:
- Queries the registry for the latest published version
- Reads the current version from package.json
- Determines if this is a first release
- publishedVersion: Latest version on registry (null if not published)
- currentVersion: Version from local package.json
- isFirstRelease: True if never published
Returns
FlowStepExample
Fetching published version from registry
import { createFetchRegistryStep, executeStep } from '@hyperfrontend/versioning'
const step = createFetchRegistryStep()
const result = await executeStep(step, context)
// Check if first release
if (result.stateUpdates?.isFirstRelease) {
console.log('First release detected')
}Similar to synced, but uses a fixed version scheme where the version is explicitly provided rather than calculated from commits.
Parameters
| Name | Type | Description |
|---|---|---|
§version | string | The fixed version to use |
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a fixed version release flow
import { createFixedVersionFlow, executeFlow } from '@hyperfrontend/versioning'
// Release with explicit version, ignoring commit analysis
const flow = createFixedVersionFlow('2.0.0')
const result = await executeFlow(flow, 'workspace', '/workspace')
console.log(result.state.nextVersion)
// => '2.0.0'createFlow( id: string, name: string, steps: unknown, options: CreateFlowOptions): VersionFlow
Parameters
| Name | Type | Description |
|---|---|---|
§id | string | Flow identifier |
§name | string | Human-readable flow name |
§steps | unknown | Ordered steps to execute |
§options | CreateFlowOptions | Optional flow configuration(default: {}) |
Returns
VersionFlowExample
Creating a custom flow
const myFlow = createFlow(
'custom',
'Custom Release Flow',
[fetchStep, analyzeStep, bumpStep],
{ description: 'My custom versioning workflow' }
)This step:
- Groups commits by type/section
- Creates changelog items from commits
- Assembles a complete changelog entry
- changelogEntry: The generated ChangelogEntry
Returns
FlowStepExample
Generating a changelog entry from commits
import { createGenerateChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createGenerateChangelogStep()
const result = await executeStep(step, context)
// Access the generated changelog entry
const entry = result.stateUpdates?.changelogEntry
console.log(entry?.version, entry?.sections.length)
// => '1.2.0', 3This step:
- Stages modified files
- Creates a commit with the configured message
- commitHash: Hash of the created commit
Returns
FlowStepExample
Creating and staging a release commit
import { createGitCommitStep, executeStep } from '@hyperfrontend/versioning'
const step = createGitCommitStep()
const result = await executeStep(step, context)
// Get the created commit hash
console.log(result.stateUpdates?.commitHash)
// => 'a1b2c3d4e5f6...'This flow is designed for monorepos where each package is versioned independently:
- Fetch published version from registry
- Analyze commits since last release
- Calculate version bump based on commit types
- Check for dependent package bumps (cascade)
- Check if version already published (idempotency)
- Generate changelog entry
- Update package.json version
- Cascade dependency updates
- Write changelog to file
- Create git commit
- Create git tag
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating an independent versioning flow
import { createIndependentFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createIndependentFlow()
const result = await executeFlow(flow, 'lib-utils', '/workspace')
// Inspect the version that was released
console.log(result.state.nextVersion)Skips changelog and tag creation.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a minimal release flow
import { createMinimalFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createMinimalFlow()
const result = await executeFlow(flow, 'my-lib', '/workspace')
// Quick release without changelog or tags
console.log('Released:', result.state.nextVersion)Parameters
Returns
FlowStepExample
Creating a placeholder step
import { createNoopStep, executeStep } from '@hyperfrontend/versioning'
const step = createNoopStep('placeholder', 'Placeholder Step')
const result = await executeStep(step, context)
console.log(result.status)
// => 'success'Returns
FlowStepExample
Pushing the tag to remote
import { createPushTagStep, executeStep } from '@hyperfrontend/versioning'
const step = createPushTagStep()
const result = await executeStep(step, context)
// Tag is pushed to remote
console.log(result.message)
// => 'Pushed tag: my-lib@1.2.0'Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Explanation for why the step was skipped |
Returns
FlowStepResultExample
Skipping a step conditionally
import { createSkippedResult } from '@hyperfrontend/versioning'
// In a step handler:
if (!config.enabled) {
return createSkippedResult('Feature disabled in config')
}createStep( id: string, name: string, execute: StepExecutor, options: CreateStepOptions): FlowStep
Parameters
| Name | Type | Description |
|---|---|---|
§id | string | Unique step identifier |
§name | string | Human-readable step name |
§execute | StepExecutor | Step executor function |
§options | CreateStepOptions | Optional step configuration(default: {}) |
Returns
FlowStepExample
Creating a custom fetch step
const fetchStep = createStep(
'fetch-registry',
'Fetch Registry Version',
async (ctx) => {
const version = await ctx.registry.getLatestVersion(ctx.packageName)
return {
status: 'success',
stateUpdates: { publishedVersion: version },
message: `Found published version: ${version}`
}
}
)Parameters
Returns
FlowStepResultExample
Returning a success result with state updates
import { createSuccessResult } from '@hyperfrontend/versioning'
// In a step handler:
return createSuccessResult('Updated 3 files', {
modifiedFiles: ['/path/a.ts', '/path/b.ts', '/path/c.ts']
})Returns
FlowStepExample
Syncing all packages to the same version
import { createSyncAllPackagesStep, executeStep } from '@hyperfrontend/versioning'
const step = createSyncAllPackagesStep()
const result = await executeStep(step, context)
// All packages updated to same version
console.log(result.stateUpdates?.modifiedFiles)This flow maintains the same version across all packages in a monorepo. When any package changes, all packages get the same new version.
Flow steps:
- Fetch published version from registry
- Analyze commits across all packages
- Calculate version bump (highest needed)
- Check if version already published
- Sync all package versions
- Generate combined changelog
- Write changelog to root
- Create git commit
- Create single git tag
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a synced versioning flow
import { createSyncedFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createSyncedFlow()
const result = await executeFlow(flow, 'workspace', '/workspace')
// All packages now share the same version
console.log(`Released v${result.state.nextVersion}`)This step:
- Creates an annotated git tag
- Uses the configured tag format
- tagName: Name of the created tag
Returns
FlowStepExample
Creating an annotated version tag
import { createTagStep, executeStep } from '@hyperfrontend/versioning'
const step = createTagStep()
const result = await executeStep(step, context)
// Get the created tag name
console.log(result.stateUpdates?.tagName)
// => 'my-lib@1.2.0'This step:
- Updates the version field in package.json
- Tracks the modified files
- modifiedFiles: Adds package.json to list
Returns
FlowStepExample
Updating the package.json version field
import { createUpdatePackageStep, executeStep } from '@hyperfrontend/versioning'
const step = createUpdatePackageStep()
const result = await executeStep(step, context)
// package.json version field is updated
console.log(result.stateUpdates?.modifiedFiles)
// => ['/workspace/libs/my-lib/package.json']This is the main factory function for creating flows. Use this when you want to create a flow based on a standard preset with optional customization.
Parameters
| Name | Type | Description |
|---|---|---|
§preset | FlowPreset | The preset to use (default: 'conventional')(default: 'conventional') |
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating flows from presets
import { createVersionFlow, executeFlow } from '@hyperfrontend/versioning'
// Create a conventional flow
const flow = createVersionFlow('conventional')
// Create an independent flow with custom config
const customFlow = createVersionFlow('independent', {
skipTag: true,
trackDeps: true,
})
const result = await executeFlow(flow, 'lib-utils', '/workspace')This step writes the generated changelog entry to CHANGELOG.md.
Returns
FlowStepExample
Writing the changelog entry to CHANGELOG.md
import { createWriteChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createWriteChangelogStep()
const result = await executeStep(step, context)
// CHANGELOG.md is updated with the new entry
console.log(result.message)
// => 'Updated CHANGELOG.md with version 1.2.0'dryRun( flow: VersionFlow, projectName: string, workspaceRoot: string, options: Omit< ExecuteOptions, "dryRun">): Promise< FlowResult>
Convenience wrapper that sets dryRun: true.
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The version flow to execute |
§projectName | string | Name of the project to version |
§workspaceRoot | string | Absolute path to workspace root |
§options | Omit< | Execution options (dryRun forced to true)(default: {}) |
Returns
Promise< FlowResult>Example
Previewing version changes without modifying files
import { dryRun, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const result = await dryRun(flow, 'my-lib', '/workspace')
// Preview what would happen
console.log('Would bump to:', result.state.nextVersion)
// No files modified, no git operations performedexecuteFlow( flow: VersionFlow, projectName: string, workspaceRoot: string, options: ExecuteOptions): Promise< FlowResult>
This is the main entry point for running a versioning workflow. Steps are executed in order, with state accumulated between steps.
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The version flow to execute |
§projectName | string | Name of the project to version (e.g., 'lib-versioning') |
§workspaceRoot | string | Absolute path to workspace root |
§options | ExecuteOptions | Execution options(default: {}) |
Returns
Promise< FlowResult>Example
Executing a conventional version flow
import { createConventionalFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow({ dryRun: true })
const result = await executeFlow(flow, 'lib-utils', '/path/to/workspace')
console.log(result.summary)
// "Flow success in 234ms: 8 completed, 0 skipped, 0 failed. Version: 1.2.3 → 1.3.0"Returns
unknownExample
Listing available presets
import { getAvailablePresets } from '@hyperfrontend/versioning'
const presets = getAvailablePresets()
// => ['conventional', 'independent', 'synced']Parameters
| Name | Type | Description |
|---|---|---|
§preset | FlowPreset | The preset name |
Returns
stringExample
Getting a preset description
import { getPresetDescription } from '@hyperfrontend/versioning'
console.log(getPresetDescription('independent'))
// => 'Version packages independently with dependency tracking'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to search |
§stepId | string | The step ID to find |
Returns
FlowStepExample
Getting a step by ID
import { getStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const step = getStep(flow, 'analyze-commits')
console.log(step?.name)
// => 'Analyze Commits'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to check |
§stepId | string | The step ID to look for |
Returns
booleanExample
Checking if a flow has a step
import { hasStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
console.log(hasStep(flow, 'create-commit'))
// => true
console.log(hasStep(flow, 'custom-step'))
// => falseParameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§index | number | Position to insert at (0-based) |
Returns
VersionFlowExample
Inserting a step at a specific position
import { insertStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStep(flow, createNoopStep('early', 'Early Step'), 0)
console.log(modified.steps[0].id)
// => 'early'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§afterStepId | string | ID of the step to insert after |
Returns
VersionFlowExample
Inserting a step after another step
import { insertStepAfter, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStepAfter(flow, createNoopStep('validate', 'Validate'), 'analyze-commits'))
// 'validate' step now follows 'analyze-commits'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§beforeStepId | string | ID of the step to insert before |
Returns
VersionFlowExample
Inserting a step before another step
import { insertStepBefore, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStepBefore(flow, createNoopStep('prep', 'Prepare'), 'create-commit'))
// 'prep' step now runs before 'create-commit'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§stepId | string | The ID of the step to remove |
Returns
VersionFlowExample
Removing a step from a flow
import { removeStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const minimal = removeStep(flow, 'generate-changelog')
// Flow no longer has changelog step
console.log(minimal.steps.find(s => s.id === 'generate-changelog'))
// => undefinedParameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§stepId | string | ID of the step to replace |
§newStep | FlowStep | The replacement step |
Returns
VersionFlowExample
Replacing a step with a custom implementation
import { replaceStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = replaceStep(flow, 'create-tag', createNoopStep('create-tag', 'Custom Tag'))
// 'create-tag' now uses custom implementationChecks for:
- Duplicate step IDs
- Invalid dependency references
- Circular dependencies
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to validate |
Returns
unknownExample
Validating a flow before execution
import { validateFlow, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const errors = validateFlow(flow)
if (errors.length > 0) {
console.error('Invalid flow:', errors)
}
// => []Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to configure |
§config | Partial< | Configuration updates to merge |
Returns
VersionFlowExample
Updating flow configuration
import { withConfig, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const dryRunFlow = withConfig(flow, { dryRun: true })
// Flow now runs in dry-run mode◈ Interfaces
Properties
Properties
requiresDiskFlush?:booleanWhether pending file writes must reach disk before this step runs. Steps that shell out to another tool, such as staging files for a commit, observe the real filesystem rather than the in-memory tree, so they see nothing until the tree is flushed.
Properties
projectRoot?:stringProject root path (relative to workspace root, e.g., 'libs/utils/immutable-api'). If provided, this is used directly instead of deriving from project name. This is the recommended approach when calling from the Nx executor.rollbackOnFailure?:booleanWhether to rollback all pending changes on step failure. When true (default), pending VFS changes are discarded when a step fails and
continueOnError is false. This ensures no partial state remains.Properties
readonly backupChangelog?:booleanCreate a backup of the existing changelog before modification. When enabled:
- Existing
CHANGELOG.mdis renamed toCHANGELOG.backup.md - New changelog is written
- Backup is deleted on success
readonly commitTypeToSection?:Partial< Record< string, ChangelogSectionType | null>>Custom mapping from commit type to changelog section. Merged with defaults; use null to exclude a type from changelog.readonly maxCommitFallback?:numberMaximum commits to analyze when no base commit is available. Used for first releases, history rewrites, and path-filtered queries. Set higher if you expect >500 commits between releases.
readonly releaseAs?:"minor" | "patch" | "major"Force a specific bump type. The bump no longer comes from the commits, but they are still analyzed, and every commit type is admitted so the changelog lists what changed since the last release rather than only what could have caused a bump.readonly repository?:RepositoryConfig | "inferred" | "disabled" | RepositoryResolutionRepository resolution configuration for compare URL generation. Controls how repository information is resolved:
'disabled': No compare URLs generated (default, backward compatible)'inferred': Auto-detect from package.json or git remoteRepositoryResolution: Fine-grained control with explicit mode and optionsRepositoryConfig: Direct repository configuration
readonly scopeFiltering?:ScopeFilteringConfigCommit scope filtering configuration. Controls how commits are attributed to projects in changelogs. By default, hybrid filtering ensures commits are included based on conventional commit scope OR file changes within the project.
Note:
state is mutable within context to allow accumulation, but individual FlowState objects are immutable.Properties
Properties
readonly diffs?:unknownDetailed diffs for pending changes. Populated when showDiff: true is passed to executeFlow. Provides unified diff format for programmatic access.Properties
readonly classificationResult?:ClassificationResultClassification result with source attribution (when scope filtering enabled)readonly effectiveBaseCommit?:stringThe verified base commit used for commit scoping and changelog generation. This will be publishedCommit if that commit is reachable from HEAD, or null if a fallback was used (e.g., history was rewritten). When null, compare URLs are omitted from the changelog.
readonly isPendingPublication?:booleanWhether this is a pending publication scenario. True when currentVersion > publishedVersion, meaning the version was already bumped but not yet published to the registry. When true: - nextVersion is calculated from publishedVersion (not currentVersion)
- Changelog entries > publishedVersion should be cleaned up
- The correct changelog entry replaces any stacked ones
Steps are pure functions that:
- Read from context (state, config, services)
- Perform work (possibly with side effects via services)
- Return state updates
Properties
readonly requiresDiskFlush?:booleanWhether pending file writes must reach disk before this step runs. Steps that shell out to another tool, such as staging files for a commit, observe the real filesystem rather than the in-memory tree, so they see nothing until the tree is flushed.
Properties
Properties
Properties
timedAsync:TimedAsyncFnWraps a promise-returning call with timing. Dumps stack trace on error to debug log.Flows are immutable configurations that define:
- What steps to execute
- In what order
- With what configuration
Properties
◆ Types
type FlowPreset = "conventional" | "independent" | "synced"type FlowStatus = "success" | "partial" | "failed" | "skipped"type StepCondition = ( context: FlowContext) => booleantype StepExecutor = ( context: FlowContext) => Promise< FlowStepResult>● Variables
Flow execution engine for running version flows with dry-run and validation support.
ƒ Functions
dryRun( flow: VersionFlow, projectName: string, workspaceRoot: string, options: Omit< ExecuteOptions, "dryRun">): Promise< FlowResult>
Convenience wrapper that sets dryRun: true.
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The version flow to execute |
§projectName | string | Name of the project to version |
§workspaceRoot | string | Absolute path to workspace root |
§options | Omit< | Execution options (dryRun forced to true)(default: {}) |
Returns
Promise< FlowResult>Example
Previewing version changes without modifying files
import { dryRun, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const result = await dryRun(flow, 'my-lib', '/workspace')
// Preview what would happen
console.log('Would bump to:', result.state.nextVersion)
// No files modified, no git operations performedexecuteFlow( flow: VersionFlow, projectName: string, workspaceRoot: string, options: ExecuteOptions): Promise< FlowResult>
This is the main entry point for running a versioning workflow. Steps are executed in order, with state accumulated between steps.
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The version flow to execute |
§projectName | string | Name of the project to version (e.g., 'lib-versioning') |
§workspaceRoot | string | Absolute path to workspace root |
§options | ExecuteOptions | Execution options(default: {}) |
Returns
Promise< FlowResult>Example
Executing a conventional version flow
import { createConventionalFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow({ dryRun: true })
const result = await executeFlow(flow, 'lib-utils', '/path/to/workspace')
console.log(result.summary)
// "Flow success in 234ms: 8 completed, 0 skipped, 0 failed. Version: 1.2.3 → 1.3.0"Checks for:
- Duplicate step IDs
- Invalid dependency references
- Circular dependencies
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to validate |
Returns
unknownExample
Validating a flow before execution
import { validateFlow, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const errors = validateFlow(flow)
if (errors.length > 0) {
console.error('Invalid flow:', errors)
}
// => []◈ Interfaces
Properties
projectRoot?:stringProject root path (relative to workspace root, e.g., 'libs/utils/immutable-api'). If provided, this is used directly instead of deriving from project name. This is the recommended approach when calling from the Nx executor.rollbackOnFailure?:booleanWhether to rollback all pending changes on step failure. When true (default), pending VFS changes are discarded when a step fails and
continueOnError is false. This ensures no partial state remains.Flow and step type definitions with factory functions and configuration builders.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to extend |
§step | FlowStep | The step to add |
Returns
VersionFlowExample
Adding a custom step to a flow
import { addStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const extended = addStep(flow, createNoopStep('custom', 'Custom Step'))
console.log(extended.steps.length)
// => original steps + 1Parameters
Returns
FlowStepResultExample
Handling step execution errors
import { createFailedResult } from '@hyperfrontend/versioning'
// In a step handler:
try {
await doOperation()
} catch (err) {
return createFailedResult(err as Error, 'Operation failed')
}createFlow( id: string, name: string, steps: unknown, options: CreateFlowOptions): VersionFlow
Parameters
| Name | Type | Description |
|---|---|---|
§id | string | Flow identifier |
§name | string | Human-readable flow name |
§steps | unknown | Ordered steps to execute |
§options | CreateFlowOptions | Optional flow configuration(default: {}) |
Returns
VersionFlowExample
Creating a custom flow
const myFlow = createFlow(
'custom',
'Custom Release Flow',
[fetchStep, analyzeStep, bumpStep],
{ description: 'My custom versioning workflow' }
)Parameters
Returns
FlowStepExample
Creating a placeholder step
import { createNoopStep, executeStep } from '@hyperfrontend/versioning'
const step = createNoopStep('placeholder', 'Placeholder Step')
const result = await executeStep(step, context)
console.log(result.status)
// => 'success'Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Explanation for why the step was skipped |
Returns
FlowStepResultExample
Skipping a step conditionally
import { createSkippedResult } from '@hyperfrontend/versioning'
// In a step handler:
if (!config.enabled) {
return createSkippedResult('Feature disabled in config')
}createStep( id: string, name: string, execute: StepExecutor, options: CreateStepOptions): FlowStep
Parameters
| Name | Type | Description |
|---|---|---|
§id | string | Unique step identifier |
§name | string | Human-readable step name |
§execute | StepExecutor | Step executor function |
§options | CreateStepOptions | Optional step configuration(default: {}) |
Returns
FlowStepExample
Creating a custom fetch step
const fetchStep = createStep(
'fetch-registry',
'Fetch Registry Version',
async (ctx) => {
const version = await ctx.registry.getLatestVersion(ctx.packageName)
return {
status: 'success',
stateUpdates: { publishedVersion: version },
message: `Found published version: ${version}`
}
}
)Parameters
Returns
FlowStepResultExample
Returning a success result with state updates
import { createSuccessResult } from '@hyperfrontend/versioning'
// In a step handler:
return createSuccessResult('Updated 3 files', {
modifiedFiles: ['/path/a.ts', '/path/b.ts', '/path/c.ts']
})Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to search |
§stepId | string | The step ID to find |
Returns
FlowStepExample
Getting a step by ID
import { getStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const step = getStep(flow, 'analyze-commits')
console.log(step?.name)
// => 'Analyze Commits'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to check |
§stepId | string | The step ID to look for |
Returns
booleanExample
Checking if a flow has a step
import { hasStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
console.log(hasStep(flow, 'create-commit'))
// => true
console.log(hasStep(flow, 'custom-step'))
// => falseParameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§index | number | Position to insert at (0-based) |
Returns
VersionFlowExample
Inserting a step at a specific position
import { insertStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStep(flow, createNoopStep('early', 'Early Step'), 0)
console.log(modified.steps[0].id)
// => 'early'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§afterStepId | string | ID of the step to insert after |
Returns
VersionFlowExample
Inserting a step after another step
import { insertStepAfter, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStepAfter(flow, createNoopStep('validate', 'Validate'), 'analyze-commits'))
// 'validate' step now follows 'analyze-commits'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§step | FlowStep | The step to insert |
§beforeStepId | string | ID of the step to insert before |
Returns
VersionFlowExample
Inserting a step before another step
import { insertStepBefore, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = insertStepBefore(flow, createNoopStep('prep', 'Prepare'), 'create-commit'))
// 'prep' step now runs before 'create-commit'Parameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§stepId | string | The ID of the step to remove |
Returns
VersionFlowExample
Removing a step from a flow
import { removeStep, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const minimal = removeStep(flow, 'generate-changelog')
// Flow no longer has changelog step
console.log(minimal.steps.find(s => s.id === 'generate-changelog'))
// => undefinedParameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to modify |
§stepId | string | ID of the step to replace |
§newStep | FlowStep | The replacement step |
Returns
VersionFlowExample
Replacing a step with a custom implementation
import { replaceStep, createConventionalFlow, createNoopStep } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const modified = replaceStep(flow, 'create-tag', createNoopStep('create-tag', 'Custom Tag'))
// 'create-tag' now uses custom implementationParameters
| Name | Type | Description |
|---|---|---|
§flow | VersionFlow | The flow to configure |
§config | Partial< | Configuration updates to merge |
Returns
VersionFlowExample
Updating flow configuration
import { withConfig, createConventionalFlow } from '@hyperfrontend/versioning'
const flow = createConventionalFlow()
const dryRunFlow = withConfig(flow, { dryRun: true })
// Flow now runs in dry-run mode◈ Interfaces
Properties
Properties
requiresDiskFlush?:booleanWhether pending file writes must reach disk before this step runs. Steps that shell out to another tool, such as staging files for a commit, observe the real filesystem rather than the in-memory tree, so they see nothing until the tree is flushed.
Properties
readonly backupChangelog?:booleanCreate a backup of the existing changelog before modification. When enabled:
- Existing
CHANGELOG.mdis renamed toCHANGELOG.backup.md - New changelog is written
- Backup is deleted on success
readonly commitTypeToSection?:Partial< Record< string, ChangelogSectionType | null>>Custom mapping from commit type to changelog section. Merged with defaults; use null to exclude a type from changelog.readonly maxCommitFallback?:numberMaximum commits to analyze when no base commit is available. Used for first releases, history rewrites, and path-filtered queries. Set higher if you expect >500 commits between releases.
readonly releaseAs?:"minor" | "patch" | "major"Force a specific bump type. The bump no longer comes from the commits, but they are still analyzed, and every commit type is admitted so the changelog lists what changed since the last release rather than only what could have caused a bump.readonly repository?:RepositoryConfig | "inferred" | "disabled" | RepositoryResolutionRepository resolution configuration for compare URL generation. Controls how repository information is resolved:
'disabled': No compare URLs generated (default, backward compatible)'inferred': Auto-detect from package.json or git remoteRepositoryResolution: Fine-grained control with explicit mode and optionsRepositoryConfig: Direct repository configuration
readonly scopeFiltering?:ScopeFilteringConfigCommit scope filtering configuration. Controls how commits are attributed to projects in changelogs. By default, hybrid filtering ensures commits are included based on conventional commit scope OR file changes within the project.
Note:
state is mutable within context to allow accumulation, but individual FlowState objects are immutable.Properties
Properties
readonly diffs?:unknownDetailed diffs for pending changes. Populated when showDiff: true is passed to executeFlow. Provides unified diff format for programmatic access.Properties
readonly classificationResult?:ClassificationResultClassification result with source attribution (when scope filtering enabled)readonly effectiveBaseCommit?:stringThe verified base commit used for commit scoping and changelog generation. This will be publishedCommit if that commit is reachable from HEAD, or null if a fallback was used (e.g., history was rewritten). When null, compare URLs are omitted from the changelog.
readonly isPendingPublication?:booleanWhether this is a pending publication scenario. True when currentVersion > publishedVersion, meaning the version was already bumped but not yet published to the registry. When true: - nextVersion is calculated from publishedVersion (not currentVersion)
- Changelog entries > publishedVersion should be cleaned up
- The correct changelog entry replaces any stacked ones
Steps are pure functions that:
- Read from context (state, config, services)
- Perform work (possibly with side effects via services)
- Return state updates
Properties
readonly requiresDiskFlush?:booleanWhether pending file writes must reach disk before this step runs. Steps that shell out to another tool, such as staging files for a commit, observe the real filesystem rather than the in-memory tree, so they see nothing until the tree is flushed.
Properties
Properties
Properties
timedAsync:TimedAsyncFnWraps a promise-returning call with timing. Dumps stack trace on error to debug log.Flows are immutable configurations that define:
- What steps to execute
- In what order
- With what configuration
Properties
◆ Types
type FlowStatus = "success" | "partial" | "failed" | "skipped"type StepCondition = ( context: FlowContext) => booleantype StepExecutor = ( context: FlowContext) => Promise< FlowStepResult>● Variables
Pre-configured release flows for conventional, independent, and synced release strategies.
ƒ Functions
This is a variant that skips commit/tag creation, intended to be used when releasing multiple packages in sequence with a single commit at the end.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Releasing multiple packages in batch
import { createBatchReleaseFlow, executeFlow } from '@hyperfrontend/versioning'
// Release multiple packages without individual commits
for (const pkg of ['lib-a', 'lib-b', 'lib-c']) {
await executeFlow(createBatchReleaseFlow(), pkg, '/workspace')
}
// Then create a single combined commitOnly generates changelog, no version bumps or git operations.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a changelog-only flow
import { createChangelogOnlyFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createChangelogOnlyFlow()
const result = await executeFlow(flow, 'my-lib', '/workspace')
// Only changelog is updated, no version bump
console.log(result.state.changelogEntry)In independent versioning, when a dependency is bumped, dependents may also need version bumps.
Returns
FlowStepExample
Checking if dependent packages need bumps
import { createCheckDependentBumpsStep, executeStep } from '@hyperfrontend/versioning'
const step = createCheckDependentBumpsStep()
const result = await executeStep(step, contextWithTrackDeps)
// Result indicates if any dependents need bumps
console.log(result.message)Returns
FlowStepExample
Generating a combined changelog for all packages
import { createCombinedChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createCombinedChangelogStep()
const result = await executeStep(step, context)
// Single changelog for all packages
console.log(result.message)This flow follows the standard conventional commits workflow:
- Fetch published version from registry
- Resolve repository configuration (for compare URLs)
- Analyze commits since last release
- Calculate version bump based on commit types
- Check if version already published (idempotency)
- Generate changelog entry (with compare URL if repository resolved)
- Update package.json version
- Write changelog to file
- Create git commit (optional)
- Create git tag (optional, typically after publish)
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a conventional versioning flow
import { createConventionalFlow, executeFlow } from '@hyperfrontend/versioning'
// Use defaults
const flow = createConventionalFlow()
// With overrides
const customFlow = createConventionalFlow({
skipTag: false,
releaseTypes: ['feat', 'fix'],
})
const result = await executeFlow(flow, 'lib-utils', '/workspace')Similar to synced, but uses a fixed version scheme where the version is explicitly provided rather than calculated from commits.
Parameters
| Name | Type | Description |
|---|---|---|
§version | string | The fixed version to use |
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a fixed version release flow
import { createFixedVersionFlow, executeFlow } from '@hyperfrontend/versioning'
// Release with explicit version, ignoring commit analysis
const flow = createFixedVersionFlow('2.0.0')
const result = await executeFlow(flow, 'workspace', '/workspace')
console.log(result.state.nextVersion)
// => '2.0.0'This flow is designed for monorepos where each package is versioned independently:
- Fetch published version from registry
- Analyze commits since last release
- Calculate version bump based on commit types
- Check for dependent package bumps (cascade)
- Check if version already published (idempotency)
- Generate changelog entry
- Update package.json version
- Cascade dependency updates
- Write changelog to file
- Create git commit
- Create git tag
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating an independent versioning flow
import { createIndependentFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createIndependentFlow()
const result = await executeFlow(flow, 'lib-utils', '/workspace')
// Inspect the version that was released
console.log(result.state.nextVersion)Skips changelog and tag creation.
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a minimal release flow
import { createMinimalFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createMinimalFlow()
const result = await executeFlow(flow, 'my-lib', '/workspace')
// Quick release without changelog or tags
console.log('Released:', result.state.nextVersion)Returns
FlowStepExample
Syncing all packages to the same version
import { createSyncAllPackagesStep, executeStep } from '@hyperfrontend/versioning'
const step = createSyncAllPackagesStep()
const result = await executeStep(step, context)
// All packages updated to same version
console.log(result.stateUpdates?.modifiedFiles)This flow maintains the same version across all packages in a monorepo. When any package changes, all packages get the same new version.
Flow steps:
- Fetch published version from registry
- Analyze commits across all packages
- Calculate version bump (highest needed)
- Check if version already published
- Sync all package versions
- Generate combined changelog
- Write changelog to root
- Create git commit
- Create single git tag
Parameters
| Name | Type | Description |
|---|---|---|
§config? | Partial< | Optional configuration overrides |
Returns
VersionFlowExample
Creating a synced versioning flow
import { createSyncedFlow, executeFlow } from '@hyperfrontend/versioning'
const flow = createSyncedFlow()
const result = await executeFlow(flow, 'workspace', '/workspace')
// All packages now share the same version
console.log(`Released v${result.state.nextVersion}`)● Variables
Individual flow step implementations for registry fetch, commit analysis, and changelog generation.
ƒ Functions
This step:
- Uses publishedCommit from npm registry (set by fetch-registry step)
- Verifies the commit is reachable from current HEAD
- Gets all commits since that commit (or recent commits if first release/fallback)
- Parses each commit using conventional commit format
- Classifies commits based on scope filtering strategy
- Filters to only release-worthy commits that belong to this project
- effectiveBaseCommit: The verified base commit (null if fallback was used)
- commits: Array of parsed conventional commits (for backward compatibility)
- classificationResult: Full classification result with source attribution
Returns
FlowStepExample
Analyzing commits since last release
import { createAnalyzeCommitsStep, executeStep } from '@hyperfrontend/versioning'
const step = createAnalyzeCommitsStep()
const result = await executeStep(step, context)
// Access analyzed commits
console.log(result.stateUpdates?.commits)
// => [{ type: 'feat', subject: 'add feature' }, ...]This step:
- Analyzes commit types and breaking changes
- Determines the appropriate bump level
- Calculates the next version
- bumpType: 'major' | 'minor' | 'patch' | 'none'
- nextVersion: Calculated next version string
Returns
FlowStepExample
Running the calculate-bump step
import { createCalculateBumpStep, executeStep } from '@hyperfrontend/versioning'
const step = createCalculateBumpStep()
const result = await executeStep(step, context)
// Get the calculated bump
console.log(result.stateUpdates?.bumpType)
// => 'minor'
console.log(result.stateUpdates?.nextVersion)
// => '1.2.0'This step cascades version updates to packages that depend on the updated package.
Returns
FlowStepExample
Updating dependent packages in a monorepo
import { createCascadeDependenciesStep, executeStep } from '@hyperfrontend/versioning'
const step = createCascadeDependenciesStep()
const result = await executeStep(step, contextWithTrackDeps)
// Dependent packages are updated
console.log(result.message)This step prevents redundant releases by checking if the calculated version is already published.
Returns
FlowStepExample
Preventing duplicate releases
import { createCheckIdempotencyStep, executeStep } from '@hyperfrontend/versioning'
const step = createCheckIdempotencyStep()
const result = await executeStep(step, context)
// If version already published, step skips with bumpType: 'none'
if (result.status === 'skipped') {
console.log('Version already published')
}This step:
- Queries the registry for the latest published version
- Reads the current version from package.json
- Determines if this is a first release
- publishedVersion: Latest version on registry (null if not published)
- currentVersion: Version from local package.json
- isFirstRelease: True if never published
Returns
FlowStepExample
Fetching published version from registry
import { createFetchRegistryStep, executeStep } from '@hyperfrontend/versioning'
const step = createFetchRegistryStep()
const result = await executeStep(step, context)
// Check if first release
if (result.stateUpdates?.isFirstRelease) {
console.log('First release detected')
}This step:
- Groups commits by type/section
- Creates changelog items from commits
- Assembles a complete changelog entry
- changelogEntry: The generated ChangelogEntry
Returns
FlowStepExample
Generating a changelog entry from commits
import { createGenerateChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createGenerateChangelogStep()
const result = await executeStep(step, context)
// Access the generated changelog entry
const entry = result.stateUpdates?.changelogEntry
console.log(entry?.version, entry?.sections.length)
// => '1.2.0', 3This step:
- Stages modified files
- Creates a commit with the configured message
- commitHash: Hash of the created commit
Returns
FlowStepExample
Creating and staging a release commit
import { createGitCommitStep, executeStep } from '@hyperfrontend/versioning'
const step = createGitCommitStep()
const result = await executeStep(step, context)
// Get the created commit hash
console.log(result.stateUpdates?.commitHash)
// => 'a1b2c3d4e5f6...'Returns
FlowStepExample
Pushing the tag to remote
import { createPushTagStep, executeStep } from '@hyperfrontend/versioning'
const step = createPushTagStep()
const result = await executeStep(step, context)
// Tag is pushed to remote
console.log(result.message)
// => 'Pushed tag: my-lib@1.2.0'This step resolves repository configuration for compare URL generation. It supports multiple resolution modes:
undefinedor'disabled': No-op, backward compatible default'inferred': Auto-detect from package.json or git remoteRepositoryConfig: Direct repository configuration providedRepositoryResolution: Fine-grained control with mode and options
- repositoryConfig: Resolved repository configuration (if successful)
Returns
FlowStepExample
Configuring repository resolution modes
// Auto-detect repository
const flow = createFlow({
repository: 'inferred'
})
// Explicit repository
const flow = createFlow({
repository: {
platform: 'github',
baseUrl: 'https://github.com/owner/repo'
}
})This step:
- Creates an annotated git tag
- Uses the configured tag format
- tagName: Name of the created tag
Returns
FlowStepExample
Creating an annotated version tag
import { createTagStep, executeStep } from '@hyperfrontend/versioning'
const step = createTagStep()
const result = await executeStep(step, context)
// Get the created tag name
console.log(result.stateUpdates?.tagName)
// => 'my-lib@1.2.0'This step:
- Updates the version field in package.json
- Tracks the modified files
- modifiedFiles: Adds package.json to list
Returns
FlowStepExample
Updating the package.json version field
import { createUpdatePackageStep, executeStep } from '@hyperfrontend/versioning'
const step = createUpdatePackageStep()
const result = await executeStep(step, context)
// package.json version field is updated
console.log(result.stateUpdates?.modifiedFiles)
// => ['/workspace/libs/my-lib/package.json']This step writes the generated changelog entry to CHANGELOG.md.
Returns
FlowStepExample
Writing the changelog entry to CHANGELOG.md
import { createWriteChangelogStep, executeStep } from '@hyperfrontend/versioning'
const step = createWriteChangelogStep()
const result = await executeStep(step, context)
// CHANGELOG.md is updated with the new entry
console.log(result.message)
// => 'Updated CHANGELOG.md with version 1.2.0'● Variables
Git operations and models for commits, tags, refs, logging, staging, and diff operations.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§options | Omit< | Configuration for the commit operation(default: {}) |
Returns
GitCommitExample
Add staged changes to last commit without changing message
stage(['extra-file.ts'])
amendCommitNoEdit() // adds staged files to last commitcommitReachableFromHead( hash: string, options: Pick< GitLogOptions, "cwd" | "timeout">): boolean
A commit may exist in the repository but be orphaned (not in current branch history). This function verifies that the commit is actually in the history of the current HEAD.
Common use cases:
- Verify an external commit reference before using it for range queries
- Detect if history was rewritten (rebase/force push) after a reference was recorded
Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash to check |
§options | Pick< | Additional options(default: {}) |
Returns
booleanExample
Check if commit is reachable from HEAD
if (commitReachableFromHead(baseCommit)) {
// Safe to use for commit range queries
const commits = getCommitsSince(baseCommit)
} else {
// Commit not in current history, need fallback strategy
}Combines
discardChanges() + unstage(['.']) for complete working tree reset. Warning: Destructive operation: discarded changes cannot be recovered.
Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Reset working directory to match HEAD
// Reset working directory to match HEAD
if (discardAllChanges()) {
console.log('Working tree reset to HEAD')
}Uses
git checkout -- <files> to restore files from HEAD. Returns true if successful, false otherwise (silent failure pattern). Warning: Destructive operation: discarded changes cannot be recovered.
Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscardChangesOptions | Configuration including optional file list(default: {}) |
Returns
booleanExamples
Discard changes in tracked files
// Discard all changes
discardChanges()
// Discard specific files
discardChanges({ files: ['package.json', 'CHANGELOG.md'] })Typical rollback pattern
// Typical rollback pattern
if (hasUnstagedChanges()) {
discardChanges()
}
if (hasStagedChanges()) {
unstage(['.'])
}Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Full commit hash |
Returns
stringExample
Get short hash from full commit hash
getShortHash('abc123def456789') // => 'abc123d'Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The new commit message to use |
§options | Omit< | Configuration for the commit operation(default: {}) |
Returns
GitCommitExample
Amend the last commit with a new message
const commit = amendCommit('feat: improved feature')Parameters
| Name | Type | Description |
|---|---|---|
§type | GitRefType | Reference type |
§name | string | Reference name |
§remote? | string | Remote name (for remote type) |
Returns
stringExample
Build full reference names from type and name
buildRefName('branch', 'main') // 'refs/heads/main'
buildRefName('tag', 'v1.0.0') // 'refs/tags/v1.0.0'
buildRefName('remote', 'main', 'origin') // 'refs/remotes/origin/main'Parameters
Returns
stringExample
Build tag names with different formats
buildTagName('@scope/pkg', '1.2.3') // '@scope/pkg@1.2.3'
buildTagName('utils', '1.0.0', 'v${version}') // 'v1.0.0'
buildTagName('pkg', '2.0.0', '${package}-v${version}') // 'pkg-v2.0.0'Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Commit message (subject line) |
§options | CreateCommitOptions | Create options(default: {}) |
Returns
GitCommitExample
Create a new commit
const commit = createCommit('feat: add new feature')
const commit = createCommit('fix: resolve bug', { body: 'Detailed description' })Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash to check |
§options | Pick< | Additional options(default: {}) |
Returns
booleanExample
Check if a commit exists in the repository
if (commitExists('abc123def')) {
console.log('Commit found in repository')
}Returns
numberExample
Sort references alphabetically by name
const refs = [refB, refA, refC]
refs.sort(compareRefsByName) // => [refA, refB, refC]Returns
numberExample
Sort tags by version (newest first)
const tags = [tagV1, tagV2, tagV3]
tags.sort(compareTagsByVersion) // => [tagV3, tagV2, tagV1]Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateAnnotatedTagOptions | Tag creation options |
Returns
GitTagExample
Create an annotated git tag with metadata
const tag = createAnnotatedTag({
name: 'v1.0.0',
commitHash: 'abc123...',
message: 'Release v1.0.0',
taggerName: 'John Doe',
taggerEmail: 'john@example.com',
tagDate: '2026-03-12T10:00:00Z',
})createEmptyCommit( message: string, options: Omit< CreateCommitOptions, "allowEmpty">): GitCommit
Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Text for the empty commit |
§options | Omit< | Configuration for the commit operation(default: {}) |
Returns
GitCommitExample
Create an empty commit for CI triggers
const commit = createEmptyCommit('chore: trigger CI')Parameters
| Name | Type | Description |
|---|---|---|
§config | GitClientConfig | Client configuration(default: {}) |
Returns
GitClientExample
Create a git client for a repository
const git = createGitClient({ cwd: '/path/to/repo' })
const status = git.getStatus()
const commits = git.getCommitsSince('v1.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateGitCommitOptions | Commit creation options |
Returns
GitCommitExample
Create a git commit model
const commit = createGitCommit({
hash: 'abc123...',
authorName: 'John Doe',
authorEmail: 'john@example.com',
authorDate: '2026-03-12T10:00:00Z',
subject: 'feat: add new feature',
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateGitRefOptions | Reference creation options |
Returns
GitRefExample
Create a git reference from full name
const ref = createGitRef({
fullName: 'refs/heads/main',
commitHash: 'abc123...',
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateLightweightTagOptions | Tag creation options |
Returns
GitTagExample
Create a lightweight git tag
const tag = createLightweightTag({
name: 'v1.0.0',
commitHash: 'abc123...',
})Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The name for the new tag |
§options | CreateTagOptions | Configuration including optional message for annotated tags(default: {}) |
Returns
GitTagExample
Create lightweight and annotated tags
// Create lightweight tag
const tag = createTag('v1.0.0')
// Create annotated tag
const tag = createTag('v1.0.0', { message: 'Release v1.0.0' })Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to delete |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Delete a tag by name
const deleted = deleteTag('v1.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§author | string | Author to escape |
Returns
stringExample
Escape an author string for git commit
const safeAuthor = escapeAuthor('John Doe <john@example.com>')
// => 'John Doe <john@example.com>'Parameters
| Name | Type | Description |
|---|---|---|
§path | string | Path to escape |
Returns
stringExample
Escape a file path for git commands
const safePath = escapeFilePath('src/utils/helper.ts')
// => 'src/utils/helper.ts'
escapeFilePath('file$name.ts') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§arg | string | Argument to escape |
Returns
stringExample
Escape a git argument for shell commands
escapeGitArg('John Doe <john@example.com>') // => 'John Doe <john@example.com>'Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Message to escape |
Returns
stringExample
Escape a git commit message
const safeMessage = escapeGitMessage('Release v1.0.0 "stable"')
// => 'Release v1.0.0 \"stable\"'Parameters
| Name | Type | Description |
|---|---|---|
§path | string | Path to escape |
Returns
stringExample
Escape a file path for git commands
escapeGitPath('src/utils/helper.ts') // => 'src/utils/helper.ts'
escapeGitPath('path with spaces/file.ts') // => 'path with spaces/file.ts'Parameters
| Name | Type | Description |
|---|---|---|
§ref | string | Reference to escape |
Returns
stringExample
Escape a git reference for shell commands
escapeGitRef('refs/heads/main') // => 'refs/heads/main'
escapeGitRef('feature/test') // => 'feature/test'
escapeGitRef('ref$invalid') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§pattern | string | Pattern to escape |
Returns
stringExample
Escape a tag pattern for git commands
const safePattern = escapeGitTagPattern('@scope/package@')
// => '@scope/package@'
escapeGitTagPattern('v1.0.*') // throws - '*' is invalid@scope/package@1.2.3, package@1.2.3, package-v1.2.3 Uses character-by-character parsing (no regex).Parameters
| Name | Type | Description |
|---|---|---|
§tagName | string | Tag name to parse |
Returns
stringExample
Extract package name from tag
extractPackageFromTag('@scope/pkg@1.2.3') // '@scope/pkg'
extractPackageFromTag('lib-utils@1.2.3') // 'lib-utils'
extractPackageFromTag('v1.2.3') // undefinedParameters
| Name | Type | Description |
|---|---|---|
§subject | string | Commit subject line |
Returns
stringExample
Extract scope from conventional commit
extractScope('feat(lib-versioning): add git support') // 'lib-versioning'
extractScope('fix: resolve issue') // undefinedParameters
| Name | Type | Description |
|---|---|---|
§subject | string | Commit subject line |
Returns
stringExample
Extract type from conventional commit
extractType('feat(lib-versioning): add git support') // 'feat'
extractType('fix: resolve issue') // 'fix'
extractType('random message') // undefined@scope/package@1.2.3, package@1.2.3 Uses character-by-character parsing (no regex).Parameters
| Name | Type | Description |
|---|---|---|
§tagName | string | Tag name to parse |
Returns
stringExample
Extract version from various tag formats
extractVersionFromTag('v1.2.3') // '1.2.3'
extractVersionFromTag('@scope/pkg@1.2.3') // '1.2.3'
extractVersionFromTag('release-1.2.3') // '1.2.3'Returns
unknownExample
Filter references by remote
const originRefs = filterRefsByRemote(remoteRefs, 'origin')
const upstreamRefs = filterRefsByRemote(remoteRefs, 'upstream')Parameters
| Name | Type | Description |
|---|---|---|
§refs | unknown | References to filter |
§type | GitRefType | Type to filter by |
Returns
unknownExample
Filter references by type
const branches = filterRefsByType(allRefs, 'branch')
const tags = filterRefsByType(allRefs, 'tag')Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
numberExample
Get commits ahead of upstream
const ahead = getAheadCount()
console.log(`${ahead} commits to push`)Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
numberExample
Get commits behind upstream
const behind = getBehindCount()
console.log(`${behind} commits to pull`)Uses
git diff --name-only base...head which computes the merge-base internally, giving files changed in head since diverging from base.Parameters
| Name | Type | Description |
|---|---|---|
§base | string | Base reference (e.g., 'origin/main', 'v1.0.0') |
§head | string | Head reference (defaults to 'HEAD')(default: 'HEAD') |
§options | DiffOptions | Additional options(default: {}) |
Returns
unknownExample
Get changed files between refs
// Files changed since diverging from main
const files = getChangedFilesBetween('origin/main')
// Files changed between two tags
const files = getChangedFilesBetween('v1.0.0', 'v2.0.0')getChangedFilesBetweenWithStatus( base: string, head: string, options: DiffOptions): unknown
Parameters
| Name | Type | Description |
|---|---|---|
§base | string | Base reference |
§head | string | Head reference (defaults to 'HEAD')(default: 'HEAD') |
§options | DiffOptions | Additional options(default: {}) |
Returns
unknownExample
Get file changes with status information
const changes = getChangedFilesBetweenWithStatus('v1.0.0', 'v2.0.0')
const added = changes.filter(c => c.status === 'added')Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash (full or short) |
§options | Pick< | Additional options(default: {}) |
Returns
GitCommitExample
Get a single commit by hash
const commit = getCommit('abc1234')Parameters
| Name | Type | Description |
|---|---|---|
§options | GitLogOptions | Configuration for retrieving the commit log(default: {}) |
Returns
unknownExample
Get commit log with options
const commits = getCommitLog({ maxCount: 10 })
const recentChanges = getCommitLog({ from: 'v1.0.0', to: 'HEAD' })getCommitsBetween( from: string, to: string, options: Omit< GitLogOptions, "from" | "to">): unknown
Parameters
| Name | Type | Description |
|---|---|---|
§from | string | Starting reference (exclusive) |
§to | string | Ending reference (inclusive, default: HEAD)(default: 'HEAD') |
§options | Omit< | Additional options(default: {}) |
Returns
unknownExample
Get commits between two references
const commits = getCommitsBetween('v1.0.0', 'v1.1.0')Parameters
| Name | Type | Description |
|---|---|---|
§since | string | Reference to start from (exclusive) |
§options | Omit< | Additional options(default: {}) |
Returns
unknownExample
Get commits since a tag
const commits = getCommitsSince('v1.0.0')Uses
git diff-tree to retrieve file changes for a single commit. More efficient than fetching files for all commits when only specific commits need file attribution.Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash (full or abbreviated) |
§options | DiffOptions | Additional options(default: {}) |
Returns
GitCommitWithFilesExample
Get commit with its changed files
const commit = getCommitWithFiles('abc123')
if (commit) {
console.log(`${commit.subject} touched ${commit.files.length} files`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
stringExample
Get the current branch name
const branch = getCurrentBranch()Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Git operation configuration(default: {}) |
Returns
stringExample
Get the current HEAD commit
const head = getHead()Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the current HEAD commit hash
const hash = getHeadHash()
// => 'abc123def456789012345678901234567890abcd'Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the short HEAD commit hash
const shortHash = getHeadShortHash()
// => 'abc123d'Parameters
| Name | Type | Description |
|---|---|---|
§options | ListTagsOptions | Tag options with optional pattern(default: {}) |
Returns
GitTagExample
Get the latest tag by creation date
const latest = getLatestTag()
const latestVersion = getLatestTag({ pattern: 'v' })Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of modified files
const modified = getModifiedFiles()
// => ['src/utils.ts', 'README.md']Uses filesystem checks on
.git/ state files rather than git commands. This is more reliable because file existence checks are atomic OS syscalls that work even when the git index is corrupted or git commands would hang. Handles worktrees, submodules, and
$GIT_DIR overrides via git rev-parse --git-dir.Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the state check(default: {}) |
Returns
GitOperationStateExample
Detect if git is mid-operation
const state = getOperationState()
if (state.inProgress) {
console.warn(`Cannot proceed: git ${state.reason} in progress`)
}Returns
stringExample
Get the remote for a reference
getRemote({ type: 'remote', name: 'main', remote: 'origin' }) // => 'origin'
getRemote({ type: 'branch', name: 'main' }) // => undefinedParameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the repository root directory
const root = getRepositoryRoot()Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of staged files
const staged = getStagedFiles()
// => ['src/index.ts', 'package.json']Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Configuration for the status query(default: {}) |
Returns
RepositoryStatusExample
Get the full repository status
const status = getStatus()
if (!status.clean) {
console.log('Working tree has changes')
}Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to look up |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
GitTagExample
Get detailed information about a specific tag
const tag = getTag('v1.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§options | ListTagsOptions | Tag listing options(default: {}) |
Returns
unknownExample
Get all tags from the repository
const tags = getTags()
const versionTags = getTags({ pattern: 'v' })Parameters
| Name | Type | Description |
|---|---|---|
§packageName | string | Package name to match |
§options | ListTagsOptions | Tag options(default: {}) |
Returns
unknownExample
Get tags matching a package name
const tags = getTagsForPackage('@scope/pkg')Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of untracked files
const untracked = getUntrackedFiles()
// => ['new-file.ts', 'temp.log']Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check for merge conflicts
if (hasConflicts()) {
console.log('Resolve merge conflicts before continuing')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for staged changes before committing
if (hasStagedChanges()) { createCommit('...') }Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for unstaged changes before staging
if (hasUnstagedChanges()) { stage(['.']) }Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for untracked files
if (hasUntrackedFiles()) {
console.log('New files detected that are not tracked by git')
}Returns
booleanExample
Check if a tag is annotated
const tag = createAnnotatedTag({ name: 'v1.0.0', commitHash: 'abc123', message: 'Release' })
isAnnotatedTag(tag) // => trueReturns
booleanExample
Check if reference is a branch
isBranchRef({ type: 'branch', name: 'main' }) // => trueParameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Configuration for the status check(default: {}) |
Returns
booleanExample
Check if working tree is clean
if (!isClean()) {
throw new Error('Working tree has changes')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if directory is a git repository
if (!isGitRepository()) {
throw new Error('Not a git repository')
}Returns
booleanExample
Check if reference points to HEAD
isHeadRef({ type: 'head', name: 'HEAD' }) // => true
isHeadRef({ type: 'branch', name: 'main', isHead: true }) // => trueReturns
booleanExample
Check if a tag is lightweight
const tag = createLightweightTag({ name: 'v1.0.0', commitHash: 'abc123' })
isLightweightTag(tag) // => trueReturns
booleanExample
Check if commit is a merge commit
if (isMergeCommit(commit)) {
console.log('Commit has parents:', commit.parents)
}Convenience wrapper around
getOperationState() for simple checks. Use getOperationState() directly when you need to know which operation is in progress.Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the state check(default: {}) |
Returns
booleanExample
Check for incomplete operations
if (isOperationInProgress()) {
throw new Error('Complete or abort the current git operation first')
}Returns
booleanExample
Check if reference is a remote tracking branch
isRemoteRef({ type: 'remote', name: 'main', remote: 'origin' }) // => trueReturns
booleanExample
Check if commit is the initial commit
if (isRootCommit(commit)) {
console.log('This is the initial commit')
}Returns
booleanExample
Compare two commits by hash
isSameCommit(commitA, commitB) // => true if commitA.hash === commitB.hashReturns
booleanExample
Check if reference is a tag
isTagRef({ type: 'tag', name: 'v1.0.0' }) // => trueParameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if repository needs to be pulled
if (needsPull()) {
console.log('Remote commits need to be pulled')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if repository needs to be pushed
if (needsPush()) {
console.log('Local commits need to be pushed')
}Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag to push to the remote |
§remote | string | Remote name (defaults to 'origin')(default: 'origin') |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Push a tag to remote
pushTag('v1.0.0')
pushTag('v1.0.0', 'upstream')Parameters
| Name | Type | Description |
|---|---|---|
§files | unknown | Array of file paths relative to working directory |
§options | StageOptions | Configuration for the staging operation(default: {}) |
Returns
booleanExample
Stage files for commit
stage(['package.json', 'CHANGELOG.md'])
stage(['.'], { all: true })Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the staging operation(default: {}) |
Returns
booleanExample
Stage all tracked and untracked changes
stageAll() // stages all tracked and untracked changesParameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to verify |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Check if a tag exists
if (tagExists('v1.0.0')) { ... }Parameters
| Name | Type | Description |
|---|---|---|
§files | unknown | Array of file paths to remove from staging area |
§options | GitCommitOptions | Configuration for the unstage operation(default: {}) |
Returns
booleanExample
Unstage files from the index
unstage(['package.json'])◈ Interfaces
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Provides a unified interface for all git operations within a specific working directory.
Properties
Properties
Properties
Properties
Properties
readonly maxCount?:numberMaximum number of commits to retrieve. Zero retrieves every matching commit.Properties
readonly details:GitOperationStateDetailsDetailed state for each check performed. Useful for diagnostics or when multiple states could theoretically overlap.readonly reason:GitOperationStateReasonWhich operation is in progress, if any. Returns the first detected state (exit-early pattern).Properties
Properties
Properties
Properties
Properties
Properties
Properties
◆ Types
type FileChangeStatus = "added" | "modified" | "deleted" | "renamed" | "copied"type FileStatus = "modified" | "added" | "deleted" | "renamed" | "copied" | "untracked" | "ignored" | "unmerged"type GitOperationStateOptions = GitCommitOptionsThese correspond to state files that git creates during multi-step operations:
'rebase-interactive':.git/rebase-mergeexists (interactive rebase paused)'rebase-apply':.git/rebase-applyexists (mailbox-based rebase orgit am)'merge-in-progress':.git/MERGE_HEADexists (merge awaiting conflict resolution)
type GitOperationStateReason = "rebase-interactive" | "rebase-apply" | "merge-in-progress"type GitRefType = "branch" | "tag" | "remote" | "head" | "stash"type GitTagType = "lightweight" | "annotated"● Variables
Git data models for commits, tags, and refs with factory functions and utilities.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§type | GitRefType | Reference type |
§name | string | Reference name |
§remote? | string | Remote name (for remote type) |
Returns
stringExample
Build full reference names from type and name
buildRefName('branch', 'main') // 'refs/heads/main'
buildRefName('tag', 'v1.0.0') // 'refs/tags/v1.0.0'
buildRefName('remote', 'main', 'origin') // 'refs/remotes/origin/main'Parameters
Returns
stringExample
Build tag names with different formats
buildTagName('@scope/pkg', '1.2.3') // '@scope/pkg@1.2.3'
buildTagName('utils', '1.0.0', 'v${version}') // 'v1.0.0'
buildTagName('pkg', '2.0.0', '${package}-v${version}') // 'pkg-v2.0.0'Returns
numberExample
Sort references alphabetically by name
const refs = [refB, refA, refC]
refs.sort(compareRefsByName) // => [refA, refB, refC]Returns
numberExample
Sort tags by version (newest first)
const tags = [tagV1, tagV2, tagV3]
tags.sort(compareTagsByVersion) // => [tagV3, tagV2, tagV1]Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateAnnotatedTagOptions | Tag creation options |
Returns
GitTagExample
Create an annotated git tag with metadata
const tag = createAnnotatedTag({
name: 'v1.0.0',
commitHash: 'abc123...',
message: 'Release v1.0.0',
taggerName: 'John Doe',
taggerEmail: 'john@example.com',
tagDate: '2026-03-12T10:00:00Z',
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateGitCommitOptions | Commit creation options |
Returns
GitCommitExample
Create a git commit model
const commit = createGitCommit({
hash: 'abc123...',
authorName: 'John Doe',
authorEmail: 'john@example.com',
authorDate: '2026-03-12T10:00:00Z',
subject: 'feat: add new feature',
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateGitRefOptions | Reference creation options |
Returns
GitRefExample
Create a git reference from full name
const ref = createGitRef({
fullName: 'refs/heads/main',
commitHash: 'abc123...',
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateLightweightTagOptions | Tag creation options |
Returns
GitTagExample
Create a lightweight git tag
const tag = createLightweightTag({
name: 'v1.0.0',
commitHash: 'abc123...',
})@scope/package@1.2.3, package@1.2.3, package-v1.2.3 Uses character-by-character parsing (no regex).Parameters
| Name | Type | Description |
|---|---|---|
§tagName | string | Tag name to parse |
Returns
stringExample
Extract package name from tag
extractPackageFromTag('@scope/pkg@1.2.3') // '@scope/pkg'
extractPackageFromTag('lib-utils@1.2.3') // 'lib-utils'
extractPackageFromTag('v1.2.3') // undefinedParameters
| Name | Type | Description |
|---|---|---|
§subject | string | Commit subject line |
Returns
stringExample
Extract scope from conventional commit
extractScope('feat(lib-versioning): add git support') // 'lib-versioning'
extractScope('fix: resolve issue') // undefinedParameters
| Name | Type | Description |
|---|---|---|
§subject | string | Commit subject line |
Returns
stringExample
Extract type from conventional commit
extractType('feat(lib-versioning): add git support') // 'feat'
extractType('fix: resolve issue') // 'fix'
extractType('random message') // undefined@scope/package@1.2.3, package@1.2.3 Uses character-by-character parsing (no regex).Parameters
| Name | Type | Description |
|---|---|---|
§tagName | string | Tag name to parse |
Returns
stringExample
Extract version from various tag formats
extractVersionFromTag('v1.2.3') // '1.2.3'
extractVersionFromTag('@scope/pkg@1.2.3') // '1.2.3'
extractVersionFromTag('release-1.2.3') // '1.2.3'Returns
unknownExample
Filter references by remote
const originRefs = filterRefsByRemote(remoteRefs, 'origin')
const upstreamRefs = filterRefsByRemote(remoteRefs, 'upstream')Parameters
| Name | Type | Description |
|---|---|---|
§refs | unknown | References to filter |
§type | GitRefType | Type to filter by |
Returns
unknownExample
Filter references by type
const branches = filterRefsByType(allRefs, 'branch')
const tags = filterRefsByType(allRefs, 'tag')Returns
stringExample
Get the remote for a reference
getRemote({ type: 'remote', name: 'main', remote: 'origin' }) // => 'origin'
getRemote({ type: 'branch', name: 'main' }) // => undefinedParameters
| Name | Type | Description |
|---|---|---|
§hash | string | Full commit hash |
Returns
stringExample
Get short hash from full commit hash
getShortHash('abc123def456789') // => 'abc123d'Returns
booleanExample
Check if a tag is annotated
const tag = createAnnotatedTag({ name: 'v1.0.0', commitHash: 'abc123', message: 'Release' })
isAnnotatedTag(tag) // => trueReturns
booleanExample
Check if reference is a branch
isBranchRef({ type: 'branch', name: 'main' }) // => trueReturns
booleanExample
Check if reference points to HEAD
isHeadRef({ type: 'head', name: 'HEAD' }) // => true
isHeadRef({ type: 'branch', name: 'main', isHead: true }) // => trueReturns
booleanExample
Check if a tag is lightweight
const tag = createLightweightTag({ name: 'v1.0.0', commitHash: 'abc123' })
isLightweightTag(tag) // => trueReturns
booleanExample
Check if commit is a merge commit
if (isMergeCommit(commit)) {
console.log('Commit has parents:', commit.parents)
}Returns
booleanExample
Check if reference is a remote tracking branch
isRemoteRef({ type: 'remote', name: 'main', remote: 'origin' }) // => trueReturns
booleanExample
Check if commit is the initial commit
if (isRootCommit(commit)) {
console.log('This is the initial commit')
}Returns
booleanExample
Compare two commits by hash
isSameCommit(commitA, commitB) // => true if commitA.hash === commitB.hashReturns
booleanExample
Check if reference is a tag
isTagRef({ type: 'tag', name: 'v1.0.0' }) // => true◈ Interfaces
Properties
Properties
Properties
Properties
Properties
Properties
◆ Types
type GitRefType = "branch" | "tag" | "remote" | "head" | "stash"type GitTagType = "lightweight" | "annotated"Git shell command wrappers for log, tag, commit, staging, status, and diff operations.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The new commit message to use |
§options | Omit< | Configuration for the commit operation(default: {}) |
Returns
GitCommitExample
Amend the last commit with a new message
const commit = amendCommit('feat: improved feature')Parameters
| Name | Type | Description |
|---|---|---|
§options | Omit< | Configuration for the commit operation(default: {}) |
Returns
GitCommitExample
Add staged changes to last commit without changing message
stage(['extra-file.ts'])
amendCommitNoEdit() // adds staged files to last commitParameters
| Name | Type | Description |
|---|---|---|
§message | string | Commit message (subject line) |
§options | CreateCommitOptions | Create options(default: {}) |
Returns
GitCommitExample
Create a new commit
const commit = createCommit('feat: add new feature')
const commit = createCommit('fix: resolve bug', { body: 'Detailed description' })Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash to check |
§options | Pick< | Additional options(default: {}) |
Returns
booleanExample
Check if a commit exists in the repository
if (commitExists('abc123def')) {
console.log('Commit found in repository')
}commitReachableFromHead( hash: string, options: Pick< GitLogOptions, "cwd" | "timeout">): boolean
A commit may exist in the repository but be orphaned (not in current branch history). This function verifies that the commit is actually in the history of the current HEAD.
Common use cases:
- Verify an external commit reference before using it for range queries
- Detect if history was rewritten (rebase/force push) after a reference was recorded
Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash to check |
§options | Pick< | Additional options(default: {}) |
Returns
booleanExample
Check if commit is reachable from HEAD
if (commitReachableFromHead(baseCommit)) {
// Safe to use for commit range queries
const commits = getCommitsSince(baseCommit)
} else {
// Commit not in current history, need fallback strategy
}createEmptyCommit( message: string, options: Omit< CreateCommitOptions, "allowEmpty">): GitCommit
Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Text for the empty commit |
§options | Omit< | Configuration for the commit operation(default: {}) |
Returns
GitCommitExample
Create an empty commit for CI triggers
const commit = createEmptyCommit('chore: trigger CI')Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The name for the new tag |
§options | CreateTagOptions | Configuration including optional message for annotated tags(default: {}) |
Returns
GitTagExample
Create lightweight and annotated tags
// Create lightweight tag
const tag = createTag('v1.0.0')
// Create annotated tag
const tag = createTag('v1.0.0', { message: 'Release v1.0.0' })Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to delete |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Delete a tag by name
const deleted = deleteTag('v1.0.0')Combines
discardChanges() + unstage(['.']) for complete working tree reset. Warning: Destructive operation: discarded changes cannot be recovered.
Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Reset working directory to match HEAD
// Reset working directory to match HEAD
if (discardAllChanges()) {
console.log('Working tree reset to HEAD')
}Uses
git checkout -- <files> to restore files from HEAD. Returns true if successful, false otherwise (silent failure pattern). Warning: Destructive operation: discarded changes cannot be recovered.
Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscardChangesOptions | Configuration including optional file list(default: {}) |
Returns
booleanExamples
Discard changes in tracked files
// Discard all changes
discardChanges()
// Discard specific files
discardChanges({ files: ['package.json', 'CHANGELOG.md'] })Typical rollback pattern
// Typical rollback pattern
if (hasUnstagedChanges()) {
discardChanges()
}
if (hasStagedChanges()) {
unstage(['.'])
}Parameters
| Name | Type | Description |
|---|---|---|
§author | string | Author to escape |
Returns
stringExample
Escape an author string for git commit
const safeAuthor = escapeAuthor('John Doe <john@example.com>')
// => 'John Doe <john@example.com>'Parameters
| Name | Type | Description |
|---|---|---|
§path | string | Path to escape |
Returns
stringExample
Escape a file path for git commands
const safePath = escapeFilePath('src/utils/helper.ts')
// => 'src/utils/helper.ts'
escapeFilePath('file$name.ts') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§arg | string | Argument to escape |
Returns
stringExample
Escape a git argument for shell commands
escapeGitArg('John Doe <john@example.com>') // => 'John Doe <john@example.com>'Parameters
| Name | Type | Description |
|---|---|---|
§message | string | Message to escape |
Returns
stringExample
Escape a git commit message
const safeMessage = escapeGitMessage('Release v1.0.0 "stable"')
// => 'Release v1.0.0 \"stable\"'Parameters
| Name | Type | Description |
|---|---|---|
§path | string | Path to escape |
Returns
stringExample
Escape a file path for git commands
escapeGitPath('src/utils/helper.ts') // => 'src/utils/helper.ts'
escapeGitPath('path with spaces/file.ts') // => 'path with spaces/file.ts'Parameters
| Name | Type | Description |
|---|---|---|
§ref | string | Reference to escape |
Returns
stringExample
Escape a git reference for shell commands
escapeGitRef('refs/heads/main') // => 'refs/heads/main'
escapeGitRef('feature/test') // => 'feature/test'
escapeGitRef('ref$invalid') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§pattern | string | Pattern to escape |
Returns
stringExample
Escape a tag pattern for git commands
const safePattern = escapeGitTagPattern('@scope/package@')
// => '@scope/package@'
escapeGitTagPattern('v1.0.*') // throws - '*' is invalidParameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
numberExample
Get commits ahead of upstream
const ahead = getAheadCount()
console.log(`${ahead} commits to push`)Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
numberExample
Get commits behind upstream
const behind = getBehindCount()
console.log(`${behind} commits to pull`)Uses
git diff --name-only base...head which computes the merge-base internally, giving files changed in head since diverging from base.Parameters
| Name | Type | Description |
|---|---|---|
§base | string | Base reference (e.g., 'origin/main', 'v1.0.0') |
§head | string | Head reference (defaults to 'HEAD')(default: 'HEAD') |
§options | DiffOptions | Additional options(default: {}) |
Returns
unknownExample
Get changed files between refs
// Files changed since diverging from main
const files = getChangedFilesBetween('origin/main')
// Files changed between two tags
const files = getChangedFilesBetween('v1.0.0', 'v2.0.0')getChangedFilesBetweenWithStatus( base: string, head: string, options: DiffOptions): unknown
Parameters
| Name | Type | Description |
|---|---|---|
§base | string | Base reference |
§head | string | Head reference (defaults to 'HEAD')(default: 'HEAD') |
§options | DiffOptions | Additional options(default: {}) |
Returns
unknownExample
Get file changes with status information
const changes = getChangedFilesBetweenWithStatus('v1.0.0', 'v2.0.0')
const added = changes.filter(c => c.status === 'added')Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash (full or short) |
§options | Pick< | Additional options(default: {}) |
Returns
GitCommitExample
Get a single commit by hash
const commit = getCommit('abc1234')Parameters
| Name | Type | Description |
|---|---|---|
§options | GitLogOptions | Configuration for retrieving the commit log(default: {}) |
Returns
unknownExample
Get commit log with options
const commits = getCommitLog({ maxCount: 10 })
const recentChanges = getCommitLog({ from: 'v1.0.0', to: 'HEAD' })getCommitsBetween( from: string, to: string, options: Omit< GitLogOptions, "from" | "to">): unknown
Parameters
| Name | Type | Description |
|---|---|---|
§from | string | Starting reference (exclusive) |
§to | string | Ending reference (inclusive, default: HEAD)(default: 'HEAD') |
§options | Omit< | Additional options(default: {}) |
Returns
unknownExample
Get commits between two references
const commits = getCommitsBetween('v1.0.0', 'v1.1.0')Parameters
| Name | Type | Description |
|---|---|---|
§since | string | Reference to start from (exclusive) |
§options | Omit< | Additional options(default: {}) |
Returns
unknownExample
Get commits since a tag
const commits = getCommitsSince('v1.0.0')Uses
git diff-tree to retrieve file changes for a single commit. More efficient than fetching files for all commits when only specific commits need file attribution.Parameters
| Name | Type | Description |
|---|---|---|
§hash | string | Commit hash (full or abbreviated) |
§options | DiffOptions | Additional options(default: {}) |
Returns
GitCommitWithFilesExample
Get commit with its changed files
const commit = getCommitWithFiles('abc123')
if (commit) {
console.log(`${commit.subject} touched ${commit.files.length} files`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
stringExample
Get the current branch name
const branch = getCurrentBranch()Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Git operation configuration(default: {}) |
Returns
stringExample
Get the current HEAD commit
const head = getHead()Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the current HEAD commit hash
const hash = getHeadHash()
// => 'abc123def456789012345678901234567890abcd'Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the short HEAD commit hash
const shortHash = getHeadShortHash()
// => 'abc123d'Parameters
| Name | Type | Description |
|---|---|---|
§options | ListTagsOptions | Tag options with optional pattern(default: {}) |
Returns
GitTagExample
Get the latest tag by creation date
const latest = getLatestTag()
const latestVersion = getLatestTag({ pattern: 'v' })Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of modified files
const modified = getModifiedFiles()
// => ['src/utils.ts', 'README.md']Uses filesystem checks on
.git/ state files rather than git commands. This is more reliable because file existence checks are atomic OS syscalls that work even when the git index is corrupted or git commands would hang. Handles worktrees, submodules, and
$GIT_DIR overrides via git rev-parse --git-dir.Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the state check(default: {}) |
Returns
GitOperationStateExample
Detect if git is mid-operation
const state = getOperationState()
if (state.inProgress) {
console.warn(`Cannot proceed: git ${state.reason} in progress`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
stringExample
Get the repository root directory
const root = getRepositoryRoot()Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of staged files
const staged = getStagedFiles()
// => ['src/index.ts', 'package.json']Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Configuration for the status query(default: {}) |
Returns
RepositoryStatusExample
Get the full repository status
const status = getStatus()
if (!status.clean) {
console.log('Working tree has changes')
}Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to look up |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
GitTagExample
Get detailed information about a specific tag
const tag = getTag('v1.0.0')Parameters
| Name | Type | Description |
|---|---|---|
§options | ListTagsOptions | Tag listing options(default: {}) |
Returns
unknownExample
Get all tags from the repository
const tags = getTags()
const versionTags = getTags({ pattern: 'v' })Parameters
| Name | Type | Description |
|---|---|---|
§packageName | string | Package name to match |
§options | ListTagsOptions | Tag options(default: {}) |
Returns
unknownExample
Get tags matching a package name
const tags = getTagsForPackage('@scope/pkg')Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
unknownExample
Get list of untracked files
const untracked = getUntrackedFiles()
// => ['new-file.ts', 'temp.log']Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check for merge conflicts
if (hasConflicts()) {
console.log('Resolve merge conflicts before continuing')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for staged changes before committing
if (hasStagedChanges()) { createCommit('...') }Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for unstaged changes before staging
if (hasUnstagedChanges()) { stage(['.']) }Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the operation(default: {}) |
Returns
booleanExample
Check for untracked files
if (hasUntrackedFiles()) {
console.log('New files detected that are not tracked by git')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Configuration for the status check(default: {}) |
Returns
booleanExample
Check if working tree is clean
if (!isClean()) {
throw new Error('Working tree has changes')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if directory is a git repository
if (!isGitRepository()) {
throw new Error('Not a git repository')
}Convenience wrapper around
getOperationState() for simple checks. Use getOperationState() directly when you need to know which operation is in progress.Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the state check(default: {}) |
Returns
booleanExample
Check for incomplete operations
if (isOperationInProgress()) {
throw new Error('Complete or abort the current git operation first')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if repository needs to be pulled
if (needsPull()) {
console.log('Remote commits need to be pulled')
}Parameters
| Name | Type | Description |
|---|---|---|
§options | GitStatusOptions | Status options(default: {}) |
Returns
booleanExample
Check if repository needs to be pushed
if (needsPush()) {
console.log('Local commits need to be pushed')
}Parameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag to push to the remote |
§remote | string | Remote name (defaults to 'origin')(default: 'origin') |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Push a tag to remote
pushTag('v1.0.0')
pushTag('v1.0.0', 'upstream')Parameters
| Name | Type | Description |
|---|---|---|
§files | unknown | Array of file paths relative to working directory |
§options | StageOptions | Configuration for the staging operation(default: {}) |
Returns
booleanExample
Stage files for commit
stage(['package.json', 'CHANGELOG.md'])
stage(['.'], { all: true })Parameters
| Name | Type | Description |
|---|---|---|
§options | GitCommitOptions | Configuration for the staging operation(default: {}) |
Returns
booleanExample
Stage all tracked and untracked changes
stageAll() // stages all tracked and untracked changesParameters
| Name | Type | Description |
|---|---|---|
§name | string | The tag name to verify |
§options | GitTagOptions | Configuration for the tag operation(default: {}) |
Returns
booleanExample
Check if a tag exists
if (tagExists('v1.0.0')) { ... }Parameters
| Name | Type | Description |
|---|---|---|
§files | unknown | Array of file paths to remove from staging area |
§options | GitCommitOptions | Configuration for the unstage operation(default: {}) |
Returns
booleanExample
Unstage files from the index
unstage(['package.json'])◈ Interfaces
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
readonly maxCount?:numberMaximum number of commits to retrieve. Zero retrieves every matching commit.Properties
readonly details:GitOperationStateDetailsDetailed state for each check performed. Useful for diagnostics or when multiple states could theoretically overlap.readonly reason:GitOperationStateReasonWhich operation is in progress, if any. Returns the first detected state (exit-early pattern).Properties
Properties
Properties
Properties
Properties
◆ Types
type FileChangeStatus = "added" | "modified" | "deleted" | "renamed" | "copied"type FileStatus = "modified" | "added" | "deleted" | "renamed" | "copied" | "untracked" | "ignored" | "unmerged"type GitOperationStateOptions = GitCommitOptionsThese correspond to state files that git creates during multi-step operations:
'rebase-interactive':.git/rebase-mergeexists (interactive rebase paused)'rebase-apply':.git/rebase-applyexists (mailbox-based rebase orgit am)'merge-in-progress':.git/MERGE_HEADexists (merge awaiting conflict resolution)
type GitOperationStateReason = "rebase-interactive" | "rebase-apply" | "merge-in-progress"● Variables
Package registry abstraction with NPM client, caching, and version/package info models.
ƒ Functions
npm view proves absence or leaves the answer unknown. Only a 404 from the registry proves absence, and npm reports both a missing package and a missing version that way. Every other outcome, including a timeout that produces no output at all, leaves the published state unknown and must not be read as "not published".
Parameters
| Name | Type | Description |
|---|---|---|
§error | unknown | The error thrown by the invocation |
Returns
NpmLookupFailureExample
Classifying a lookup failure
try {
execFileSync('npm', ['view', name, 'version'])
} catch (error) {
const failure = classifyNpmError(error)
if (failure.kind === 'absent') return null
throw createRegistryUnavailableError({ ...failure, packageName: name })
}Parameters
| Name | Type | Description |
|---|---|---|
§ttl | number | Time-to-live in milliseconds (default: 60000 = 1 minute)(default: 60000) |
Returns
CacheExample
Using a cache with custom TTL
const cache = createCache(30000) // 30 second TTL
cache.set('key', { data: 'value' })
const value = cache.get<{ data: string }>('key')Parameters
| Name | Type | Description |
|---|---|---|
§config | RegistryConfig | Registry configuration(default: {}) |
Returns
RegistryExample
Creating an npm registry client
const registry = createNpmRegistry()
const version = await registry.getLatestVersion('@hyperfrontend/utils')Parameters
| Name | Type | Description |
|---|---|---|
§options | CreatePackageInfoOptions | Configuration for the package info |
Returns
PackageInfoExample
Creating package info from registry data
const info = createPackageInfo({
name: '@scope/my-package',
latestVersion: '2.0.0',
versions: ['1.0.0', '1.1.0', '2.0.0'],
license: 'MIT'
})Parameters
| Name | Type | Description |
|---|---|---|
§type | "npm" | Type of registry to create(default: 'npm') |
§config | RegistryConfig | Registry configuration(default: {}) |
Returns
RegistryExample
Creating an npm registry client
const registry = createRegistry('npm')
const version = await registry.getLatestVersion('lodash')Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateVersionInfoOptions | Configuration for the release info |
Returns
VersionInfoExample
Creating version info from registry data
const release = createVersionInfo({
version: '1.2.3',
publishedAt: '2024-01-15T10:30:00Z',
tarball: 'https://registry.npmjs.org/pkg/-/pkg-1.2.3.tgz'
})Parameters
| Name | Type | Description |
|---|---|---|
§name | string | Package name to escape |
Returns
stringExample
Validating package names for shell safety
escapePackageName('@scope/package') // => '@scope/package'
escapePackageName('simple-pkg') // => 'simple-pkg'
escapePackageName('pkg$invalid') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§version | string | Version string to escape |
Returns
stringExample
Validating version strings for shell safety
escapeVersion('1.2.3') // => '1.2.3'
escapeVersion('1.0.0-beta.1') // => '1.0.0-beta.1'
escapeVersion('1.0.0+build') // => '1.0.0+build'◈ Interfaces
Properties
Properties
Properties
Properties
readonly gitHead?:stringGit commit hash at publish time. Used to determine commit range for changelog generation. NOTE: This value comes from npm registry, making it immutable and independent of local git state.
◆ Types
npm view invocation.type NpmLookupFailure = AbsentLookupFailure | UnavailableLookupFailureEvery value here means the answer is unknown, never that the package or version is absent. An absent package is a successful lookup with a negative answer, and is reported through the normal return value instead.
type RegistryFailureReason = "network" | "authentication" | "rate-limit" | "server" | "timeout" | "unknown"● Variables
Registry types and factories for registries, packages, versions, and maintainers.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§options | CreatePackageInfoOptions | Configuration for the package info |
Returns
PackageInfoExample
Creating package info from registry data
const info = createPackageInfo({
name: '@scope/my-package',
latestVersion: '2.0.0',
versions: ['1.0.0', '1.1.0', '2.0.0'],
license: 'MIT'
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateVersionInfoOptions | Configuration for the release info |
Returns
VersionInfoExample
Creating version info from registry data
const release = createVersionInfo({
version: '1.2.3',
publishedAt: '2024-01-15T10:30:00Z',
tarball: 'https://registry.npmjs.org/pkg/-/pkg-1.2.3.tgz'
})◈ Interfaces
Properties
Properties
Properties
readonly gitHead?:stringGit commit hash at publish time. Used to determine commit range for changelog generation. NOTE: This value comes from npm registry, making it immutable and independent of local git state.
◆ Types
Every value here means the answer is unknown, never that the package or version is absent. An absent package is a successful lookup with a negative answer, and is reported through the normal return value instead.
type RegistryFailureReason = "network" | "authentication" | "rate-limit" | "server" | "timeout" | "unknown"● Variables
NPM registry client with package escaping and in-memory caching.
ƒ Functions
npm view proves absence or leaves the answer unknown. Only a 404 from the registry proves absence, and npm reports both a missing package and a missing version that way. Every other outcome, including a timeout that produces no output at all, leaves the published state unknown and must not be read as "not published".
Parameters
| Name | Type | Description |
|---|---|---|
§error | unknown | The error thrown by the invocation |
Returns
NpmLookupFailureExample
Classifying a lookup failure
try {
execFileSync('npm', ['view', name, 'version'])
} catch (error) {
const failure = classifyNpmError(error)
if (failure.kind === 'absent') return null
throw createRegistryUnavailableError({ ...failure, packageName: name })
}Parameters
| Name | Type | Description |
|---|---|---|
§ttl | number | Time-to-live in milliseconds (default: 60000 = 1 minute)(default: 60000) |
Returns
CacheExample
Using a cache with custom TTL
const cache = createCache(30000) // 30 second TTL
cache.set('key', { data: 'value' })
const value = cache.get<{ data: string }>('key')Parameters
| Name | Type | Description |
|---|---|---|
§config | RegistryConfig | Registry configuration(default: {}) |
Returns
RegistryExample
Creating an npm registry client
const registry = createNpmRegistry()
const version = await registry.getLatestVersion('@hyperfrontend/utils')Parameters
| Name | Type | Description |
|---|---|---|
§name | string | Package name to escape |
Returns
stringExample
Validating package names for shell safety
escapePackageName('@scope/package') // => '@scope/package'
escapePackageName('simple-pkg') // => 'simple-pkg'
escapePackageName('pkg$invalid') // throws - '$' is invalidParameters
| Name | Type | Description |
|---|---|---|
§version | string | Version string to escape |
Returns
stringExample
Validating version strings for shell safety
escapeVersion('1.2.3') // => '1.2.3'
escapeVersion('1.0.0-beta.1') // => '1.0.0-beta.1'
escapeVersion('1.0.0+build') // => '1.0.0+build'◈ Interfaces
Properties
◆ Types
npm view invocation.type NpmLookupFailure = AbsentLookupFailure | UnavailableLookupFailureRepository configuration with platform detection, URL parsing, and compare URL generation.
ƒ Functions
Each platform has a different URL format:
- GitHub:
{baseUrl}/compare/{fromCommit}...{toCommit}(three dots) - GitLab:
{baseUrl}/-/compare/{fromCommit}...{toCommit}(three dots,/-/prefix) - Bitbucket:
{baseUrl}/compare/{toCommit}..{fromCommit}(two dots, reversed order) - Azure DevOps:
{baseUrl}/compare?version=GT{toCommit}&compareVersion=GT{fromCommit}(query params)
custom platforms, a formatCompareUrl function must be provided in the repository config. For unknown platforms, returns null.Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateCompareUrlOptions | Compare URL options including repository, fromCommit, and toCommit |
Returns
stringExample
Creating platform-specific compare URLs
// GitHub
createCompareUrl({
repository: { platform: 'github', baseUrl: 'https://github.com/owner/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://github.com/owner/repo/compare/abc1234...def5678'
// GitLab
createCompareUrl({
repository: { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://gitlab.com/group/project/-/compare/abc1234...def5678'
// Bitbucket (reversed order)
createCompareUrl({
repository: { platform: 'bitbucket', baseUrl: 'https://bitbucket.org/owner/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://bitbucket.org/owner/repo/compare/def5678..abc1234'
// Azure DevOps
createCompareUrl({
repository: { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://dev.azure.com/org/proj/_git/repo/compare?version=GTdef5678&compareVersion=GTabc1234'
// Custom formatter
createCompareUrl({
repository: {
platform: 'custom',
baseUrl: 'https://my-git.internal/repo',
formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
},
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://my-git.internal/diff/abc1234/def5678'No compare URLs will be generated.
Returns
RepositoryResolutionExample
Disabling repository resolution
const config = createDisabledResolution()
// { mode: 'disabled' }Parameters
| Name | Type | Description |
|---|---|---|
§repository | RepositoryConfig | The repository config to use |
Returns
RepositoryResolutionExample
Using an explicit repository configuration
const config = createExplicitResolution({
platform: 'github',
baseUrl: 'https://github.com/owner/repo'
})Parameters
| Name | Type | Description |
|---|---|---|
§inferenceOrder | unknown | Order to try inference sources (default: package-json first)(default: ...) |
Returns
RepositoryResolutionExample
Configuring inference order
// Default order: package.json → git remote
const config = createInferredResolution()
// Custom order: git remote → package.json
const customOrder = createInferredResolution(['git-remote', 'package-json'])Normalizes the base URL by stripping trailing slashes and validating that custom platforms have a formatter function.
Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateRepositoryConfigOptions | Repository configuration options |
Returns
RepositoryConfigExample
Creating repository configurations
// GitHub repository
const config = createRepositoryConfig({
platform: 'github',
baseUrl: 'https://github.com/owner/repo'
})
// Custom platform
const customConfig = createRepositoryConfig({
platform: 'custom',
baseUrl: 'https://my-git.internal/repo',
formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
})This is a convenience function that combines
parseRepositoryUrl with createRepositoryConfig to produce a ready-to-use configuration.Parameters
| Name | Type | Description |
|---|---|---|
§gitUrl | string | Git repository URL in any supported format |
Returns
RepositoryConfigExample
Creating config from git URLs
const config = createRepositoryConfigFromUrl('https://github.com/owner/repo'))
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
const config = createRepositoryConfigFromUrl('git@gitlab.com:group/project.git')
// → { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' }First checks for exact match in known platforms, then applies heuristics for self-hosted instances (e.g.,
github.company.com → github).Parameters
| Name | Type | Description |
|---|---|---|
§hostname | string | Hostname to detect platform from (e.g., "github.com") |
Returns
RepositoryPlatformExample
Detecting platform from hostname
detectPlatformFromHostname('github.com') // 'github'
detectPlatformFromHostname('gitlab.mycompany.com') // 'gitlab'
detectPlatformFromHostname('custom-git.internal') // 'unknown'Unlike
inferRepositoryFromPackageJson, this returns just the URL string without creating a RepositoryConfig. Useful when you need the raw URL.Parameters
| Name | Type | Description |
|---|---|---|
§packageJsonContent | string | Raw JSON string content of package.json |
Returns
stringExample
Extracting raw repository URL
extractRepositoryUrl('{"repository": {"url": "https://github.com/owner/repo"}}')
// → 'https://github.com/owner/repo'
extractRepositoryUrl('{"repository": "github:owner/repo"}')
// → null (shorthand is not a URL)Handles multiple formats:
- Shorthand:
"github:owner/repo","gitlab:group/project","bitbucket:team/repo" - Bare shorthand:
"owner/repo"(defaults to GitHub) - URL string:
"https://github.com/owner/repo" - Object with URL:
{ "type": "git", "url": "https://..." }
Parameters
| Name | Type | Description |
|---|---|---|
§packageJsonContent | string | Raw JSON string content of package.json |
Returns
RepositoryConfigExample
Inferring repository from package.json content
// Shorthand format
inferRepositoryFromPackageJson('{"repository": "github:owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// URL string
inferRepositoryFromPackageJson('{"repository": "https://github.com/owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Object format
inferRepositoryFromPackageJson('{"repository": {"type": "git", "url": "https://github.com/owner/repo"}}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Bare shorthand (defaults to GitHub)
inferRepositoryFromPackageJson('{"repository": "owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }inferRepositoryFromPackageJsonObject( packageJson: PackageJsonForRepository): RepositoryConfig
This is useful when you already have the parsed object.
Parameters
| Name | Type | Description |
|---|---|---|
§packageJson | PackageJsonForRepository | Parsed package.json object |
Returns
RepositoryConfigExample
Inferring repository from parsed object
const pkg = { repository: 'github:owner/repo' }
inferRepositoryFromPackageJsonObject(pkg)
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }Parameters
| Name | Type | Description |
|---|---|---|
§platform | RepositoryPlatform | Platform identifier to check |
Returns
unknownExample
Checking if platform has built-in support
isKnownPlatform('github') // true
isKnownPlatform('gitlab') // true
isKnownPlatform('custom') // false
isKnownPlatform('unknown') // falseParameters
| Name | Type | Description |
|---|---|---|
§value | unknown | Value to check |
Returns
unknownExample
Type-checking repository config values
const config = { platform: 'github', baseUrl: 'https://...' }
if (isRepositoryConfig(config)) {
// config is typed as RepositoryConfig
}Parameters
| Name | Type | Description |
|---|---|---|
§value | unknown | Value to check |
Returns
unknownExample
Type-checking resolution configurations
import { isRepositoryResolution } from '@hyperfrontend/versioning'
const config = loadConfig()
if (isRepositoryResolution(config.repository)) {
console.log('Repository mode:', config.repository.mode)
}Supports multiple URL formats:
https://github.com/owner/repohttps://github.com/owner/repo.gitgit+https://github.com/owner/repo.gitgit://github.com/owner/repo.gitgit@github.com:owner/repo.git(SSH format)
github.mycompany.com→githubgitlab.internal.com→gitlab
https://dev.azure.com/org/project/_git/repohttps://org.visualstudio.com/project/_git/repo
Parameters
| Name | Type | Description |
|---|---|---|
§gitUrl | string | Git repository URL in any supported format |
Returns
ParsedRepositoryExample
Parsing various git URL formats
// GitHub HTTPS
parseRepositoryUrl('https://github.com/owner/repo')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// SSH format
parseRepositoryUrl('git@github.com:owner/repo.git')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Azure DevOps
parseRepositoryUrl('https://dev.azure.com/org/proj/_git/repo')
// → { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' }
// Self-hosted GitLab
parseRepositoryUrl('https://gitlab.mycompany.com/team/project')
// → { platform: 'gitlab', baseUrl: 'https://gitlab.mycompany.com/team/project' }◈ Interfaces
Properties
readonly baseUrl:stringBase URL for the repository. Trailing slashes will be automatically stripped.readonly formatCompareUrl?:CompareUrlFormatterCustom compare URL formatter. Required when platform is 'custom'.Properties
Package.json supports multiple formats for the repository field:
- Shorthand:
"github:owner/repo" - URL string:
"https://github.com/owner/repo" - Object:
{ "type": "git", "url": "..." }
Properties
Properties
Supports both standard platforms and self-hosted instances. When using a known platform, compare URLs are generated automatically. For custom or unknown platforms, provide a
formatCompareUrl function.Properties
readonly baseUrl:stringBase URL for the repository. This is the URL without any trailing slashes or paths like
/compare.readonly formatCompareUrl?:CompareUrlFormatterCustom compare URL formatter. Required when
platform is 'custom'. Optional for known platforms (overrides built-in formatter). Receives the from and to tags, returns the full compare URL.
Provides fine-grained control over repository resolution behavior.
Properties
readonly inferenceOrder?:unknownInference source order when mode is 'inferred'. Sources are tried in order until one succeeds. Default:
['package-json', 'git-remote']readonly repository?:RepositoryConfigExplicit repository config. Required when
mode is 'explicit'. Ignored when mode is 'inferred' or 'disabled'.◆ Types
type CompareUrlFormatter = ( fromCommit: string, toCommit: string) => stringEach platform has a specific URL format for compare links:
github:{baseUrl}/compare/{fromCommit}...{toCommit}gitlab:{baseUrl}/-/compare/{fromCommit}...{toCommit}bitbucket:{baseUrl}/compare/{toCommit}..{fromCommit}(reversed order)azure-devops:{baseUrl}/compare?version=GT{toCommit}&compareVersion=GT{fromCommit}
type KnownPlatform = "github" | "gitlab" | "bitbucket" | "azure-devops"package-json: Read fromrepositoryfield in package.jsongit-remote: Read fromgit remote get-url origin
type RepositoryInferenceSource = "package-json" | "git-remote"- Known platforms have built-in compare URL formatters
customrequires a customformatCompareUrlfunctionunknownindicates platform could not be determined (no URL generation)
type RepositoryPlatform = KnownPlatform | "custom" | "unknown"explicit: Use only the providedRepositoryConfig; error if missinginferred: Auto-detect from package.json repository field or git remotedisabled: Do not generate compare URLs (default for backward compatibility)
type RepositoryResolutionMode = "explicit" | "inferred" | "disabled"● Variables
Includes both standard SaaS domains and common patterns for self-hosted instances.
Repository types for platforms, resolutions, and configuration with factory functions.
ƒ Functions
No compare URLs will be generated.
Returns
RepositoryResolutionExample
Disabling repository resolution
const config = createDisabledResolution()
// { mode: 'disabled' }Parameters
| Name | Type | Description |
|---|---|---|
§repository | RepositoryConfig | The repository config to use |
Returns
RepositoryResolutionExample
Using an explicit repository configuration
const config = createExplicitResolution({
platform: 'github',
baseUrl: 'https://github.com/owner/repo'
})Parameters
| Name | Type | Description |
|---|---|---|
§inferenceOrder | unknown | Order to try inference sources (default: package-json first)(default: ...) |
Returns
RepositoryResolutionExample
Configuring inference order
// Default order: package.json → git remote
const config = createInferredResolution()
// Custom order: git remote → package.json
const customOrder = createInferredResolution(['git-remote', 'package-json'])Normalizes the base URL by stripping trailing slashes and validating that custom platforms have a formatter function.
Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateRepositoryConfigOptions | Repository configuration options |
Returns
RepositoryConfigExample
Creating repository configurations
// GitHub repository
const config = createRepositoryConfig({
platform: 'github',
baseUrl: 'https://github.com/owner/repo'
})
// Custom platform
const customConfig = createRepositoryConfig({
platform: 'custom',
baseUrl: 'https://my-git.internal/repo',
formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
})First checks for exact match in known platforms, then applies heuristics for self-hosted instances (e.g.,
github.company.com → github).Parameters
| Name | Type | Description |
|---|---|---|
§hostname | string | Hostname to detect platform from (e.g., "github.com") |
Returns
RepositoryPlatformExample
Detecting platform from hostname
detectPlatformFromHostname('github.com') // 'github'
detectPlatformFromHostname('gitlab.mycompany.com') // 'gitlab'
detectPlatformFromHostname('custom-git.internal') // 'unknown'Parameters
| Name | Type | Description |
|---|---|---|
§platform | RepositoryPlatform | Platform identifier to check |
Returns
unknownExample
Checking if platform has built-in support
isKnownPlatform('github') // true
isKnownPlatform('gitlab') // true
isKnownPlatform('custom') // false
isKnownPlatform('unknown') // falseParameters
| Name | Type | Description |
|---|---|---|
§value | unknown | Value to check |
Returns
unknownExample
Type-checking repository config values
const config = { platform: 'github', baseUrl: 'https://...' }
if (isRepositoryConfig(config)) {
// config is typed as RepositoryConfig
}Parameters
| Name | Type | Description |
|---|---|---|
§value | unknown | Value to check |
Returns
unknownExample
Type-checking resolution configurations
import { isRepositoryResolution } from '@hyperfrontend/versioning'
const config = loadConfig()
if (isRepositoryResolution(config.repository)) {
console.log('Repository mode:', config.repository.mode)
}◈ Interfaces
Properties
readonly baseUrl:stringBase URL for the repository. Trailing slashes will be automatically stripped.readonly formatCompareUrl?:CompareUrlFormatterCustom compare URL formatter. Required when platform is 'custom'.Supports both standard platforms and self-hosted instances. When using a known platform, compare URLs are generated automatically. For custom or unknown platforms, provide a
formatCompareUrl function.Properties
readonly baseUrl:stringBase URL for the repository. This is the URL without any trailing slashes or paths like
/compare.readonly formatCompareUrl?:CompareUrlFormatterCustom compare URL formatter. Required when
platform is 'custom'. Optional for known platforms (overrides built-in formatter). Receives the from and to tags, returns the full compare URL.
Provides fine-grained control over repository resolution behavior.
Properties
readonly inferenceOrder?:unknownInference source order when mode is 'inferred'. Sources are tried in order until one succeeds. Default:
['package-json', 'git-remote']readonly repository?:RepositoryConfigExplicit repository config. Required when
mode is 'explicit'. Ignored when mode is 'inferred' or 'disabled'.◆ Types
type CompareUrlFormatter = ( fromCommit: string, toCommit: string) => stringEach platform has a specific URL format for compare links:
github:{baseUrl}/compare/{fromCommit}...{toCommit}gitlab:{baseUrl}/-/compare/{fromCommit}...{toCommit}bitbucket:{baseUrl}/compare/{toCommit}..{fromCommit}(reversed order)azure-devops:{baseUrl}/compare?version=GT{toCommit}&compareVersion=GT{fromCommit}
type KnownPlatform = "github" | "gitlab" | "bitbucket" | "azure-devops"package-json: Read fromrepositoryfield in package.jsongit-remote: Read fromgit remote get-url origin
type RepositoryInferenceSource = "package-json" | "git-remote"- Known platforms have built-in compare URL formatters
customrequires a customformatCompareUrlfunctionunknownindicates platform could not be determined (no URL generation)
type RepositoryPlatform = KnownPlatform | "custom" | "unknown"explicit: Use only the providedRepositoryConfig; error if missinginferred: Auto-detect from package.json repository field or git remotedisabled: Do not generate compare URLs (default for backward compatibility)
type RepositoryResolutionMode = "explicit" | "inferred" | "disabled"● Variables
Includes both standard SaaS domains and common patterns for self-hosted instances.
Repository URL parsing and package.json inference utilities.
ƒ Functions
This is a convenience function that combines
parseRepositoryUrl with createRepositoryConfig to produce a ready-to-use configuration.Parameters
| Name | Type | Description |
|---|---|---|
§gitUrl | string | Git repository URL in any supported format |
Returns
RepositoryConfigExample
Creating config from git URLs
const config = createRepositoryConfigFromUrl('https://github.com/owner/repo'))
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
const config = createRepositoryConfigFromUrl('git@gitlab.com:group/project.git')
// → { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' }Unlike
inferRepositoryFromPackageJson, this returns just the URL string without creating a RepositoryConfig. Useful when you need the raw URL.Parameters
| Name | Type | Description |
|---|---|---|
§packageJsonContent | string | Raw JSON string content of package.json |
Returns
stringExample
Extracting raw repository URL
extractRepositoryUrl('{"repository": {"url": "https://github.com/owner/repo"}}')
// → 'https://github.com/owner/repo'
extractRepositoryUrl('{"repository": "github:owner/repo"}')
// → null (shorthand is not a URL)Handles multiple formats:
- Shorthand:
"github:owner/repo","gitlab:group/project","bitbucket:team/repo" - Bare shorthand:
"owner/repo"(defaults to GitHub) - URL string:
"https://github.com/owner/repo" - Object with URL:
{ "type": "git", "url": "https://..." }
Parameters
| Name | Type | Description |
|---|---|---|
§packageJsonContent | string | Raw JSON string content of package.json |
Returns
RepositoryConfigExample
Inferring repository from package.json content
// Shorthand format
inferRepositoryFromPackageJson('{"repository": "github:owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// URL string
inferRepositoryFromPackageJson('{"repository": "https://github.com/owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Object format
inferRepositoryFromPackageJson('{"repository": {"type": "git", "url": "https://github.com/owner/repo"}}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Bare shorthand (defaults to GitHub)
inferRepositoryFromPackageJson('{"repository": "owner/repo"}')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }inferRepositoryFromPackageJsonObject( packageJson: PackageJsonForRepository): RepositoryConfig
This is useful when you already have the parsed object.
Parameters
| Name | Type | Description |
|---|---|---|
§packageJson | PackageJsonForRepository | Parsed package.json object |
Returns
RepositoryConfigExample
Inferring repository from parsed object
const pkg = { repository: 'github:owner/repo' }
inferRepositoryFromPackageJsonObject(pkg)
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }Supports multiple URL formats:
https://github.com/owner/repohttps://github.com/owner/repo.gitgit+https://github.com/owner/repo.gitgit://github.com/owner/repo.gitgit@github.com:owner/repo.git(SSH format)
github.mycompany.com→githubgitlab.internal.com→gitlab
https://dev.azure.com/org/project/_git/repohttps://org.visualstudio.com/project/_git/repo
Parameters
| Name | Type | Description |
|---|---|---|
§gitUrl | string | Git repository URL in any supported format |
Returns
ParsedRepositoryExample
Parsing various git URL formats
// GitHub HTTPS
parseRepositoryUrl('https://github.com/owner/repo')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// SSH format
parseRepositoryUrl('git@github.com:owner/repo.git')
// → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
// Azure DevOps
parseRepositoryUrl('https://dev.azure.com/org/proj/_git/repo')
// → { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' }
// Self-hosted GitLab
parseRepositoryUrl('https://gitlab.mycompany.com/team/project')
// → { platform: 'gitlab', baseUrl: 'https://gitlab.mycompany.com/team/project' }◈ Interfaces
Properties
Package.json supports multiple formats for the repository field:
- Shorthand:
"github:owner/repo" - URL string:
"https://github.com/owner/repo" - Object:
{ "type": "git", "url": "..." }
Properties
Properties
Compare URL generation for building diff links between versions on supported platforms.
ƒ Functions
Each platform has a different URL format:
- GitHub:
{baseUrl}/compare/{fromCommit}...{toCommit}(three dots) - GitLab:
{baseUrl}/-/compare/{fromCommit}...{toCommit}(three dots,/-/prefix) - Bitbucket:
{baseUrl}/compare/{toCommit}..{fromCommit}(two dots, reversed order) - Azure DevOps:
{baseUrl}/compare?version=GT{toCommit}&compareVersion=GT{fromCommit}(query params)
custom platforms, a formatCompareUrl function must be provided in the repository config. For unknown platforms, returns null.Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateCompareUrlOptions | Compare URL options including repository, fromCommit, and toCommit |
Returns
stringExample
Creating platform-specific compare URLs
// GitHub
createCompareUrl({
repository: { platform: 'github', baseUrl: 'https://github.com/owner/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://github.com/owner/repo/compare/abc1234...def5678'
// GitLab
createCompareUrl({
repository: { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://gitlab.com/group/project/-/compare/abc1234...def5678'
// Bitbucket (reversed order)
createCompareUrl({
repository: { platform: 'bitbucket', baseUrl: 'https://bitbucket.org/owner/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://bitbucket.org/owner/repo/compare/def5678..abc1234'
// Azure DevOps
createCompareUrl({
repository: { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' },
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://dev.azure.com/org/proj/_git/repo/compare?version=GTdef5678&compareVersion=GTabc1234'
// Custom formatter
createCompareUrl({
repository: {
platform: 'custom',
baseUrl: 'https://my-git.internal/repo',
formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
},
fromCommit: 'abc1234',
toCommit: 'def5678'
})
// → 'https://my-git.internal/diff/abc1234/def5678'Full semver implementation with parsing, comparison, incrementing, and formatting.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The string to coerce |
Returns
SemVerExample
Coerce partial versions to valid semver
coerceVersion('1') // { major: 1, minor: 0, patch: 0, ... }
coerceVersion('1.2') // { major: 1, minor: 2, patch: 0, ... }
coerceVersion('v2.0') // { major: 2, minor: 0, patch: 0, ... }Returns
-1 | 0 | 1Example
Compare two semantic versions
compare(parseVersion('1.0.0'), parseVersion('2.0.0')) // -1
compare(parseVersion('1.0.0'), parseVersion('1.0.0')) // 0
compare(parseVersion('2.0.0'), parseVersion('1.0.0')) // 1Returns
RangeExample
Create a range that matches any version
const anyRange = createAnyRange()
satisfies(parseVersionStrict('999.999.999'), anyRange) // => trueParameters
| Name | Type | Description |
|---|---|---|
§operator | RangeOperator | The comparison operator |
§version | SemVer | The version to compare against |
Returns
ComparatorExample
Create a comparator for version comparison
createComparator('>=', parseVersionStrict('1.0.0'))
// => { operator: '>=', version: { major: 1, minor: 0, patch: 0, ... } }Parameters
| Name | Type | Description |
|---|---|---|
§comparators | unknown | Array of comparators (AND logic) |
Returns
ComparatorSetExample
Create a comparator set with AND logic
createComparatorSet([gte100, lt200]) // AND: >=1.0.0 AND <2.0.0Returns
RangeExample
Create a range that matches exactly one version
const exact = createExactRange(parseVersionStrict('1.2.3'))
satisfies(parseVersionStrict('1.2.3'), exact) // => true
satisfies(parseVersionStrict('1.2.4'), exact) // => falseReturns
SemVerExample
Create first 1.0.0 release version
const first = createFirstRelease()
format(first) // => '1.0.0'Returns
SemVerExample
Create initial 0.0.0 version
const initial = createInitialVersion()
format(initial) // => '0.0.0'Parameters
Returns
RangeExample
Create a range with OR logic between sets
createRange([set1, set2], '>=1.0.0 || >=2.0.0 <3.0.0')Returns
SemVerExample
Create a SemVer object from components
createSemVer({ major: 1, minor: 2, patch: 3 })
// => { major: 1, minor: 2, patch: 3, prerelease: [], build: [] }
createSemVer({ major: 1, minor: 0, patch: 0, prerelease: ['beta', '1'] })
// => 1.0.0-beta.1Returns
BumpTypeExample
Calculate the difference type between two versions
diff(parseVersion('1.0.0'), parseVersion('2.0.0')) // 'major'
diff(parseVersion('1.0.0'), parseVersion('1.1.0')) // 'minor'
diff(parseVersion('1.0.0'), parseVersion('1.0.1')) // 'patch'Returns
booleanExample
Check if two versions are equal
eq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => true
eq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.1')) // => falseReturns
stringExample
Format a version to its canonical string
format(parseVersionStrict('1.2.3')) // => '1.2.3'
format(createSemVer({ major: 1, minor: 0, patch: 0, prerelease: ['beta', '1'] }))
// => '1.0.0-beta.1'Parameters
| Name | Type | Description |
|---|---|---|
§comparator | Comparator | The comparator to format |
Returns
stringExample
Format a comparator to its string representation
formatComparator(createComparator('>=', parseVersionStrict('1.0.0')))
// => '>=1.0.0'Returns
stringExample
Format a range to its string representation
formatRange(parseRangeStrict('^1.0.0')) // => '^1.0.0'
formatRange(createAnyRange()) // => '*'Returns
stringExample
Format version without prerelease/build metadata
formatSimple(parseVersionStrict('1.2.3-beta.1+build')) // => '1.2.3'Returns
booleanExample
Check if version a is greater than b
gt(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => true
gt(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => falseReturns
booleanExample
Check if version a is greater than or equal to b
gte(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => true
gte(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => trueParameters
Returns
SemVerExample
Increment version by bump type
increment(parseVersion('1.2.3'), 'minor') // 1.3.0
increment(parseVersion('1.2.3'), 'major') // 2.0.0
increment(parseVersion('1.2.3'), 'prerelease', 'alpha') // 1.2.4-alpha.0Parameters
Returns
SemVerExample
Increment the prerelease portion of a version
incrementPrerelease(parseVersionStrict('1.0.0')) // => 1.0.1-alpha.0
incrementPrerelease(parseVersionStrict('1.0.0-alpha.0')) // => 1.0.0-alpha.1
incrementPrerelease(parseVersionStrict('1.0.0'), 'beta') // => 1.0.1-beta.0Returns
booleanExample
Check if version has prerelease identifiers
isPrerelease(parseVersionStrict('1.0.0-beta.1')) // => true
isPrerelease(parseVersionStrict('1.0.0')) // => falseReturns
booleanExample
Check if version is a stable release
isStable(parseVersionStrict('1.0.0')) // => true
isStable(parseVersionStrict('0.9.0')) // => false (< 1.0.0)
isStable(parseVersionStrict('1.0.0-beta')) // => false (prerelease)Returns
booleanExample
Check if a range represents a wildcard
isWildcard(parseRangeStrict('*')) // => true
isWildcard(parseRangeStrict('^1.0.0')) // => falseReturns
booleanExample
Check if version a is less than b
lt(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => true
lt(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => falseReturns
booleanExample
Check if version a is less than or equal to b
lte(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => true
lte(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => trueParameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions |
Returns
SemVerExample
Get the maximum version from an array
const versions = [parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')]
max(versions) // => { major: 2, minor: 0, patch: 0, ... }
max([]) // => nullParameters
Returns
SemVerExample
Find the maximum version satisfying a range
const versions = ['1.0.0', '1.5.0', '2.0.0'].map(parseVersionStrict)
maxSatisfying(versions, parseRangeStrict('^1.0.0'))
// => { major: 1, minor: 5, patch: 0, ... }Parameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions |
Returns
SemVerExample
Get the minimum version from an array
const versions = [parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')]
min(versions) // => { major: 1, minor: 0, patch: 0, ... }
min([]) // => nullParameters
Returns
SemVerExample
Find the minimum version satisfying a range
const versions = ['1.0.0', '1.5.0', '2.0.0'].map(parseVersionStrict)
minSatisfying(versions, parseRangeStrict('^1.0.0'))
// => { major: 1, minor: 0, patch: 0, ... }Returns
booleanExample
Check if two versions are not equal
neq(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => true
neq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => falseSupports:
- Exact: 1.2.3, =1.2.3
- Comparators: >1.0.0, >=1.0.0, <2.0.0, <=2.0.0
- Caret: ^1.2.3 (compatible with version)
- Tilde: ~1.2.3 (approximately equivalent)
- X-ranges: 1.x, 1.2.x, *
- Hyphen ranges: 1.0.0 - 2.0.0
- OR: 1.0.0 || 2.0.0
- AND: >=1.0.0 <2.0.0
Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The range string to parse |
Returns
ParseRangeResultExample
Parse semver range strings
parseRange('^1.0.0') // => { success: true, range: ... }
parseRange('>=1.0.0 <2.0.0') // => { success: true, range: ... }
parseRange('1.0.0 || 2.0.0') // => { success: true, range: ... }Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The range string to parse |
Returns
RangeExample
Parse range with strict validation
const range = parseRangeStrict('^1.0.0')
parseRangeStrict('invalid range!!!') // throws ErrorAccepts versions in the format: MAJOR.MINOR.PATCH[-prerelease][+build] Optional leading 'v' or '=' prefixes are stripped.
Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The version string to parse |
Returns
ParseVersionResultExample
Parse a semantic version string
parseVersion('1.2.3') // { success: true, version: { major: 1, minor: 2, patch: 3, ... } }
parseVersion('v1.0.0-alpha.1+build.123') // { success: true, ... }
parseVersion('invalid') // { success: false, error: '...' }Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The version string to parse |
Returns
SemVerExample
Parse version with strict validation
const v = parseVersionStrict('1.2.3')
// => { major: 1, minor: 2, patch: 3, prerelease: [], build: [] }
parseVersionStrict('invalid') // throws ErrorReturns
booleanExample
Check if version satisfies a range
satisfies(parseVersion('1.2.3'), parseRange('^1.0.0')) // true
satisfies(parseVersion('2.0.0'), parseRange('^1.0.0')) // falseParameters
| Name | Type | Description |
|---|---|---|
§version | SemVer | Version to check |
§comparator | Comparator | Comparator to test against |
Returns
booleanExample
Check if version satisfies a comparator
const gte100 = createComparator('>=', parseVersionStrict('1.0.0'))
satisfiesComparator(parseVersionStrict('2.0.0'), gte100) // => true
satisfiesComparator(parseVersionStrict('0.9.0'), gte100) // => falseParameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions to sort |
Returns
SemVer[ ]Example
Sort versions in ascending order
sort([v2, v1, v3]) // [v1, v2, v3]Parameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions to sort |
Returns
SemVer[ ]Example
Sort versions in descending order
sortDescending([v1, v3, v2]) // [v3, v2, v1]Returns
SemVerExample
Strip build metadata from version
const v = parseVersionStrict('1.0.0+build.123')
format(stripBuild(v)) // => '1.0.0'Returns
SemVerExample
Strip prerelease identifiers from version
const v = parseVersionStrict('1.0.0-beta.1')
format(stripPrerelease(v)) // => '1.0.0'◈ Interfaces
Examples:
- >=1.2.3 -> { operator: '>=', version: { major: 1, minor: 2, patch: 3 } }
- ^1.2.0 -> { operator: '^', version: { major: 1, minor: 2, patch: 0 } }
Properties
Example: ">=1.0.0 <2.0.0" -> two comparators in one set
Properties
Properties
Properties
Example: "^1.0.0 || ^2.0.0" -> two comparator sets
Properties
A version number in the format MAJOR.MINOR.PATCH with optional prerelease and build metadata.
Properties
◆ Types
type BumpType = "major" | "minor" | "patch" | "premajor" | "preminor" | "prepatch" | "prerelease" | "none"type RangeOperator = "=" | ">" | ">=" | "< " | "< =" | "^" | "~"Version comparison functions for equality, ordering, range satisfaction, and sorting.
ƒ Functions
Returns
-1 | 0 | 1Example
Compare two semantic versions
compare(parseVersion('1.0.0'), parseVersion('2.0.0')) // -1
compare(parseVersion('1.0.0'), parseVersion('1.0.0')) // 0
compare(parseVersion('2.0.0'), parseVersion('1.0.0')) // 1Returns
booleanExample
Check if two versions are equal
eq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => true
eq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.1')) // => falseReturns
booleanExample
Check if version a is greater than b
gt(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => true
gt(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => falseReturns
booleanExample
Check if version a is greater than or equal to b
gte(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => true
gte(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => trueReturns
booleanExample
Check if version a is less than b
lt(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => true
lt(parseVersionStrict('2.0.0'), parseVersionStrict('1.0.0')) // => falseReturns
booleanExample
Check if version a is less than or equal to b
lte(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => true
lte(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => trueParameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions |
Returns
SemVerExample
Get the maximum version from an array
const versions = [parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')]
max(versions) // => { major: 2, minor: 0, patch: 0, ... }
max([]) // => nullParameters
Returns
SemVerExample
Find the maximum version satisfying a range
const versions = ['1.0.0', '1.5.0', '2.0.0'].map(parseVersionStrict)
maxSatisfying(versions, parseRangeStrict('^1.0.0'))
// => { major: 1, minor: 5, patch: 0, ... }Parameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions |
Returns
SemVerExample
Get the minimum version from an array
const versions = [parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')]
min(versions) // => { major: 1, minor: 0, patch: 0, ... }
min([]) // => nullParameters
Returns
SemVerExample
Find the minimum version satisfying a range
const versions = ['1.0.0', '1.5.0', '2.0.0'].map(parseVersionStrict)
minSatisfying(versions, parseRangeStrict('^1.0.0'))
// => { major: 1, minor: 0, patch: 0, ... }Returns
booleanExample
Check if two versions are not equal
neq(parseVersionStrict('1.0.0'), parseVersionStrict('2.0.0')) // => true
neq(parseVersionStrict('1.0.0'), parseVersionStrict('1.0.0')) // => falseReturns
booleanExample
Check if version satisfies a range
satisfies(parseVersion('1.2.3'), parseRange('^1.0.0')) // true
satisfies(parseVersion('2.0.0'), parseRange('^1.0.0')) // falseParameters
| Name | Type | Description |
|---|---|---|
§version | SemVer | Version to check |
§comparator | Comparator | Comparator to test against |
Returns
booleanExample
Check if version satisfies a comparator
const gte100 = createComparator('>=', parseVersionStrict('1.0.0'))
satisfiesComparator(parseVersionStrict('2.0.0'), gte100) // => true
satisfiesComparator(parseVersionStrict('0.9.0'), gte100) // => falseParameters
| Name | Type | Description |
|---|---|---|
§versions | unknown | Array of versions to sort |
Returns
SemVer[ ]Example
Sort versions in ascending order
sort([v2, v1, v3]) // [v1, v2, v3]Version formatting for converting semver objects to strings.
ƒ Functions
Returns
stringExample
Format a version to its canonical string
format(parseVersionStrict('1.2.3')) // => '1.2.3'
format(createSemVer({ major: 1, minor: 0, patch: 0, prerelease: ['beta', '1'] }))
// => '1.0.0-beta.1'Parameters
| Name | Type | Description |
|---|---|---|
§comparator | Comparator | The comparator to format |
Returns
stringExample
Format a comparator to its string representation
formatComparator(createComparator('>=', parseVersionStrict('1.0.0')))
// => '>=1.0.0'Returns
stringExample
Format a range to its string representation
formatRange(parseRangeStrict('^1.0.0')) // => '^1.0.0'
formatRange(createAnyRange()) // => '*'Returns
stringExample
Format version without prerelease/build metadata
formatSimple(parseVersionStrict('1.2.3-beta.1+build')) // => '1.2.3'Version bumping utilities for incrementing versions and calculating diffs.
ƒ Functions
Returns
BumpTypeExample
Calculate the difference type between two versions
diff(parseVersion('1.0.0'), parseVersion('2.0.0')) // 'major'
diff(parseVersion('1.0.0'), parseVersion('1.1.0')) // 'minor'
diff(parseVersion('1.0.0'), parseVersion('1.0.1')) // 'patch'Parameters
Returns
SemVerExample
Increment version by bump type
increment(parseVersion('1.2.3'), 'minor') // 1.3.0
increment(parseVersion('1.2.3'), 'major') // 2.0.0
increment(parseVersion('1.2.3'), 'prerelease', 'alpha') // 1.2.4-alpha.0Parameters
Returns
SemVerExample
Increment the prerelease portion of a version
incrementPrerelease(parseVersionStrict('1.0.0')) // => 1.0.1-alpha.0
incrementPrerelease(parseVersionStrict('1.0.0-alpha.0')) // => 1.0.0-alpha.1
incrementPrerelease(parseVersionStrict('1.0.0'), 'beta') // => 1.0.1-beta.0Semver types for versions, ranges, comparators, and bump types with factory functions.
ƒ Functions
Returns
RangeExample
Create a range that matches any version
const anyRange = createAnyRange()
satisfies(parseVersionStrict('999.999.999'), anyRange) // => trueParameters
| Name | Type | Description |
|---|---|---|
§operator | RangeOperator | The comparison operator |
§version | SemVer | The version to compare against |
Returns
ComparatorExample
Create a comparator for version comparison
createComparator('>=', parseVersionStrict('1.0.0'))
// => { operator: '>=', version: { major: 1, minor: 0, patch: 0, ... } }Parameters
| Name | Type | Description |
|---|---|---|
§comparators | unknown | Array of comparators (AND logic) |
Returns
ComparatorSetExample
Create a comparator set with AND logic
createComparatorSet([gte100, lt200]) // AND: >=1.0.0 AND <2.0.0Returns
RangeExample
Create a range that matches exactly one version
const exact = createExactRange(parseVersionStrict('1.2.3'))
satisfies(parseVersionStrict('1.2.3'), exact) // => true
satisfies(parseVersionStrict('1.2.4'), exact) // => falseReturns
SemVerExample
Create first 1.0.0 release version
const first = createFirstRelease()
format(first) // => '1.0.0'Returns
SemVerExample
Create initial 0.0.0 version
const initial = createInitialVersion()
format(initial) // => '0.0.0'Parameters
Returns
RangeExample
Create a range with OR logic between sets
createRange([set1, set2], '>=1.0.0 || >=2.0.0 <3.0.0')Returns
SemVerExample
Create a SemVer object from components
createSemVer({ major: 1, minor: 2, patch: 3 })
// => { major: 1, minor: 2, patch: 3, prerelease: [], build: [] }
createSemVer({ major: 1, minor: 0, patch: 0, prerelease: ['beta', '1'] })
// => 1.0.0-beta.1Returns
booleanExample
Check if version has prerelease identifiers
isPrerelease(parseVersionStrict('1.0.0-beta.1')) // => true
isPrerelease(parseVersionStrict('1.0.0')) // => falseReturns
booleanExample
Check if version is a stable release
isStable(parseVersionStrict('1.0.0')) // => true
isStable(parseVersionStrict('0.9.0')) // => false (< 1.0.0)
isStable(parseVersionStrict('1.0.0-beta')) // => false (prerelease)Returns
booleanExample
Check if a range represents a wildcard
isWildcard(parseRangeStrict('*')) // => true
isWildcard(parseRangeStrict('^1.0.0')) // => falseReturns
SemVerExample
Strip build metadata from version
const v = parseVersionStrict('1.0.0+build.123')
format(stripBuild(v)) // => '1.0.0'Returns
SemVerExample
Strip prerelease identifiers from version
const v = parseVersionStrict('1.0.0-beta.1')
format(stripPrerelease(v)) // => '1.0.0'◈ Interfaces
Examples:
- >=1.2.3 -> { operator: '>=', version: { major: 1, minor: 2, patch: 3 } }
- ^1.2.0 -> { operator: '^', version: { major: 1, minor: 2, patch: 0 } }
Properties
Example: ">=1.0.0 <2.0.0" -> two comparators in one set
Properties
Example: "^1.0.0 || ^2.0.0" -> two comparator sets
Properties
A version number in the format MAJOR.MINOR.PATCH with optional prerelease and build metadata.
Properties
◆ Types
type BumpType = "major" | "minor" | "patch" | "premajor" | "preminor" | "prepatch" | "prerelease" | "none"type RangeOperator = "=" | ">" | ">=" | "< " | "< =" | "^" | "~"Version and range parsing with strict and coercion modes.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The string to coerce |
Returns
SemVerExample
Coerce partial versions to valid semver
coerceVersion('1') // { major: 1, minor: 0, patch: 0, ... }
coerceVersion('1.2') // { major: 1, minor: 2, patch: 0, ... }
coerceVersion('v2.0') // { major: 2, minor: 0, patch: 0, ... }Supports:
- Exact: 1.2.3, =1.2.3
- Comparators: >1.0.0, >=1.0.0, <2.0.0, <=2.0.0
- Caret: ^1.2.3 (compatible with version)
- Tilde: ~1.2.3 (approximately equivalent)
- X-ranges: 1.x, 1.2.x, *
- Hyphen ranges: 1.0.0 - 2.0.0
- OR: 1.0.0 || 2.0.0
- AND: >=1.0.0 <2.0.0
Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The range string to parse |
Returns
ParseRangeResultExample
Parse semver range strings
parseRange('^1.0.0') // => { success: true, range: ... }
parseRange('>=1.0.0 <2.0.0') // => { success: true, range: ... }
parseRange('1.0.0 || 2.0.0') // => { success: true, range: ... }Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The range string to parse |
Returns
RangeExample
Parse range with strict validation
const range = parseRangeStrict('^1.0.0')
parseRangeStrict('invalid range!!!') // throws ErrorAccepts versions in the format: MAJOR.MINOR.PATCH[-prerelease][+build] Optional leading 'v' or '=' prefixes are stripped.
Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The version string to parse |
Returns
ParseVersionResultExample
Parse a semantic version string
parseVersion('1.2.3') // { success: true, version: { major: 1, minor: 2, patch: 3, ... } }
parseVersion('v1.0.0-alpha.1+build.123') // { success: true, ... }
parseVersion('invalid') // { success: false, error: '...' }Parameters
| Name | Type | Description |
|---|---|---|
§input | string | The version string to parse |
Returns
SemVerExample
Parse version with strict validation
const v = parseVersionStrict('1.2.3')
// => { major: 1, minor: 2, patch: 3, prerelease: [], build: [] }
parseVersionStrict('invalid') // throws Error◈ Interfaces
Properties
Workspace management with package discovery, dependency graphs, and versioning coordination.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscoveryOptions | Discovery configuration options(default: {}) |
Returns
WorkspaceExample
Create a complete workspace object from disk
import { createWorkspaceFromDisk } from '@hyperfrontend/versioning'
const workspace = createWorkspaceFromDisk()
// Access projects
for (const project of workspace.projectList) {
console.log(`${project.name}@${project.version}`)
}
// Get dependents of a package
const dependents = workspace.dependencyGraph.get('lib-utils')Parameters
Returns
Map< string, string>Example
Find changelogs using VFS tree
import { findChangelogsInTree, discoverPackages } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function myGenerator(tree: Tree) {
const { packages } = discoverPackages()
const changelogs = findChangelogsInTree(tree, packages)
for (const [projectPath, changelogPath] of changelogs) {
console.log(`Found changelog at ${changelogPath}`)
}
}Returns
stringExample
Find project changelog using VFS tree
import { findProjectChangelogInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function myGenerator(tree: Tree) {
const changelogPath = findProjectChangelogInTree(tree, 'libs/my-lib')
if (changelogPath) {
const content = tree.read(changelogPath, 'utf-8')
// Process changelog content
}
}updateDependencyReferencesInTree( tree: Tree, packageJsonPath: string, versionUpdates: Map< string, string>): void
Uses the
changeFile() pattern for cleaner transformation. Silently skips if the file doesn't exist.Parameters
Example
Update dependency version references using VFS Tree
import { updateDependencyReferencesInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function syncDeps(tree: Tree) {
const updates = new Map([
['@myorg/utils', '2.0.0'],
['@myorg/core', '3.1.0'],
])
updateDependencyReferencesInTree(tree, 'apps/my-app/package.json', updates)
}Parameters
Returns
ProjectExample
Add an internal dependent to a project
import { discoverProject, addDependent } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
const updated = addDependent(project, '@myorg/new-app')
console.log('Dependents now:', updated.internalDependents)
}applyBumps( tree: Tree, workspace: Workspace, bumps: unknown, options: BatchUpdateOptions): BatchUpdateResult
Changes are buffered in the tree until
commitChanges() is called, enabling atomic commits and rollback on failure.Parameters
Returns
BatchUpdateResultExample
Apply planned bumps to workspace using VFS Tree
import { createTree, commitChanges } from '@hyperfrontend/project-scope'
import { applyBumps, calculateCascadeBumps } from '@hyperfrontend/versioning'
const tree = createTree(workspaceRoot)
const cascadeResult = calculateCascadeBumps(workspace, directBumps)
const updateResult = applyBumps(tree, workspace, cascadeResult.bumps)
if (updateResult.success) {
commitChanges(tree) // Atomic commit of all changes
console.log(`Updated ${updateResult.updated.length} packages`)
} else {
console.error('Some updates failed:', updateResult.failed)
// No commitChanges() call - changes are discarded
}Parameters
| Name | Type | Description |
|---|---|---|
§projects | unknown | List of projects to analyze |
Returns
DependencyGraphAnalysisExample
Build a complete dependency graph
import { buildDependencyGraph, discoverPackages } from '@hyperfrontend/versioning'
const { projects } = discoverPackages()
const analysis = buildDependencyGraph(projects)
// Get packages that depend on 'lib-utils'
const dependents = analysis.dependencyGraph.get('lib-utils') ?? []
// Get packages in topological order for building
const buildOrder = getTopologicalOrder(analysis)calculateCascadeBumps( workspace: Workspace, directBumps: unknown, options: CascadeBumpOptions): CascadeBumpResult
When packages are directly bumped (e.g., due to commits), their dependents may also need version bumps. This function calculates all affected packages.
Parameters
| Name | Type | Description |
|---|---|---|
§workspace | Workspace | Workspace containing projects and dependency graph |
§directBumps | unknown | Packages with direct changes |
§options | CascadeBumpOptions | Configuration for cascade bump calculation(default: {}) |
Returns
CascadeBumpResultExample
Calculate cascade bumps for a workspace
import { calculateCascadeBumps } from '@hyperfrontend/versioning'
// If lib-utils is getting a minor bump
const result = calculateCascadeBumps(workspace, [
{ name: 'lib-utils', bumpType: 'minor' }
])
// result.bumps includes lib-utils and all packages that depend on it
for (const bump of result.bumps) {
console.log(`${bump.name}: ${bump.currentVersion} -> ${bump.nextVersion}`)
}calculateCascadeBumpsFromPackage( workspace: Workspace, packageName: string, bumpType: BumpType, options: CascadeBumpOptions): CascadeBumpResult
Parameters
Returns
CascadeBumpResultExample
Calculate cascade bumps from a single package
import { discoverWorkspace, calculateCascadeBumpsFromPackage } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const result = calculateCascadeBumpsFromPackage(workspace, '@myorg/utils', 'minor')
console.log(`${result.totalAffected} packages will be bumped`)
for (const bump of result.bumps) {
console.log(`${bump.name}: ${bump.currentVersion} -> ${bump.nextVersion}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateProjectOptions | Project properties |
Returns
ProjectExample
Create a new Project object
import { createProject, readPackageJson } from '@hyperfrontend/versioning'
const packageJson = readPackageJson('./libs/my-lib/package.json')
const project = createProject({
name: '@myorg/my-lib',
version: '1.0.0',
path: './libs/my-lib',
packageJsonPath: './libs/my-lib/package.json',
packageJson,
changelogPath: './libs/my-lib/CHANGELOG.md',
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateWorkspaceOptions | Workspace properties |
Returns
WorkspaceExample
Create a new workspace object
import { createWorkspace, createWorkspaceConfig, createProject } from '@hyperfrontend/versioning'
const projects = new Map([['@myorg/utils', createProject({ ... })]])
const workspace = createWorkspace({
root: '/path/to/workspace',
type: 'nx',
projects,
config: createWorkspaceConfig(),
dependencyGraph: new Map(),
reverseDependencyGraph: new Map(),
})Parameters
| Name | Type | Description |
|---|---|---|
§options? | Partial< | Partial configuration options |
Returns
WorkspaceConfigExample
Create a workspace configuration with defaults
import { createWorkspaceConfig } from '@hyperfrontend/versioning'
const config = createWorkspaceConfig({
patterns: ['packages/*', 'libs/*'],
exclude: ['node_modules', 'dist'],
})Parameters
Returns
booleanExample
Check if one project depends on another
import { discoverWorkspace, dependsOn } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (dependsOn(workspace, '@myorg/app', '@myorg/utils')) {
console.log('@myorg/app directly depends on @myorg/utils')
}Parameters
Returns
unknownExample
Discover all changelog files within a workspace
import { discoverAllChangelogs } from '@hyperfrontend/versioning'
const changelogs = discoverAllChangelogs('/path/to/workspace')
for (const changelog of changelogs) {
console.log(`${changelog.projectPath} -> ${changelog.path}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscoveryOptions | Discovery options(default: {}) |
Returns
DiscoveryResultExample
Discover all packages within a workspace
import { discoverPackages } from '@hyperfrontend/versioning'
// Discover all packages in current workspace
const result = discoverPackages()
// Discover with custom patterns
const result = discoverPackages({
patterns: ['packages/*/package.json'],
includeChangelogs: true
})
// Access discovered projects
for (const project of result.projects) {
console.log(`${project.name}@${project.version}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Path to project directory or package.json |
Returns
ProjectExample
Discover a single project by path
import { discoverProject } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
console.log(`Found ${project.name}@${project.version}`)
}
// Also accepts direct package.json path
const project2 = discoverProject('./libs/utils/package.json')Parameters
| Name | Type | Description |
|---|---|---|
§projectName | string | Name of the project to find |
§options | DiscoveryOptions | Discovery options(default: {}) |
Returns
ProjectExample
Discover a project by name within a workspace
import { discoverProjectByName } from '@hyperfrontend/versioning'
const project = discoverProjectByName('@myorg/utils')
if (project) {
console.log(`Found at ${project.path}`)
}
// With custom workspace root
const project2 = discoverProjectByName('@myorg/core', { workspaceRoot: '/custom/path' })Parameters
Returns
Map< string, string>Example
Find changelog files for all packages
import { findChangelogs, discoverPackages } from '@hyperfrontend/versioning'
const { packages } = discoverPackages()
const changelogs = findChangelogs('/workspace', packages)
for (const [projectPath, changelogPath] of changelogs) {
console.log(`${projectPath} -> ${changelogPath}`)
}findInternalDependencies( packageJson: PackageJson, workspacePackageNames: Set< string>): string[ ]
Parameters
| Name | Type | Description |
|---|---|---|
§packageJson | PackageJson | Parsed package.json content |
§workspacePackageNames | Set< | Set of all package names in the workspace |
Returns
string[ ]Example
Find internal dependencies in a package
const internalDeps = findInternalDependencies(packageJson, allPackageNames)
// ['@scope/lib-a', '@scope/lib-b']findInternalDependenciesWithTypes( packageName: string, packageJson: PackageJson, workspacePackageNames: Set< string>): DependencyEdge[ ]
Parameters
| Name | Type | Description |
|---|---|---|
§packageName | string | Name of the package being analyzed |
§packageJson | PackageJson | Parsed package.json content |
§workspacePackageNames | Set< | Set of all package names in the workspace |
Returns
DependencyEdge[ ]Example
Find internal dependencies with type information
import { findInternalDependenciesWithTypes, readPackageJson } from '@hyperfrontend/versioning'
const packageJson = readPackageJson('./libs/my-lib/package.json')
const workspacePackages = new Set(['@myorg/utils', '@myorg/core'])
const edges = findInternalDependenciesWithTypes('@myorg/my-lib', packageJson, workspacePackages)
for (const edge of edges) {
console.log(`${edge.from} -> ${edge.to} (${edge.type})`)
}Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Path to project directory |
Returns
stringExample
Find the changelog for a single project
import { findProjectChangelog } from '@hyperfrontend/versioning'
const changelogPath = findProjectChangelog('./libs/my-lib')
if (changelogPath) {
console.log('Found changelog:', changelogPath)
}Parameters
Returns
unknownExample
Get projects that the given project depends on
import { discoverWorkspace, getDependencies } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const deps = getDependencies(workspace, '@myorg/app')
console.log(`@myorg/app depends on:`, deps)Returns
numberExample
Get the dependency count for a project
import { discoverProject, getDependencyCount } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project) {
console.log(`${project.name} depends on ${getDependencyCount(project)} internal packages`)
}Returns
numberExample
Get the dependent count for a project
import { discoverProject, getDependentCount } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
console.log(`${project.name} is used by ${getDependentCount(project)} packages`)
}Parameters
Returns
unknownExample
Get projects that depend on a given project
import { discoverWorkspace, getDependents } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const dependents = getDependents(workspace, '@myorg/utils')
console.log(`Packages depending on @myorg/utils:`, dependents)Parameters
Returns
stringExample
Get expected changelog path for a project
import { getExpectedChangelogPath } from '@hyperfrontend/versioning'
const changelogPath = getExpectedChangelogPath('./libs/my-lib')
// => '/workspace/libs/my-lib/CHANGELOG.md'
const customPath = getExpectedChangelogPath('./libs/my-lib', 'HISTORY.md')
// => '/workspace/libs/my-lib/HISTORY.md'Parameters
Returns
ProjectExample
Get a project by name from the workspace
import { discoverWorkspace, getProject } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const project = getProject(workspace, '@myorg/utils')
if (project) {
console.log(`Version: ${project.version}`)
}Returns
numberExample
Get the count of projects in the workspace
import { discoverWorkspace, getProjectCount } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
console.log(`Workspace contains ${getProjectCount(workspace)} projects`)Returns
unknownExample
Get all project names in the workspace
import { discoverWorkspace, getProjectNames } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const names = getProjectNames(workspace)
console.log(`Found ${names.length} projects:`, names)Parameters
| Name | Type | Description |
|---|---|---|
§analysis | DependencyGraphAnalysis | Dependency graph analysis result |
Returns
unknownExample
Get packages in topological order for building
const buildOrder = getTopologicalOrder(analysis)
for (const pkg of buildOrder) {
await build(pkg)
}Parameters
Returns
Set< string>Example
Get all transitive dependencies of a package
import { discoverWorkspace, getTransitiveDependencies } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const allDeps = getTransitiveDependencies(workspace, '@myorg/app')
console.log(`@myorg/app transitively depends on ${allDeps.size} packages`)
for (const dep of allDeps) {
console.log(` - ${dep}`)
}Parameters
Returns
Set< string>Example
Get all transitive dependents of a package
// If lib-a depends on lib-utils and app-main depends on lib-a
// Then getTransitiveDependents('lib-utils') returns ['lib-a', 'app-main']Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Directory containing the project to check |
Returns
booleanExample
Check if a project has a changelog
import { hasChangelog } from '@hyperfrontend/versioning'
if (hasChangelog('./libs/my-lib')) {
console.log('Project has a changelog')
}Returns
booleanExample
Check if a project has internal dependencies
import { discoverProject, hasInternalDependencies } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && hasInternalDependencies(project)) {
console.log(`${project.name} depends on:`, project.internalDependencies)
}Returns
booleanExample
Check if a project has internal dependents
import { discoverProject, hasInternalDependents } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project && hasInternalDependents(project)) {
console.log(`${project.name} is used by:`, project.internalDependents)
}Parameters
Returns
booleanExample
Check if a project exists in the workspace
import { discoverWorkspace, hasProject } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (hasProject(workspace, '@myorg/utils')) {
console.log('Package exists in workspace')
}Returns
booleanExample
Check if a project has a changelog
import { discoverProject, hasChangelog } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && !hasChangelog(project)) {
console.log('Warning: No changelog found for', project.name)
}Returns
booleanExample
Check if a project is private
import { discoverProject, isPrivate } from '@hyperfrontend/versioning'
const project = discoverProject('./apps/internal-app')
if (project && isPrivate(project)) {
console.log('Skipping private package')
}Returns
booleanExample
Check if a project is publishable
import { discoverProject, isPublishable } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && isPublishable(project)) {
console.log(`${project.name} can be published to npm`)
}Parameters
| Name | Type | Description |
|---|---|---|
§result | BatchUpdateResult | Result object from batch update operation |
Returns
stringExample
Create a summary of the batch update result
import { batchUpdateVersions, summarizeBatchUpdate } from '@hyperfrontend/versioning'
const result = batchUpdateVersions(workspace, updates)
console.log(summarizeBatchUpdate(result))
// Output:
// Successfully updated 3 package(s)
//
// Updated packages:
// @myorg/utils: 1.0.0 -> 1.1.0
// @myorg/core: 2.0.0 -> 2.1.0Parameters
| Name | Type | Description |
|---|---|---|
§result | CascadeBumpResult | Result object from cascade bump calculation |
Returns
stringExample
Get a summary of cascade bump results
import { calculateCascadeBumps, summarizeCascadeBumps } from '@hyperfrontend/versioning'
const result = calculateCascadeBumps(workspace, [{ name: '@myorg/utils', bumpType: 'patch' }])
console.log(summarizeCascadeBumps(result))
// Output:
// 3 package(s) affected:
// - 1 direct bump(s)
// - 2 cascade bump(s)Parameters
| Name | Type | Description |
|---|---|---|
§report | ValidationReport | Report object from workspace validation |
Returns
stringExample
Create a summary of the validation report
import { validateWorkspace, summarizeValidation } from '@hyperfrontend/versioning'
const report = validateWorkspace(workspace)
console.log(summarizeValidation(report))
// Output:
// Workspace validation passed
// 2 warning(s)Parameters
Returns
booleanExample
Check if one package transitively depends on another
import { discoverWorkspace, transitivelyDependsOn } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (transitivelyDependsOn(workspace, '@myorg/app', '@myorg/utils')) {
console.log('Bumping @myorg/utils will affect @myorg/app')
}Parameters
Example
Update a package.json version using VFS Tree
import { updatePackageVersionInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function bumpVersion(tree: Tree) {
updatePackageVersionInTree(tree, 'libs/my-lib/package.json', '2.0.0')
}Returns
ValidationResultExample
Validate a single project
import { discoverProject, validateProject } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project) {
const result = validateProject(project)
if (!result.valid) {
console.error('Validation failed:', result.error)
}
}Returns
ValidationReportExample
Validate a workspace for common issues
import { validateWorkspace } from '@hyperfrontend/versioning'
const report = validateWorkspace(workspace)
if (!report.valid) {
console.error(`${report.errorCount} error(s) found`)
for (const result of report.results) {
if (!result.result.valid) {
console.error(` ${result.checkName}: ${result.result.error}`)
}
}
}Parameters
Returns
ProjectExample
Create a project copy with updated dependents
import { discoverProject, withDependents } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
const updated = withDependents(project, ['@myorg/app', '@myorg/web'])
console.log('Updated dependents:', updated.internalDependents)
}◈ Interfaces
Properties
Properties
Properties
cascadeBumpType?:BumpTypeBump type for cascaded dependents. Default is 'patch' - dependents get a patch bump when their dependencies change.includeDevDependencies?:booleanWhether to include dev dependencies in cascade. Default is false - only production dependencies trigger cascades.includePeerDependencies?:booleanWhether to include peer dependencies in cascade. Default is true - peer dependency updates should cascade.Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
Properties
readonly reverseDependencyGraph:ReadonlyMap< string, unknown>Reverse dependency graph (project name -> dependencies)Properties
◆ Types
type BumpReason = "direct" | "cascade" | "sync"type DependencyGraph = ReadonlyMap< string, unknown>type DependencyType = "dependencies" | "devDependencies" | "peerDependencies" | "optionalDependencies"type WorkspaceType = "nx" | "turbo" | "lerna" | "pnpm" | "npm" | "yarn" | "rush" | "unknown"● Variables
Package discovery, changelog finding, and dependency graph construction for monorepos.
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§projects | unknown | List of projects to analyze |
Returns
DependencyGraphAnalysisExample
Build a complete dependency graph
import { buildDependencyGraph, discoverPackages } from '@hyperfrontend/versioning'
const { projects } = discoverPackages()
const analysis = buildDependencyGraph(projects)
// Get packages that depend on 'lib-utils'
const dependents = analysis.dependencyGraph.get('lib-utils') ?? []
// Get packages in topological order for building
const buildOrder = getTopologicalOrder(analysis)Parameters
Returns
unknownExample
Discover all changelog files within a workspace
import { discoverAllChangelogs } from '@hyperfrontend/versioning'
const changelogs = discoverAllChangelogs('/path/to/workspace')
for (const changelog of changelogs) {
console.log(`${changelog.projectPath} -> ${changelog.path}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscoveryOptions | Discovery options(default: {}) |
Returns
DiscoveryResultExample
Discover all packages within a workspace
import { discoverPackages } from '@hyperfrontend/versioning'
// Discover all packages in current workspace
const result = discoverPackages()
// Discover with custom patterns
const result = discoverPackages({
patterns: ['packages/*/package.json'],
includeChangelogs: true
})
// Access discovered projects
for (const project of result.projects) {
console.log(`${project.name}@${project.version}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Path to project directory or package.json |
Returns
ProjectExample
Discover a single project by path
import { discoverProject } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
console.log(`Found ${project.name}@${project.version}`)
}
// Also accepts direct package.json path
const project2 = discoverProject('./libs/utils/package.json')Parameters
| Name | Type | Description |
|---|---|---|
§projectName | string | Name of the project to find |
§options | DiscoveryOptions | Discovery options(default: {}) |
Returns
ProjectExample
Discover a project by name within a workspace
import { discoverProjectByName } from '@hyperfrontend/versioning'
const project = discoverProjectByName('@myorg/utils')
if (project) {
console.log(`Found at ${project.path}`)
}
// With custom workspace root
const project2 = discoverProjectByName('@myorg/core', { workspaceRoot: '/custom/path' })Parameters
Returns
Map< string, string>Example
Find changelog files for all packages
import { findChangelogs, discoverPackages } from '@hyperfrontend/versioning'
const { packages } = discoverPackages()
const changelogs = findChangelogs('/workspace', packages)
for (const [projectPath, changelogPath] of changelogs) {
console.log(`${projectPath} -> ${changelogPath}`)
}Parameters
Returns
Map< string, string>Example
Find changelogs using VFS tree
import { findChangelogsInTree, discoverPackages } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function myGenerator(tree: Tree) {
const { packages } = discoverPackages()
const changelogs = findChangelogsInTree(tree, packages)
for (const [projectPath, changelogPath] of changelogs) {
console.log(`Found changelog at ${changelogPath}`)
}
}findInternalDependencies( packageJson: PackageJson, workspacePackageNames: Set< string>): string[ ]
Parameters
| Name | Type | Description |
|---|---|---|
§packageJson | PackageJson | Parsed package.json content |
§workspacePackageNames | Set< | Set of all package names in the workspace |
Returns
string[ ]Example
Find internal dependencies in a package
const internalDeps = findInternalDependencies(packageJson, allPackageNames)
// ['@scope/lib-a', '@scope/lib-b']findInternalDependenciesWithTypes( packageName: string, packageJson: PackageJson, workspacePackageNames: Set< string>): DependencyEdge[ ]
Parameters
| Name | Type | Description |
|---|---|---|
§packageName | string | Name of the package being analyzed |
§packageJson | PackageJson | Parsed package.json content |
§workspacePackageNames | Set< | Set of all package names in the workspace |
Returns
DependencyEdge[ ]Example
Find internal dependencies with type information
import { findInternalDependenciesWithTypes, readPackageJson } from '@hyperfrontend/versioning'
const packageJson = readPackageJson('./libs/my-lib/package.json')
const workspacePackages = new Set(['@myorg/utils', '@myorg/core'])
const edges = findInternalDependenciesWithTypes('@myorg/my-lib', packageJson, workspacePackages)
for (const edge of edges) {
console.log(`${edge.from} -> ${edge.to} (${edge.type})`)
}Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Path to project directory |
Returns
stringExample
Find the changelog for a single project
import { findProjectChangelog } from '@hyperfrontend/versioning'
const changelogPath = findProjectChangelog('./libs/my-lib')
if (changelogPath) {
console.log('Found changelog:', changelogPath)
}Returns
stringExample
Find project changelog using VFS tree
import { findProjectChangelogInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function myGenerator(tree: Tree) {
const changelogPath = findProjectChangelogInTree(tree, 'libs/my-lib')
if (changelogPath) {
const content = tree.read(changelogPath, 'utf-8')
// Process changelog content
}
}Parameters
Returns
stringExample
Get expected changelog path for a project
import { getExpectedChangelogPath } from '@hyperfrontend/versioning'
const changelogPath = getExpectedChangelogPath('./libs/my-lib')
// => '/workspace/libs/my-lib/CHANGELOG.md'
const customPath = getExpectedChangelogPath('./libs/my-lib', 'HISTORY.md')
// => '/workspace/libs/my-lib/HISTORY.md'Parameters
| Name | Type | Description |
|---|---|---|
§analysis | DependencyGraphAnalysis | Dependency graph analysis result |
Returns
unknownExample
Get packages in topological order for building
const buildOrder = getTopologicalOrder(analysis)
for (const pkg of buildOrder) {
await build(pkg)
}Parameters
Returns
Set< string>Example
Get all transitive dependencies of a package
import { discoverWorkspace, getTransitiveDependencies } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const allDeps = getTransitiveDependencies(workspace, '@myorg/app')
console.log(`@myorg/app transitively depends on ${allDeps.size} packages`)
for (const dep of allDeps) {
console.log(` - ${dep}`)
}Parameters
Returns
Set< string>Example
Get all transitive dependents of a package
// If lib-a depends on lib-utils and app-main depends on lib-a
// Then getTransitiveDependents('lib-utils') returns ['lib-a', 'app-main']Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Directory containing the project to check |
Returns
booleanExample
Check if a project has a changelog
import { hasChangelog } from '@hyperfrontend/versioning'
if (hasChangelog('./libs/my-lib')) {
console.log('Project has a changelog')
}Parameters
Returns
booleanExample
Check if one package transitively depends on another
import { discoverWorkspace, transitivelyDependsOn } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (transitivelyDependsOn(workspace, '@myorg/app', '@myorg/utils')) {
console.log('Bumping @myorg/utils will affect @myorg/app')
}◈ Interfaces
Properties
Properties
Properties
Properties
Properties
◆ Types
type DependencyGraph = ReadonlyMap< string, unknown>type DependencyType = "dependencies" | "devDependencies" | "peerDependencies" | "optionalDependencies"● Variables
Workspace types for configuration, projects, and query utilities.
ƒ Functions
Parameters
Returns
ProjectExample
Add an internal dependent to a project
import { discoverProject, addDependent } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
const updated = addDependent(project, '@myorg/new-app')
console.log('Dependents now:', updated.internalDependents)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateProjectOptions | Project properties |
Returns
ProjectExample
Create a new Project object
import { createProject, readPackageJson } from '@hyperfrontend/versioning'
const packageJson = readPackageJson('./libs/my-lib/package.json')
const project = createProject({
name: '@myorg/my-lib',
version: '1.0.0',
path: './libs/my-lib',
packageJsonPath: './libs/my-lib/package.json',
packageJson,
changelogPath: './libs/my-lib/CHANGELOG.md',
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateWorkspaceOptions | Workspace properties |
Returns
WorkspaceExample
Create a new workspace object
import { createWorkspace, createWorkspaceConfig, createProject } from '@hyperfrontend/versioning'
const projects = new Map([['@myorg/utils', createProject({ ... })]])
const workspace = createWorkspace({
root: '/path/to/workspace',
type: 'nx',
projects,
config: createWorkspaceConfig(),
dependencyGraph: new Map(),
reverseDependencyGraph: new Map(),
})Parameters
| Name | Type | Description |
|---|---|---|
§options? | Partial< | Partial configuration options |
Returns
WorkspaceConfigExample
Create a workspace configuration with defaults
import { createWorkspaceConfig } from '@hyperfrontend/versioning'
const config = createWorkspaceConfig({
patterns: ['packages/*', 'libs/*'],
exclude: ['node_modules', 'dist'],
})Parameters
Returns
booleanExample
Check if one project depends on another
import { discoverWorkspace, dependsOn } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (dependsOn(workspace, '@myorg/app', '@myorg/utils')) {
console.log('@myorg/app directly depends on @myorg/utils')
}Parameters
Returns
unknownExample
Get projects that the given project depends on
import { discoverWorkspace, getDependencies } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const deps = getDependencies(workspace, '@myorg/app')
console.log(`@myorg/app depends on:`, deps)Returns
numberExample
Get the dependency count for a project
import { discoverProject, getDependencyCount } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project) {
console.log(`${project.name} depends on ${getDependencyCount(project)} internal packages`)
}Returns
numberExample
Get the dependent count for a project
import { discoverProject, getDependentCount } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
console.log(`${project.name} is used by ${getDependentCount(project)} packages`)
}Parameters
Returns
unknownExample
Get projects that depend on a given project
import { discoverWorkspace, getDependents } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const dependents = getDependents(workspace, '@myorg/utils')
console.log(`Packages depending on @myorg/utils:`, dependents)Parameters
Returns
ProjectExample
Get a project by name from the workspace
import { discoverWorkspace, getProject } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const project = getProject(workspace, '@myorg/utils')
if (project) {
console.log(`Version: ${project.version}`)
}Returns
numberExample
Get the count of projects in the workspace
import { discoverWorkspace, getProjectCount } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
console.log(`Workspace contains ${getProjectCount(workspace)} projects`)Returns
unknownExample
Get all project names in the workspace
import { discoverWorkspace, getProjectNames } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const names = getProjectNames(workspace)
console.log(`Found ${names.length} projects:`, names)Returns
booleanExample
Check if a project has a changelog
import { discoverProject, hasChangelog } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && !hasChangelog(project)) {
console.log('Warning: No changelog found for', project.name)
}Returns
booleanExample
Check if a project has internal dependencies
import { discoverProject, hasInternalDependencies } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && hasInternalDependencies(project)) {
console.log(`${project.name} depends on:`, project.internalDependencies)
}Returns
booleanExample
Check if a project has internal dependents
import { discoverProject, hasInternalDependents } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project && hasInternalDependents(project)) {
console.log(`${project.name} is used by:`, project.internalDependents)
}Parameters
Returns
booleanExample
Check if a project exists in the workspace
import { discoverWorkspace, hasProject } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (hasProject(workspace, '@myorg/utils')) {
console.log('Package exists in workspace')
}Returns
booleanExample
Check if a project is private
import { discoverProject, isPrivate } from '@hyperfrontend/versioning'
const project = discoverProject('./apps/internal-app')
if (project && isPrivate(project)) {
console.log('Skipping private package')
}Returns
booleanExample
Check if a project is publishable
import { discoverProject, isPublishable } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && isPublishable(project)) {
console.log(`${project.name} can be published to npm`)
}Parameters
Returns
ProjectExample
Create a project copy with updated dependents
import { discoverProject, withDependents } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
const updated = withDependents(project, ['@myorg/app', '@myorg/web'])
console.log('Updated dependents:', updated.internalDependents)
}◈ Interfaces
Properties
Properties
Properties
readonly reverseDependencyGraph:ReadonlyMap< string, unknown>Reverse dependency graph (project name -> dependencies)Properties
◆ Types
type WorkspaceType = "nx" | "turbo" | "lerna" | "pnpm" | "npm" | "yarn" | "rush" | "unknown"● Variables
Workspace operations for cascade bumps, batch updates, and dependency reference management.
ƒ Functions
applyBumps( tree: Tree, workspace: Workspace, bumps: unknown, options: BatchUpdateOptions): BatchUpdateResult
Changes are buffered in the tree until
commitChanges() is called, enabling atomic commits and rollback on failure.Parameters
Returns
BatchUpdateResultExample
Apply planned bumps to workspace using VFS Tree
import { createTree, commitChanges } from '@hyperfrontend/project-scope'
import { applyBumps, calculateCascadeBumps } from '@hyperfrontend/versioning'
const tree = createTree(workspaceRoot)
const cascadeResult = calculateCascadeBumps(workspace, directBumps)
const updateResult = applyBumps(tree, workspace, cascadeResult.bumps)
if (updateResult.success) {
commitChanges(tree) // Atomic commit of all changes
console.log(`Updated ${updateResult.updated.length} packages`)
} else {
console.error('Some updates failed:', updateResult.failed)
// No commitChanges() call - changes are discarded
}calculateCascadeBumps( workspace: Workspace, directBumps: unknown, options: CascadeBumpOptions): CascadeBumpResult
When packages are directly bumped (e.g., due to commits), their dependents may also need version bumps. This function calculates all affected packages.
Parameters
| Name | Type | Description |
|---|---|---|
§workspace | Workspace | Workspace containing projects and dependency graph |
§directBumps | unknown | Packages with direct changes |
§options | CascadeBumpOptions | Configuration for cascade bump calculation(default: {}) |
Returns
CascadeBumpResultExample
Calculate cascade bumps for a workspace
import { calculateCascadeBumps } from '@hyperfrontend/versioning'
// If lib-utils is getting a minor bump
const result = calculateCascadeBumps(workspace, [
{ name: 'lib-utils', bumpType: 'minor' }
])
// result.bumps includes lib-utils and all packages that depend on it
for (const bump of result.bumps) {
console.log(`${bump.name}: ${bump.currentVersion} -> ${bump.nextVersion}`)
}calculateCascadeBumpsFromPackage( workspace: Workspace, packageName: string, bumpType: BumpType, options: CascadeBumpOptions): CascadeBumpResult
Parameters
Returns
CascadeBumpResultExample
Calculate cascade bumps from a single package
import { discoverWorkspace, calculateCascadeBumpsFromPackage } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const result = calculateCascadeBumpsFromPackage(workspace, '@myorg/utils', 'minor')
console.log(`${result.totalAffected} packages will be bumped`)
for (const bump of result.bumps) {
console.log(`${bump.name}: ${bump.currentVersion} -> ${bump.nextVersion}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§result | BatchUpdateResult | Result object from batch update operation |
Returns
stringExample
Create a summary of the batch update result
import { batchUpdateVersions, summarizeBatchUpdate } from '@hyperfrontend/versioning'
const result = batchUpdateVersions(workspace, updates)
console.log(summarizeBatchUpdate(result))
// Output:
// Successfully updated 3 package(s)
//
// Updated packages:
// @myorg/utils: 1.0.0 -> 1.1.0
// @myorg/core: 2.0.0 -> 2.1.0Parameters
| Name | Type | Description |
|---|---|---|
§result | CascadeBumpResult | Result object from cascade bump calculation |
Returns
stringExample
Get a summary of cascade bump results
import { calculateCascadeBumps, summarizeCascadeBumps } from '@hyperfrontend/versioning'
const result = calculateCascadeBumps(workspace, [{ name: '@myorg/utils', bumpType: 'patch' }])
console.log(summarizeCascadeBumps(result))
// Output:
// 3 package(s) affected:
// - 1 direct bump(s)
// - 2 cascade bump(s)Parameters
| Name | Type | Description |
|---|---|---|
§report | ValidationReport | Report object from workspace validation |
Returns
stringExample
Create a summary of the validation report
import { validateWorkspace, summarizeValidation } from '@hyperfrontend/versioning'
const report = validateWorkspace(workspace)
console.log(summarizeValidation(report))
// Output:
// Workspace validation passed
// 2 warning(s)updateDependencyReferencesInTree( tree: Tree, packageJsonPath: string, versionUpdates: Map< string, string>): void
Uses the
changeFile() pattern for cleaner transformation. Silently skips if the file doesn't exist.Parameters
Example
Update dependency version references using VFS Tree
import { updateDependencyReferencesInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function syncDeps(tree: Tree) {
const updates = new Map([
['@myorg/utils', '2.0.0'],
['@myorg/core', '3.1.0'],
])
updateDependencyReferencesInTree(tree, 'apps/my-app/package.json', updates)
}Parameters
Example
Update a package.json version using VFS Tree
import { updatePackageVersionInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function bumpVersion(tree: Tree) {
updatePackageVersionInTree(tree, 'libs/my-lib/package.json', '2.0.0')
}Returns
ValidationResultExample
Validate a single project
import { discoverProject, validateProject } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project) {
const result = validateProject(project)
if (!result.valid) {
console.error('Validation failed:', result.error)
}
}Returns
ValidationReportExample
Validate a workspace for common issues
import { validateWorkspace } from '@hyperfrontend/versioning'
const report = validateWorkspace(workspace)
if (!report.valid) {
console.error(`${report.errorCount} error(s) found`)
for (const result of report.results) {
if (!result.result.valid) {
console.error(` ${result.checkName}: ${result.result.error}`)
}
}
}◈ Interfaces
Properties
Properties
Properties
cascadeBumpType?:BumpTypeBump type for cascaded dependents. Default is 'patch' - dependents get a patch bump when their dependencies change.includeDevDependencies?:booleanWhether to include dev dependencies in cascade. Default is false - only production dependencies trigger cascades.includePeerDependencies?:booleanWhether to include peer dependencies in cascade. Default is true - peer dependency updates should cascade.Properties
Properties
Properties
Properties
Properties
Properties
Properties
◆ Types
type BumpReason = "direct" | "cascade" | "sync"● Variables
Related reading§
- Architecture
Architecture
How @hyperfrontend/versioning is put together, and why.
- How-to
How to publish release notes from your CHANGELOG.md
My release job has a version and a CHANGELOG.md, and every attempt to get one from the other is a regex over markdown headings that breaks the first time an entry carries a compare link, a scope, or a breaking marker.
- How-to
How to replace commitizen and commitlint with one package
Guided commit authoring and commit-message linting take four packages and two config files that have to be kept agreeing with each other, and the prompt still offers types the linter rejects.
- Package
@hyperfrontend/questions
Uses @hyperfrontend/questions to power the interactive cz authoring session
- Package
@hyperfrontend/project-scope
Works seamlessly with @hyperfrontend/project-scope for virtual file system operations
- Package
@hyperfrontend/cryptography
Looking for cryptographic utilities? See @hyperfrontend/cryptography
- Getting started
Getting Started
Set HyperFrontend up and embed a first feature.
- Architecture
Architecture Guide
How the packages fit together.

