import { Logger } from '../../_dependencies/@hyperfrontend/logging/index.js'; export { Logger } from '../../_dependencies/@hyperfrontend/logging/index.js'; import { Tree, FileDiff } from '../../_dependencies/@hyperfrontend/project-scope/vfs/index.js'; import { ChangelogSectionType, ChangelogEntry } from '../../changelog/models'; import { InfrastructureConfig, InfrastructureMatcher, ClassificationResult } from '../../commits/classify'; import { ConventionalCommit } from '../../commits/models'; import { GitClient } from '../../git'; import { Registry } from '../../registry/models'; import { RepositoryResolution, RepositoryConfig } from '../../repository/models'; import { BumpType } from '../../semver/models'; /** * Default changelog filename. */ declare const DEFAULT_CHANGELOG_FILENAME = "CHANGELOG.md"; /** * Accumulated state during flow execution. * Each step can read previous state and contribute updates. */ interface FlowState { /** Current/local version from package.json */ readonly currentVersion?: string; /** Published version on registry (null if never published) */ readonly publishedVersion?: string | null; /** Git commit hash of the last published version */ readonly publishedCommit?: string | null; /** Calculated next version */ readonly nextVersion?: string; /** Bump type (major/minor/patch/none) */ readonly bumpType?: BumpType; /** Analyzed commits since last release */ readonly commits?: readonly ConventionalCommit[]; /** Classification result with source attribution (when scope filtering enabled) */ readonly classificationResult?: ClassificationResult; /** * The 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 effectiveBaseCommit?: string | null; /** Generated changelog entry */ readonly changelogEntry?: ChangelogEntry; /** Files modified during flow execution */ readonly modifiedFiles?: readonly string[]; /** Created git commit hash */ readonly commitHash?: string; /** Created git tag name */ readonly tagName?: string; /** Whether this is a first release (no prior versions) */ readonly isFirstRelease?: boolean; /** Repository configuration for compare URL generation */ readonly repositoryConfig?: RepositoryConfig; /** * Whether 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 isPendingPublication?: boolean; /** Additional custom state for extensibility */ readonly [key: string]: unknown; } /** * Strategy for filtering commits to a project's changelog. * * - `'hybrid'`: Use scope + file validation (default, recommended) * - `'scope-only'`: Only use scope matching (fast, for disciplined teams) * - `'file-only'`: Only use file-based filtering (for non-scoped repos) * - `'inferred'`: Auto-detect from commit history */ type ScopeFilteringStrategy = 'hybrid' | 'scope-only' | 'file-only' | 'inferred'; /** * Scope filtering configuration. * * By default, hybrid filtering is enabled universally. * Configuration exists for edge cases and external codebases. */ interface ScopeFilteringConfig { /** * Filtering strategy. * * @default 'hybrid' */ readonly strategy?: ScopeFilteringStrategy; /** * Additional scopes to include as direct commits. * Use for shared library scopes that should appear in multiple projects. * * @example ['shared-utils', 'common'] */ readonly includeScopes?: readonly string[]; /** * Scopes to explicitly exclude even if files match. * Use for infrastructure scopes that affect build but aren't "changes". * * @example ['deps', 'release'] */ readonly excludeScopes?: readonly string[]; /** * Include commits that touch dependency packages. * When true, changes to dependencies appear as indirect commits. * * @default false */ readonly trackDependencyChanges?: boolean; /** * Project name prefixes stripped for scope matching. * Example: ['lib-', 'pkg-'] means 'lib-auth' matches scope 'auth'. * * @default ['lib-', 'app-', 'e2e-', 'tool-', 'plugin-', 'feature-', 'package-'] */ readonly projectPrefixes?: readonly string[]; /** * Infrastructure tracking configuration. * * Defines how to detect commits that affect build/tooling infrastructure. * Supports multiple detection methods: * - `paths`: File paths to track via git queries * - `scopes`: Conventional commit scopes to match * - `matcher`: Custom matching logic * * All methods are combined with OR logic. * * @example * // Simple path-based * infrastructure: { paths: ['tools/', '.github/workflows/'] } * * @example * // Scope-based * infrastructure: { scopes: ['ci', 'build', 'tooling'] } * * @example * // Composable matcher * import { anyOf, scopeMatcher, scopePrefixMatcher } from '@hyperfrontend/versioning' * infrastructure: { * paths: ['tools/'], * matcher: anyOf( * scopeMatcher(['ci', 'build']), * scopePrefixMatcher(['tool-']) * ) * } */ readonly infrastructure?: InfrastructureConfig; /** * Custom infrastructure matcher function. * * Provides full programmatic control over infrastructure detection. * Takes precedence over `infrastructure` config if both provided. * * @example * infrastructureMatcher: (ctx) => { * // Match CI scopes * if (ctx.scope.includes('ci') || ctx.scope.includes('build')) return true * // Match tool-prefixed scopes * if (ctx.scope.some((s) => s.startsWith('tool-'))) return true * // Match workspace commits during major refactors * if (ctx.message.includes('[infra]')) return true * return false * } */ readonly infrastructureMatcher?: InfrastructureMatcher; } /** * Flow configuration options. */ interface FlowConfig { /** Preset name or custom configuration */ readonly preset?: 'conventional' | 'independent' | 'synced'; /** Commit types that trigger releases */ readonly releaseTypes?: readonly string[]; /** Commit types that trigger minor bumps */ readonly minorTypes?: readonly string[]; /** Commit types that trigger patch bumps */ readonly patchTypes?: readonly string[]; /** Skip git operations */ readonly skipGit?: boolean; /** Skip tag creation */ readonly skipTag?: boolean; /** Skip changelog update */ readonly skipChangelog?: boolean; /** Dry run mode - preview changes without applying */ readonly dryRun?: boolean; /** Custom commit message template */ readonly commitMessage?: string; /** Custom tag format */ readonly tagFormat?: string; /** Track dependencies for cascade bumps */ readonly trackDeps?: boolean; /** Branch allowed for releases */ readonly releaseBranch?: string; /** Base version for first release */ readonly firstReleaseVersion?: string; /** Allow prerelease versions */ readonly allowPrerelease?: boolean; /** Prerelease identifier (e.g., 'alpha', 'beta') */ readonly prereleaseId?: string; /** * 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 releaseAs?: 'major' | 'minor' | 'patch'; /** * Maximum 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. * * @default 500 */ readonly maxCommitFallback?: number; /** * Repository 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 repository?: 'disabled' | 'inferred' | RepositoryResolution | RepositoryConfig; /** * Commit 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 scopeFiltering?: ScopeFilteringConfig; /** * Changelog file name relative to project root. * * @default 'CHANGELOG.md' */ readonly changelogFileName?: string; /** * Custom mapping from commit type to changelog section. * Merged with defaults; use `null` to exclude a type from changelog. */ readonly commitTypeToSection?: Partial>; /** * Create 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. * * @default false */ readonly backupChangelog?: boolean; } /** * Default flow configuration values. */ declare const DEFAULT_FLOW_CONFIG: Required> & Pick; /** * 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. */ interface FlowContext { /** Workspace root path */ readonly workspaceRoot: string; /** Target project name */ readonly projectName: string; /** Project root path */ readonly projectRoot: string; /** Package name from package.json */ readonly packageName: string; /** Virtual file system tree */ readonly tree: Tree; /** Registry client */ readonly registry: Registry; /** Git client */ readonly git: GitClient; /** Logger instance */ readonly logger: Logger; /** Flow configuration */ readonly config: FlowConfig; /** Accumulated state from previous steps (mutable reference) */ state: FlowState; } /** * Result of a single step execution. */ interface FlowStepResult { /** Step outcome */ readonly status: 'success' | 'skipped' | 'failed'; /** State updates from this step */ readonly stateUpdates?: Partial; /** Descriptive message */ readonly message?: string; /** Error if failed */ readonly error?: Error; } /** * Step result with step identification. */ interface FlowStepResultWithId extends FlowStepResult { /** Step identifier */ readonly stepId: string; /** Step display name */ readonly stepName: string; } /** * Overall flow execution status. */ type FlowStatus = 'success' | 'partial' | 'failed' | 'skipped'; /** * Information about a file change. */ interface FileChangeInfo { /** Relative path from workspace root */ readonly path: string; /** Type of change */ readonly changeType: 'CREATE' | 'UPDATE' | 'DELETE'; } /** * Complete result of flow execution. */ interface FlowResult { /** Overall flow outcome */ readonly status: FlowStatus; /** Results for each step */ readonly steps: readonly FlowStepResultWithId[]; /** Final accumulated state */ readonly state: FlowState; /** Duration in milliseconds */ readonly duration: number; /** Summary message */ readonly summary: string; /** Files that were modified (or would be in dry-run) */ readonly modifiedFiles?: readonly FileChangeInfo[]; /** * Detailed diffs for pending changes. * Populated when `showDiff: true` is passed to executeFlow. * Provides unified diff format for programmatic access. */ readonly diffs?: readonly FileDiff[]; } /** * Step executor function type. * Takes flow context and returns a step result promise. */ type StepExecutor = (context: FlowContext) => Promise; /** * Step skip condition function type. * Returns true if the step should be skipped. */ type StepCondition = (context: FlowContext) => boolean; /** * 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 */ interface FlowStep { /** Step identifier (unique within flow) */ readonly id: string; /** Human-readable step name */ readonly name: string; /** Optional step description */ readonly description?: string; /** Step function to execute */ readonly execute: StepExecutor; /** Condition for skipping step */ readonly skipIf?: StepCondition; /** Whether step failure should fail the flow */ readonly continueOnError?: boolean; /** Steps that must complete before this one */ readonly dependsOn?: readonly string[]; /** * Whether 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 requiresDiskFlush?: boolean; } /** * Options for creating a flow step. */ interface CreateStepOptions { /** Step description */ description?: string; /** Condition for skipping step */ skipIf?: StepCondition; /** Whether step failure should fail the flow */ continueOnError?: boolean; /** Steps that must complete before this one */ dependsOn?: readonly string[]; /** * Whether 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. */ requiresDiskFlush?: boolean; } /** * Creates a flow step. * * @param id - Unique step identifier * @param name - Human-readable step name * @param execute - Step executor function * @param options - Optional step configuration * @returns A FlowStep object * * @example Creating a custom fetch step * ```typescript * 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}` * } * } * ) * ``` */ declare function createStep(id: string, name: string, execute: StepExecutor, options?: CreateStepOptions): FlowStep; /** * Creates a step that succeeds immediately. * Useful for placeholder or conditional steps. * * @param id - Unique identifier within the flow * @param name - Display label shown during execution * @param message - Success message * @returns A FlowStep that always succeeds * * @example Creating a placeholder step * ```typescript * import { createNoopStep, executeStep } from '@hyperfrontend/versioning' * * const step = createNoopStep('placeholder', 'Placeholder Step') * const result = await executeStep(step, context) * * console.log(result.status) * // => 'success' * ``` */ declare function createNoopStep(id: string, name: string, message?: string): FlowStep; /** * Creates a skipped step result. * * @param message - Explanation for why the step was skipped * @returns A FlowStepResult with 'skipped' status * * @example Skipping a step conditionally * ```typescript * import { createSkippedResult } from '@hyperfrontend/versioning' * * // In a step handler: * if (!config.enabled) { * return createSkippedResult('Feature disabled in config') * } * ``` */ declare function createSkippedResult(message: string): FlowStepResult; /** * Creates a success step result. * * @param message - Output text describing what the step accomplished * @param stateUpdates - Optional state updates to apply after step completion * @returns A FlowStepResult with 'success' status * * @example Returning a success result with state updates * ```typescript * import { createSuccessResult } from '@hyperfrontend/versioning' * * // In a step handler: * return createSuccessResult('Updated 3 files', { * modifiedFiles: ['/path/a.ts', '/path/b.ts', '/path/c.ts'] * }) * ``` */ declare function createSuccessResult(message: string, stateUpdates?: FlowStepResult['stateUpdates']): FlowStepResult; /** * Creates a failed step result. * * @param error - Error that caused the failure * @param message - Optional message (defaults to error.message) * @returns A FlowStepResult with 'failed' status * * @example Handling step execution errors * ```typescript * import { createFailedResult } from '@hyperfrontend/versioning' * * // In a step handler: * try { * await doOperation() * } catch (err) { * return createFailedResult(err as Error, 'Operation failed') * } * ``` */ declare function createFailedResult(error: Error, message?: string): FlowStepResult; /** * A complete version flow definition. * * Flows are immutable configurations that define: * 1. What steps to execute * 2. In what order * 3. With what configuration */ interface VersionFlow { /** Flow identifier */ readonly id: string; /** Human-readable flow name */ readonly name: string; /** Flow description */ readonly description?: string; /** Ordered steps to execute */ readonly steps: readonly FlowStep[]; /** Flow-level configuration */ readonly config: FlowConfig; } /** * Options for creating a version flow. */ interface CreateFlowOptions { /** Flow description */ description?: string; /** Flow configuration */ config?: FlowConfig; } /** * Creates a version flow. * * @param id - Flow identifier * @param name - Human-readable flow name * @param steps - Ordered steps to execute * @param options - Optional flow configuration * @returns A VersionFlow object * * @example Creating a custom flow * ```typescript * const myFlow = createFlow( * 'custom', * 'Custom Release Flow', * [fetchStep, analyzeStep, bumpStep], * { description: 'My custom versioning workflow' } * ) * ``` */ declare function createFlow(id: string, name: string, steps: readonly FlowStep[], options?: CreateFlowOptions): VersionFlow; /** * Adds a step to a flow. * Returns a new flow with the step appended. * * @param flow - The flow to extend * @param step - The step to add * @returns A new VersionFlow with the step added * * @example Adding a custom step to a flow * ```typescript * 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 * ``` */ declare function addStep(flow: VersionFlow, step: FlowStep): VersionFlow; /** * Removes a step from a flow by ID. * Returns a new flow without the specified step. * * @param flow - The flow to modify * @param stepId - The ID of the step to remove * @returns A new VersionFlow without the step * * @example Removing a step from a flow * ```typescript * 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 * ``` */ declare function removeStep(flow: VersionFlow, stepId: string): VersionFlow; /** * Inserts a step at a specific position. * * @param flow - The flow to modify * @param step - The step to insert * @param index - Position to insert at (0-based) * @returns A new VersionFlow with the step inserted * * @example Inserting a step at a specific position * ```typescript * 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' * ``` */ declare function insertStep(flow: VersionFlow, step: FlowStep, index: number): VersionFlow; /** * Inserts a step after another step. * * @param flow - The flow to modify * @param step - The step to insert * @param afterStepId - ID of the step to insert after * @returns A new VersionFlow with the step inserted * * @example Inserting a step after another step * ```typescript * 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' * ``` */ declare function insertStepAfter(flow: VersionFlow, step: FlowStep, afterStepId: string): VersionFlow; /** * Inserts a step before another step. * * @param flow - The flow to modify * @param step - The step to insert * @param beforeStepId - ID of the step to insert before * @returns A new VersionFlow with the step inserted * * @example Inserting a step before another step * ```typescript * 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' * ``` */ declare function insertStepBefore(flow: VersionFlow, step: FlowStep, beforeStepId: string): VersionFlow; /** * Replaces a step in the flow. * * @param flow - The flow to modify * @param stepId - ID of the step to replace * @param newStep - The replacement step * @returns A new VersionFlow with the step replaced * * @example Replacing a step with a custom implementation * ```typescript * 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 * ``` */ declare function replaceStep(flow: VersionFlow, stepId: string, newStep: FlowStep): VersionFlow; /** * Updates the flow configuration. * * @param flow - The flow to configure * @param config - Configuration updates to merge * @returns A new VersionFlow with updated config * * @example Updating flow configuration * ```typescript * import { withConfig, createConventionalFlow } from '@hyperfrontend/versioning' * * const flow = createConventionalFlow() * const dryRunFlow = withConfig(flow, { dryRun: true }) * * // Flow now runs in dry-run mode * ``` */ declare function withConfig(flow: VersionFlow, config: Partial): VersionFlow; /** * Gets a step from a flow by ID. * * @param flow - The flow to search * @param stepId - The step ID to find * @returns The step if found, undefined otherwise * * @example Getting a step by ID * ```typescript * import { getStep, createConventionalFlow } from '@hyperfrontend/versioning' * * const flow = createConventionalFlow() * const step = getStep(flow, 'analyze-commits') * * console.log(step?.name) * // => 'Analyze Commits' * ``` */ declare function getStep(flow: VersionFlow, stepId: string): FlowStep | undefined; /** * Checks if a flow has a specific step. * * @param flow - The flow to check * @param stepId - The step ID to look for * @returns True if the flow contains the step * * @example Checking if a flow has a step * ```typescript * import { hasStep, createConventionalFlow } from '@hyperfrontend/versioning' * * const flow = createConventionalFlow() * * console.log(hasStep(flow, 'create-commit')) * // => true * console.log(hasStep(flow, 'custom-step')) * // => false * ``` */ declare function hasStep(flow: VersionFlow, stepId: string): boolean; export { DEFAULT_CHANGELOG_FILENAME, DEFAULT_FLOW_CONFIG, addStep, createFailedResult, createFlow, createNoopStep, createSkippedResult, createStep, createSuccessResult, getStep, hasStep, insertStep, insertStepAfter, insertStepBefore, removeStep, replaceStep, withConfig }; export type { CreateFlowOptions, CreateStepOptions, FlowConfig, FlowContext, FlowResult, FlowState, FlowStatus, FlowStep, FlowStepResult, FlowStepResultWithId, StepCondition, StepExecutor, VersionFlow };