@hyperfrontend/versioning/flowflow/
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
| Option | Default | Description |
|---|---|---|
showDiff | false | Preview changes before committing to VFS |
diffFormat | 'unified' | Diff format: 'unified' or 'simple' |
rollbackOnFailure | false | Discard all VFS changes if any step fails |
WriteChangelogStepOptions
| Option | Default | Description |
|---|---|---|
backupChangelog | false | Backup 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
| Option | Type | Default | Description |
|---|---|---|---|
preset | string | 'conventional' | Flow preset name |
releaseTypes | string[] | ['feat', 'fix', 'perf', 'revert'] | Types that trigger releases |
minorTypes | string[] | ['feat'] | Types that trigger minor bumps |
patchTypes | string[] | ['fix', 'perf', 'revert'] | Types that trigger patch bumps |
skipGit | boolean | false | Skip git operations |
skipTag | boolean | true | Skip tag creation |
skipChangelog | boolean | false | Skip changelog update |
dryRun | boolean | false | Preview without changes |
commitMessage | string | 'chore(${projectName}): release...' | Commit message template |
tagFormat | string | '${projectName}@${version}' | Tag name template |
trackDeps | boolean | false | Track dependency bumps |
releaseBranch | string | 'main' | Allowed release branch |
firstReleaseVersion | string | '0.1.0' | Initial version for new packages |
releaseAs | string | undefined | Force bump type: 'major', 'minor', or 'patch'; the changelog then lists every commit type since the last release |
maxCommitFallback | number | 500 | Max commits to analyze when no base available |
repository | * | undefined | Repository config for compare URLs (see below) |
changelogFileName | string | 'CHANGELOG.md' | Custom changelog filename |
commitTypeToSection | object | undefined | Custom 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:
| Commit Type | Section |
|---|---|
feat | features |
fix | fixes |
perf | performance |
docs | documentation |
refactor | refactoring |
revert | other |
build | build |
ci | ci |
test | tests |
chore | chores |
style | other |
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
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>