@hyperfrontend/versioning/flow

flow/

Composable versioning workflow system for orchestrating release operations.

Overview

This module provides a declarative flow execution engine for versioning workflows. Flows are sequences of steps that perform operations like fetching registry versions, analyzing commits, calculating bumps, generating changelogs, and creating git commits/tags.

Architecture

Execution Options

FlowExecutionOptions

OptionDefaultDescription
showDifffalsePreview changes before committing to VFS
diffFormat'unified'Diff format: 'unified' or 'simple'
rollbackOnFailurefalseDiscard all VFS changes if any step fails

WriteChangelogStepOptions

OptionDefaultDescription
backupChangelogfalseBackup existing changelog before writing (uses tree.rename())

Usage

Basic Execution

import { createConventionalFlow, executeFlow } from '@hyperfrontend/versioning/flow'

const flow = createConventionalFlow({ dryRun: true })
const result = await executeFlow(flow, 'lib-utils', '/workspace')

console.log(result.summary)
// "Flow success in 234ms: 8 completed, 0 skipped, 0 failed. Version: 1.2.3 → 1.3.0"

Custom Flow

import {
  createFlow,
  createFetchRegistryStep,
  createAnalyzeCommitsStep,
  createCalculateBumpStep,
  executeFlow,
} from '@hyperfrontend/versioning/flow'

const minimalFlow = createFlow('minimal', 'Minimal Flow', [
  createFetchRegistryStep(),
  createAnalyzeCommitsStep(),
  createCalculateBumpStep(),
])

const result = await executeFlow(minimalFlow, 'lib-utils', '/workspace', {
  dryRun: true,
})

Custom Step

import { createStep, addStep, createConventionalFlow } from '@hyperfrontend/versioning/flow'

const notifyStep = createStep(
  'notify',
  'Send Notification',
  async (ctx) => {
    const { state } = ctx
    console.log(`Released ${state.nextVersion}`)
    return { status: 'success', message: 'Notification sent' }
  },
  { dependsOn: ['create-commit'] }
)

let flow = createConventionalFlow()
flow = addStep(flow, notifyStep)

Flow Execution Lifecycle

Configuration

OptionTypeDefaultDescription
presetstring'conventional'Flow preset name
releaseTypesstring[]['feat', 'fix', 'perf', 'revert']Types that trigger releases
minorTypesstring[]['feat']Types that trigger minor bumps
patchTypesstring[]['fix', 'perf', 'revert']Types that trigger patch bumps
skipGitbooleanfalseSkip git operations
skipTagbooleantrueSkip tag creation
skipChangelogbooleanfalseSkip changelog update
dryRunbooleanfalsePreview without changes
commitMessagestring'chore(${projectName}): release...'Commit message template
tagFormatstring'${projectName}@${version}'Tag name template
trackDepsbooleanfalseTrack dependency bumps
releaseBranchstring'main'Allowed release branch
firstReleaseVersionstring'0.1.0'Initial version for new packages
releaseAsstringundefinedForce bump type: 'major', 'minor', or 'patch'; the changelog then lists every commit type since the last release
maxCommitFallbacknumber500Max commits to analyze when no base available
repository*undefinedRepository config for compare URLs (see below)
changelogFileNamestring'CHANGELOG.md'Custom changelog filename
commitTypeToSectionobjectundefinedCustom commit type → section mapping (see below)

Repository Configuration

The repository option controls compare URL generation in changelog entries:

// Auto-detect from package.json or git remote
createVersionFlow('conventional', { repository: 'inferred' })

// Disable compare URLs
createVersionFlow('conventional', { repository: 'disabled' })

// Explicit configuration
createVersionFlow('conventional', {
  repository: {
    mode: 'explicit',
    repository: {
      platform: 'github',
      baseUrl: 'https://github.com/owner/repo',
    },
  },
})

// Inferred with custom order
createVersionFlow('conventional', {
  repository: {
    mode: 'inferred',
    inferenceOrder: ['git-remote', 'package-json'], // Try git first
  },
})

When repository is resolved, changelog entries include compare URLs:

## [1.2.0](https://github.com/owner/repo/compare/v1.1.0...v1.2.0) - 2026-03-17

Commit Type to Section Mapping

The commitTypeToSection option customizes how commit types map to changelog sections:

createVersionFlow('conventional', {
  commitTypeToSection: {
    // Override default mapping
    chore: 'other',

    // Add custom commit type
    wip: 'other',

    // Exclude type from changelog
    docs: null,
  },
})

Default mapping:

Unmapped types fall back to chores. Use null to exclude a type entirely.

Step Dependencies

Steps can declare dependencies using dependsOn:

const tagStep = createStep('create-tag', 'Create Tag', execute, {
  dependsOn: ['create-commit'], // Only runs after create-commit succeeds
})

The executor respects dependencies and skips steps when dependencies fail.

Error Handling

Steps can use continueOnError to allow the flow to continue:

const optionalStep = createStep('optional', 'Optional Step', execute, {
  continueOnError: true, // Flow continues even if this fails
})

Flow results include detailed step-by-step outcomes:

const result = await executeFlow(flow, project, root)

for (const step of result.steps) {
  console.log(`${step.stepName}: ${step.status}`)
  if (step.error) {
    console.error(step.error.message)
  }
}

See Also

  • commits/: Commit parsing for analyze-commits step
  • semver/: Version bumping for calculate-bump step
  • changelog/: Changelog generation step
  • git/: Git operations for commit/tag steps
  • registry/: Registry queries for fetch-registry step
  • workspace/: Workspace operations for batch flows
  • Main README: Package overview and quick start
  • ARCHITECTURE.md: Design principles and data flow

API Reference§

ƒ Functions

§function

createResolveRepositoryStep(): FlowStep

Creates the resolve-repository step.
This step resolves repository configuration for compare URL generation. It supports multiple resolution modes:
  • undefined or 'disabled': No-op, backward compatible default
  • 'inferred': Auto-detect from package.json or git remote
  • RepositoryConfig: Direct repository configuration provided
  • RepositoryResolution: Fine-grained control with mode and options
State updates:
  • repositoryConfig: Resolved repository configuration (if successful)
Returns
FlowStep
A FlowStep that resolves repository configuration
Example

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'
  }
})
§function

addStep(flow: VersionFlow, step: FlowStep): VersionFlow

Adds a step to a flow. Returns a new flow with the step appended.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to extend
§step
FlowStepThe step to add
Returns
VersionFlow
A new VersionFlow with the step added
Example

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 + 1
§function

createAnalyzeCommitsStep(): FlowStep

Creates the analyze-commits step.
This step:
  1. Uses publishedCommit from npm registry (set by fetch-registry step)
  2. Verifies the commit is reachable from current HEAD
  3. Gets all commits since that commit (or recent commits if first release/fallback)
  4. Parses each commit using conventional commit format
  5. Classifies commits based on scope filtering strategy
  6. Filters to only release-worthy commits that belong to this project
State updates:
  • 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
FlowStep
A FlowStep that analyzes commits
Example

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' }, ...]
§function

createBatchReleaseFlow(config?: Partial<FlowConfig>): VersionFlow

Creates a flow for releasing multiple packages independently.
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
NameTypeDescription
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A VersionFlow for batch independent releases
Example

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 commit
§function

createCalculateBumpStep(): FlowStep

Creates the calculate-bump step.
This step:
  1. Analyzes commit types and breaking changes
  2. Determines the appropriate bump level
  3. Calculates the next version
State updates:
  • bumpType: 'major' | 'minor' | 'patch' | 'none'
  • nextVersion: Calculated next version string
Returns
FlowStep
A FlowStep that calculates version bump
Example

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'
§function

createCascadeDependenciesStep(): FlowStep

Creates a step that updates dependent packages in a monorepo.
This step cascades version updates to packages that depend on the updated package.
Returns
FlowStep
A FlowStep that cascades dependency updates
Example

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)
§function

createChangelogOnlyFlow(config?: Partial<FlowConfig>): VersionFlow

Creates a changelog-only flow.
Only generates changelog, no version bumps or git operations.
Parameters
NameTypeDescription
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A VersionFlow that only updates changelog
Example

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)
§function

createCheckDependentBumpsStep(): FlowStep

Creates a step that checks for dependent package bumps.
In independent versioning, when a dependency is bumped, dependents may also need version bumps.
Returns
FlowStep
A FlowStep that checks dependent bumps
Example

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)
§function

createCheckIdempotencyStep(): FlowStep

Creates a step that checks for idempotency.
This step prevents redundant releases by checking if the calculated version is already published.
Returns
FlowStep
A FlowStep that checks idempotency
Example

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')
}
§function

createCombinedChangelogStep(): FlowStep

Creates a step that generates a combined changelog for all packages.
Returns
FlowStep
A FlowStep that creates a combined changelog
Example

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)
§function

createConventionalFlow(config?: Partial<FlowConfig>): VersionFlow

Creates a conventional flow.
This flow follows the standard conventional commits workflow:
  1. Fetch published version from registry
  2. Resolve repository configuration (for compare URLs)
  3. Analyze commits since last release
  4. Calculate version bump based on commit types
  5. Check if version already published (idempotency)
  6. Generate changelog entry (with compare URL if repository resolved)
  7. Update package.json version
  8. Write changelog to file
  9. Create git commit (optional)
  10. Create git tag (optional, typically after publish)
Parameters
NameTypeDescription
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A VersionFlow configured for conventional commits
Example

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')
§function

createDryRunFlow(preset: FlowPreset, config?: Partial<FlowConfig>): VersionFlow

Creates a dry-run version of a flow.
Convenience function that creates a flow with dryRun enabled.
Parameters
NameTypeDescription
§preset
FlowPresetThe preset to use(default: 'conventional')
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A VersionFlow configured for dry run
Example

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)
§function

createFailedResult(error: Error, message?: string): FlowStepResult

Creates a failed step result.
Parameters
NameTypeDescription
§error
ErrorError that caused the failure
§message?
stringOptional message (defaults to error.message)
Returns
FlowStepResult
A FlowStepResult with 'failed' status
Example

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')
}
§function

createFetchRegistryStep(): FlowStep

Creates the fetch-registry step.
This step:
  1. Queries the registry for the latest published version
  2. Reads the current version from package.json
  3. Determines if this is a first release
State updates:
  • publishedVersion: Latest version on registry (null if not published)
  • currentVersion: Version from local package.json
  • isFirstRelease: True if never published
Returns
FlowStep
A FlowStep that fetches registry information
Example

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')
}
§function

createFixedVersionFlow(version: string, config?: Partial<FlowConfig>): VersionFlow

Creates a fixed versioning flow.
Similar to synced, but uses a fixed version scheme where the version is explicitly provided rather than calculated from commits.
Parameters
NameTypeDescription
§version
stringThe fixed version to use
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A VersionFlow with a fixed version
Example

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'
§function

createFlow(id: string, name: string, steps: unknown, options: CreateFlowOptions): VersionFlow

Creates a version flow.
Parameters
NameTypeDescription
§id
stringFlow identifier
§name
stringHuman-readable flow name
§steps
unknownOrdered steps to execute
§options
CreateFlowOptionsOptional flow configuration(default: {})
Returns
VersionFlow
A VersionFlow object
Example

Creating a custom flow

const myFlow = createFlow(
  'custom',
  'Custom Release Flow',
  [fetchStep, analyzeStep, bumpStep],
  { description: 'My custom versioning workflow' }
)
§function

createGenerateChangelogStep(): FlowStep

Creates the generate-changelog step.
This step:
  1. Groups commits by type/section
  2. Creates changelog items from commits
  3. Assembles a complete changelog entry
State updates:
  • changelogEntry: The generated ChangelogEntry
Returns
FlowStep
A FlowStep that generates changelog
Example

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', 3
§function

createGitCommitStep(): FlowStep

Creates the create-commit step.
This step:
  1. Stages modified files
  2. Creates a commit with the configured message
State updates:
  • commitHash: Hash of the created commit
Returns
FlowStep
A FlowStep that creates a git commit
Example

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...'
§function

createIndependentFlow(config?: Partial<FlowConfig>): VersionFlow

Creates an independent versioning flow.
This flow is designed for monorepos where each package is versioned independently:
  1. Fetch published version from registry
  2. Analyze commits since last release
  3. Calculate version bump based on commit types
  4. Check for dependent package bumps (cascade)
  5. Check if version already published (idempotency)
  6. Generate changelog entry
  7. Update package.json version
  8. Cascade dependency updates
  9. Write changelog to file
  10. Create git commit
  11. Create git tag
Parameters
NameTypeDescription
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A VersionFlow configured for independent versioning
Example

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)
§function

createMinimalFlow(config?: Partial<FlowConfig>): VersionFlow

Creates a minimal flow for quick releases.
Skips changelog and tag creation.
Parameters
NameTypeDescription
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A minimal VersionFlow
Example

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)
§function

createNoopStep(id: string, name: string, message: string): FlowStep

Creates a step that succeeds immediately. Useful for placeholder or conditional steps.
Parameters
NameTypeDescription
§id
stringUnique identifier within the flow
§name
stringDisplay label shown during execution
§message
stringSuccess message(default: 'Step completed (no-op)')
Returns
FlowStep
A FlowStep that always succeeds
Example

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'
§function

createPushTagStep(): FlowStep

Creates a step that pushes the created tag to remote.
Returns
FlowStep
A FlowStep that pushes the git tag
Example

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'
§function

createSkippedResult(message: string): FlowStepResult

Creates a skipped step result.
Parameters
NameTypeDescription
§message
stringExplanation for why the step was skipped
Returns
FlowStepResult
A FlowStepResult with 'skipped' status
Example

Skipping a step conditionally

import { createSkippedResult } from '@hyperfrontend/versioning'

// In a step handler:
if (!config.enabled) {
  return createSkippedResult('Feature disabled in config')
}
§function

createStep(id: string, name: string, execute: StepExecutor, options: CreateStepOptions): FlowStep

Creates a flow step.
Parameters
NameTypeDescription
§id
stringUnique step identifier
§name
stringHuman-readable step name
§execute
StepExecutorStep executor function
§options
CreateStepOptionsOptional step configuration(default: {})
Returns
FlowStep
A FlowStep object
Example

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}`
    }
  }
)
§function

createSuccessResult(message: string, stateUpdates?: Partial<FlowState>): FlowStepResult

Creates a success step result.
Parameters
NameTypeDescription
§message
stringOutput text describing what the step accomplished
§stateUpdates?
Partial<FlowState>Optional state updates to apply after step completion
Returns
FlowStepResult
A FlowStepResult with 'success' status
Example

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']
})
§function

createSyncAllPackagesStep(): FlowStep

Creates a step that updates all workspace packages to the same version.
Returns
FlowStep
A FlowStep that syncs all package versions
Example

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)
§function

createSyncedFlow(config?: Partial<FlowConfig>): VersionFlow

Creates a synced versioning flow.
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:
  1. Fetch published version from registry
  2. Analyze commits across all packages
  3. Calculate version bump (highest needed)
  4. Check if version already published
  5. Sync all package versions
  6. Generate combined changelog
  7. Write changelog to root
  8. Create git commit
  9. Create single git tag
Parameters
NameTypeDescription
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A VersionFlow configured for synced versioning
Example

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}`)
§function

createTagStep(): FlowStep

Creates the create-tag step.
This step:
  1. Creates an annotated git tag
  2. Uses the configured tag format
State updates:
  • tagName: Name of the created tag
Returns
FlowStep
A FlowStep that creates a git tag
Example

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'
§function

createUpdatePackageStep(): FlowStep

Creates the update-packages step.
This step:
  1. Updates the version field in package.json
  2. Tracks the modified files
State updates:
  • modifiedFiles: Adds package.json to list
Returns
FlowStep
A FlowStep that updates package.json
Example

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']
§function

createVersionFlow(preset: FlowPreset, config?: Partial<FlowConfig>): VersionFlow

Creates a version flow from a preset.
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
NameTypeDescription
§preset
FlowPresetThe preset to use (default: 'conventional')(default: 'conventional')
§config?
Partial<FlowConfig>Optional configuration overrides
Returns
VersionFlow
A VersionFlow configured for the preset
Example

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')
§function

createWriteChangelogStep(): FlowStep

Creates the write-changelog step.
This step writes the generated changelog entry to CHANGELOG.md.
Returns
FlowStep
A FlowStep that writes changelog to file
Example

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'
§function

dryRun(flow: VersionFlow, projectName: string, workspaceRoot: string, options: Omit<ExecuteOptions, "dryRun">): Promise<FlowResult>

Executes a flow in dry-run mode.
Convenience wrapper that sets dryRun: true.
Parameters
NameTypeDescription
§flow
VersionFlowThe version flow to execute
§projectName
stringName of the project to version
§workspaceRoot
stringAbsolute path to workspace root
§options
Omit<ExecuteOptions, "dryRun">Execution options (dryRun forced to true)(default: {})
Returns
Promise<FlowResult>
Flow execution result (no actual changes made)
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 performed
§function

executeFlow(flow: VersionFlow, projectName: string, workspaceRoot: string, options: ExecuteOptions): Promise<FlowResult>

Executes a version flow.
This is the main entry point for running a versioning workflow. Steps are executed in order, with state accumulated between steps.
Parameters
NameTypeDescription
§flow
VersionFlowThe version flow to execute
§projectName
stringName of the project to version (e.g., 'lib-versioning')
§workspaceRoot
stringAbsolute path to workspace root
§options
ExecuteOptionsExecution options(default: {})
Returns
Promise<FlowResult>
Flow execution result
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"
§function

getAvailablePresets(): unknown

Gets the list of available presets.
Returns
unknown
Array of preset names
Example

Listing available presets

import { getAvailablePresets } from '@hyperfrontend/versioning'

const presets = getAvailablePresets()
// => ['conventional', 'independent', 'synced']
§function

getPresetDescription(preset: FlowPreset): string

Gets the description for a preset.
Parameters
NameTypeDescription
§preset
FlowPresetThe preset name
Returns
string
Human-readable description
Example

Getting a preset description

import { getPresetDescription } from '@hyperfrontend/versioning'

console.log(getPresetDescription('independent'))
// => 'Version packages independently with dependency tracking'
§function

getStep(flow: VersionFlow, stepId: string): FlowStep

Gets a step from a flow by ID.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to search
§stepId
stringThe step ID to find
Returns
FlowStep
The step if found, undefined otherwise
Example

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'
§function

hasStep(flow: VersionFlow, stepId: string): boolean

Checks if a flow has a specific step.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to check
§stepId
stringThe step ID to look for
Returns
boolean
True if the flow contains the step
Example

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'))
// => false
§function

insertStep(flow: VersionFlow, step: FlowStep, index: number): VersionFlow

Inserts a step at a specific position.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to modify
§step
FlowStepThe step to insert
§index
numberPosition to insert at (0-based)
Returns
VersionFlow
A new VersionFlow with the step inserted
Example

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'
§function

insertStepAfter(flow: VersionFlow, step: FlowStep, afterStepId: string): VersionFlow

Inserts a step after another step.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to modify
§step
FlowStepThe step to insert
§afterStepId
stringID of the step to insert after
Returns
VersionFlow
A new VersionFlow with the step inserted
Example

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'
§function

insertStepBefore(flow: VersionFlow, step: FlowStep, beforeStepId: string): VersionFlow

Inserts a step before another step.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to modify
§step
FlowStepThe step to insert
§beforeStepId
stringID of the step to insert before
Returns
VersionFlow
A new VersionFlow with the step inserted
Example

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'
§function

removeStep(flow: VersionFlow, stepId: string): VersionFlow

Removes a step from a flow by ID. Returns a new flow without the specified step.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to modify
§stepId
stringThe ID of the step to remove
Returns
VersionFlow
A new VersionFlow without the step
Example

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'))
// => undefined
§function

replaceStep(flow: VersionFlow, stepId: string, newStep: FlowStep): VersionFlow

Replaces a step in the flow.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to modify
§stepId
stringID of the step to replace
§newStep
FlowStepThe replacement step
Returns
VersionFlow
A new VersionFlow with the step replaced
Example

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 implementation
§function

validateFlow(flow: VersionFlow): unknown

Validates a flow before execution.
Checks for:
  • Duplicate step IDs
  • Invalid dependency references
  • Circular dependencies
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to validate
Returns
unknown
Array of validation errors (empty if valid)
Example

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)
}
// => []
§function

withConfig(flow: VersionFlow, config: Partial<FlowConfig>): VersionFlow

Updates the flow configuration.
Parameters
NameTypeDescription
§flow
VersionFlowThe flow to configure
§config
Partial<FlowConfig>Configuration updates to merge
Returns
VersionFlow
A new VersionFlow with updated config
Example

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

§interface

CreateFlowOptions

Options for creating a version flow.
Properties
§config?:FlowConfigFlow configuration
§description?:stringFlow description
§interface

CreateStepOptions

Options for creating a flow step.
Properties
§continueOnError?:booleanWhether step failure should fail the flow
§dependsOn?:unknownSteps that must complete before this one
§description?:stringStep description
§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.
§skipIf?:StepConditionCondition for skipping step
§interface

ExecuteOptions

Options for flow execution.
Properties
§diffFormat?:DiffFormatOutput format for diff: 'unified' (full patch) or 'summary' (stats only)
§dryRun?:booleanDry run - don't commit changes to disk
§git?:GitClientCustom GitClient instance (for testing)
§logger?:LoggerCustom logger (defaults to console)
§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.
§registry?:RegistryCustom Registry instance (for testing)
§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.
§showDiff?:booleanShow unified diff of changes before committing
§tree?:TreeCustom Tree instance (for testing)
§verbose?:booleanVerbose logging
§interface

FlowConfig

Flow configuration options.
Properties
§readonly allowPrerelease?:booleanAllow prerelease versions
§readonly backupChangelog?:booleanCreate a backup of the existing changelog before modification.
When enabled:
  1. Existing CHANGELOG.md is renamed to CHANGELOG.backup.md
  2. New changelog is written
  3. Backup is deleted on success
Useful for safety during changelog regeneration.
§readonly changelogFileName?:stringChangelog file name relative to project root.
§readonly commitMessage?:stringCustom commit message template
§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 dryRun?:booleanDry run mode - preview changes without applying
§readonly firstReleaseVersion?:stringBase version for first release
§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 minorTypes?:unknownCommit types that trigger minor bumps
§readonly patchTypes?:unknownCommit types that trigger patch bumps
§readonly prereleaseId?:stringPrerelease identifier (e.g., 'alpha', 'beta')
§readonly preset?:"conventional" | "independent" | "synced"Preset name or custom configuration
§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 releaseBranch?:stringBranch allowed for releases
§readonly releaseTypes?:unknownCommit types that trigger releases
§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 remote
  • RepositoryResolution: Fine-grained control with explicit mode and options
  • RepositoryConfig: 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.
§readonly skipChangelog?:booleanSkip changelog update
§readonly skipGit?:booleanSkip git operations
§readonly skipTag?:booleanSkip tag creation
§readonly tagFormat?:stringCustom tag format
§readonly trackDeps?:booleanTrack dependencies for cascade bumps
§interface

FlowContext

Execution context passed to each step. Contains all resources and accumulated state.
Note: state is mutable within context to allow accumulation, but individual FlowState objects are immutable.
Properties
§readonly config:FlowConfigFlow configuration
§readonly git:GitClientGit client
§readonly logger:LoggerLogger instance
§readonly packageName:stringPackage name from package.json
§readonly projectName:stringTarget project name
§readonly projectRoot:stringProject root path
§readonly registry:RegistryRegistry client
§state:FlowStateAccumulated state from previous steps (mutable reference)
§readonly tree:TreeVirtual file system tree
§readonly workspaceRoot:stringWorkspace root path
§interface

FlowResult

Complete result of flow execution.
Properties
§readonly diffs?:unknownDetailed diffs for pending changes. Populated when showDiff: true is passed to executeFlow. Provides unified diff format for programmatic access.
§readonly duration:numberDuration in milliseconds
§readonly modifiedFiles?:unknownFiles that were modified (or would be in dry-run)
§readonly state:FlowStateFinal accumulated state
§readonly status:FlowStatusOverall flow outcome
§readonly steps:unknownResults for each step
§readonly summary:stringSummary message
§interface

FlowState

Accumulated state during flow execution. Each step can read previous state and contribute updates.
Properties
§readonly bumpType?:BumpTypeBump type (major/minor/patch/none)
§readonly changelogEntry?:ChangelogEntryGenerated changelog entry
§readonly classificationResult?:ClassificationResultClassification result with source attribution (when scope filtering enabled)
§readonly commitHash?:stringCreated git commit hash
§readonly commits?:unknownAnalyzed commits since last release
§readonly currentVersion?:stringCurrent/local version from package.json
§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 isFirstRelease?:booleanWhether this is a first release (no prior versions)
§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
§readonly modifiedFiles?:unknownFiles modified during flow execution
§readonly nextVersion?:stringCalculated next version
§readonly publishedCommit?:stringGit commit hash of the last published version
§readonly publishedVersion?:stringPublished version on registry (null if never published)
§readonly repositoryConfig?:RepositoryConfigRepository configuration for compare URL generation
§readonly tagName?:stringCreated git tag name
§interface

FlowStep

A single step in a version flow.
Steps are pure functions that:
  1. Read from context (state, config, services)
  2. Perform work (possibly with side effects via services)
  3. Return state updates
Properties
§readonly continueOnError?:booleanWhether step failure should fail the flow
§readonly dependsOn?:unknownSteps that must complete before this one
§readonly description?:stringOptional step description
§readonly execute:StepExecutorStep function to execute
§readonly id:stringStep identifier (unique within flow)
§readonly name:stringHuman-readable step name
§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.
§readonly skipIf?:StepConditionCondition for skipping step
§interface

FlowStepResult

Result of a single step execution.
Properties
§readonly error?:ErrorError if failed
§readonly message?:stringDescriptive message
§readonly stateUpdates?:Partial<FlowState>State updates from this step
§readonly status:"success" | "skipped" | "failed"Step outcome
§interface

FlowStepResultWithId

Step result with step identification.
Properties
§readonly error?:ErrorError if failed
§readonly message?:stringDescriptive message
§readonly stateUpdates?:Partial<FlowState>State updates from this step
§readonly status:"success" | "skipped" | "failed"Step outcome
§readonly stepId:stringStep identifier
§readonly stepName:stringStep display name
§interface

Logger

Logger interface with level-specific methods and level control
Properties
§channel:ChannelFnReturns a sub-logger that prepends [prefix] to every message.
§debug:DebugLevelFnDebug-level output
§error:ErrorLevelFnError-level output
§getLogLevel:GetLogLevelGets the current log level
§info:InfoLevelFnInfo-level output
§log:LogLevelFnStandard log output
§setLogLevel:SetLogLevelSets the current log level
§timed:TimedFnWraps a sync call with timing. Logs completion or failure with elapsed ms.
§timedAsync:TimedAsyncFnWraps a promise-returning call with timing. Dumps stack trace on error to debug log.
§warn:WarnLevelFnWarning-level output
§interface

VersionFlow

A complete version flow definition.
Flows are immutable configurations that define:
  1. What steps to execute
  2. In what order
  3. With what configuration
Properties
§readonly config:FlowConfigFlow-level configuration
§readonly description?:stringFlow description
§readonly id:stringFlow identifier
§readonly name:stringHuman-readable flow name
§readonly steps:unknownOrdered steps to execute

◆ Types

§type

FlowPreset

Supported flow presets.
type FlowPreset = "conventional" | "independent" | "synced"
§type

FlowStatus

Overall flow execution status.
type FlowStatus = "success" | "partial" | "failed" | "skipped"
§type

StepCondition

Step skip condition function type. Returns true if the step should be skipped.
type StepCondition = (context: FlowContext) => boolean
§type

StepExecutor

Step executor function type. Takes flow context and returns a step result promise.
type StepExecutor = (context: FlowContext) => Promise<FlowStepResult>

● Variables

§type

RESOLVE_REPOSITORY_STEP_ID

§type

ANALYZE_COMMITS_STEP_ID

§type

CALCULATE_BUMP_STEP_ID

§type

CONVENTIONAL_FLOW_CONFIG

Default configuration for conventional flow.
§type

CREATE_COMMIT_STEP_ID

§type

CREATE_TAG_STEP_ID

§type

DEFAULT_CHANGELOG_FILENAME

Default changelog filename.
§type

DEFAULT_COMMIT_TYPE_TO_SECTION

Maps conventional commit types to changelog section types.
§type

DEFAULT_FLOW_CONFIG

Default flow configuration values.
§type

FETCH_REGISTRY_STEP_ID

§type

GENERATE_CHANGELOG_STEP_ID

§type

INDEPENDENT_FLOW_CONFIG

Default configuration for independent flow.
§type

SYNCED_FLOW_CONFIG

Default configuration for synced flow.
§type

UPDATE_PACKAGES_STEP_ID