/** * This file is part of helpers4. * Copyright (C) 2025 baxyz * SPDX-License-Identifier: LGPL-3.0-or-later */ /** * Options shared by `buildConventionalCommitRegex`, `parseConventionalCommit`, * and `isConventionalCommit` to constrain the accepted commit format. * * @since 2.0.0 */ interface ConventionalCommitOptions { /** * Allowed commit types (e.g. `['feat', 'fix', 'chore']`). When omitted, any * lowercase/uppercase letters word is accepted. */ readonly types?: readonly string[]; /** * Allowed scopes. When omitted, any non-whitespace, non-parenthesis content * is accepted as a scope. */ readonly scopes?: readonly string[]; /** * When `true`, the scope segment becomes mandatory. Defaults to `false`. */ readonly requireScope?: boolean; } /** * Parsed representation of a Conventional Commit message. * * @since 2.0.0 */ interface ParsedConventionalCommit { /** Commit type (e.g. `feat`, `fix`). */ readonly type: string; /** Optional scope, or `null` when absent. */ readonly scope: string | null; /** `true` when the commit declares a breaking change (via `!` or footer). */ readonly breaking: boolean; /** Subject line description (text after `: `). */ readonly description: string; /** Body paragraphs (excluding the subject and any trailing footer block). */ readonly body: string; /** Trailing footer block (lines like `BREAKING CHANGE: ...` or `Refs: #1`). */ readonly footer: string; } /** * Minimal commit shape consumed by `analyzeCommits`. Only the subject line is * mandatory; the body is scanned for a `BREAKING CHANGE` footer. * * @since 2.0.0 */ interface AnalyzableCommit { readonly subject: string; readonly body?: string; } /** * Bumping suggestion produced by `analyzeCommits`. * * @since 2.0.0 */ type CommitVersionBump = 'major' | 'minor' | 'patch'; /** * Aggregated result of `analyzeCommits`. * * @since 2.0.0 */ interface CommitAnalysis { /** Suggested semantic version bump for the supplied commits. */ readonly suggestedBump: CommitVersionBump; /** Human-readable explanation behind `suggestedBump`. */ readonly reason: string; /** `true` when at least one commit declares a breaking change. */ readonly hasBreakingChanges: boolean; /** `true` when at least one commit is a `feat`. */ readonly hasFeatures: boolean; /** `true` when at least one commit is a `fix`. */ readonly hasFixes: boolean; } /** * This file is part of helpers4. * Copyright (C) 2025 baxyz * SPDX-License-Identifier: LGPL-3.0-or-later */ /** * Analyses a list of commits to suggest a semantic version bump. * * Each commit is parsed via `parseConventionalCommit`. The body is also * scanned for `BREAKING CHANGE:` / `BREAKING-CHANGE:` markers. The bump rule * is: * * - any breaking change → `'major'` * - otherwise any `feat` → `'minor'` * - otherwise any `fix` → `'patch'` * - otherwise (non-empty list of non-conventional commits) → `'patch'` * - empty list → `'patch'` with reason "No commits to analyse" * * @param commits - Iterable of commits to analyse. Only `subject` is required. * @returns Aggregated analysis with the suggested bump and reason. * @example * analyzeCommits([{ subject: 'feat: add x' }, { subject: 'fix: bug' }]) * // => { suggestedBump: 'minor', hasFeatures: true, hasFixes: true, ... } * * analyzeCommits([{ subject: 'feat!: drop v1' }]) * // => { suggestedBump: 'major', hasBreakingChanges: true, ... } * @since 2.0.0 */ declare function analyzeCommits(commits: readonly AnalyzableCommit[]): CommitAnalysis; /** * This file is part of helpers4. * Copyright (C) 2025 baxyz * SPDX-License-Identifier: LGPL-3.0-or-later */ /** * Builds a regular expression matching the **subject line** of a Conventional * Commits message. * * The returned regex exposes four capture groups: * * 1. type * 2. scope (or `undefined` when absent) * 3. breaking marker (`'!'` or `undefined`) * 4. description * * @param options - Constrain accepted types/scopes and toggle scope requirement. * @returns Regex anchored on `^...$` matching the subject line only. * @example * buildConventionalCommitRegex().test('feat(api): add endpoint') // true * buildConventionalCommitRegex({ types: ['feat', 'fix'] }).test('chore: x') // false * buildConventionalCommitRegex({ requireScope: true }).test('feat: no scope') // false * @since 2.0.0 */ declare function buildConventionalCommitRegex(options?: ConventionalCommitOptions): RegExp; /** * This file is part of helpers4. * Copyright (C) 2025 baxyz * SPDX-License-Identifier: LGPL-3.0-or-later */ /** * Checks whether a commit message's subject line follows the Conventional * Commits format constrained by the given options. * * Only the first line is inspected — body and footer are ignored. * * @param message - Full commit message or just its subject line. * @param options - Optional constraints (allowed types/scopes, scope requirement). * @returns `true` when the subject line matches; `false` otherwise. * @example * isConventionalCommit('feat(api): add endpoint') // => true * isConventionalCommit('hello world') // => false * isConventionalCommit('chore: x', { types: ['feat', 'fix'] }) // => false * @since 2.0.0 */ declare function isConventionalCommit(message: string, options?: ConventionalCommitOptions): boolean; /** * This file is part of helpers4. * Copyright (C) 2025 baxyz * SPDX-License-Identifier: LGPL-3.0-or-later */ /** * Parses a Conventional Commits message into a structured object. * * The first line is matched against the regex produced by * `buildConventionalCommitRegex(options)`. The remaining content is split into * a `body` and an optional trailing `footer` block (lines matching * `Token: value` / `Token #value`, including `BREAKING CHANGE: ...`). * * @param message - Full commit message (subject + optional body/footer). * @param options - Optional constraints forwarded to the regex builder. * @returns Parsed commit object, or `null` when the subject is not conventional. * @example * parseConventionalCommit('feat(api)!: add v2\n\nDetails here') * // => { type: 'feat', scope: 'api', breaking: true, description: 'add v2', body: 'Details here', footer: '' } * * parseConventionalCommit('not conventional') // => null * @since 2.0.0 */ declare function parseConventionalCommit(message: string, options?: ConventionalCommitOptions): ParsedConventionalCommit | null; export { analyzeCommits, buildConventionalCommitRegex, isConventionalCommit, parseConventionalCommit }; export type { AnalyzableCommit, CommitAnalysis, CommitVersionBump, ConventionalCommitOptions, ParsedConventionalCommit };