# @helpers4/commit

> Tree-shakable TypeScript utility functions for the `commit` domain.
> Package: `@helpers4/commit` — Version: 2.0.0
> License: LGPL-3.0-or-later

## Installation

```sh
npm install @helpers4/commit
# or
pnpm add @helpers4/commit
```

## Usage

```typescript
import { analyzeCommits, buildConventionalCommitRegex, isConventionalCommit, ... } from '@helpers4/commit';
```

## Functions

| Function | Description |
|---|---|
| `analyzeCommits` | Analyses a list of commits to suggest a semantic version bump.  Each commit is parsed via `parseConv |
| `buildConventionalCommitRegex` | Builds a regular expression matching the **subject line** of a Conventional Commits message.  The re |
| `isConventionalCommit` | Checks whether a commit message's subject line follows the Conventional Commits format constrained b |
| `parseConventionalCommit` | Parses a Conventional Commits message into a structured object.  The first line is matched against t |

---

## API Reference

### `analyzeCommits`

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"

```typescript
import { analyzeCommits } from '@helpers4/commit';

analyzeCommits(commits: readonly AnalyzableCommit[]): CommitAnalysis
```

**Parameters:**

- `commits: readonly AnalyzableCommit[]` — Iterable of commits to analyse. Only `subject` is required.

**Returns:** `CommitAnalysis` — Aggregated analysis with the suggested bump and reason.

**Examples:**

*Suggest a semver bump from a list of commits*

Walks through commits and suggests `major`, `minor`, or `patch` based on Conventional Commits.

```typescript
analyzeCommits([
  { subject: 'feat: add login' },
  { subject: 'fix: handle null' },
])
// => { suggestedBump: 'minor', hasFeatures: true, hasFixes: true, ... }
```

*Promote to major on breaking change*

A `!` marker or a `BREAKING CHANGE:` footer always promotes the suggestion to `major`.

```typescript
analyzeCommits([{ subject: 'feat!: drop v1 API' }]).suggestedBump
// => 'major'
```

---

### `buildConventionalCommitRegex`

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

```typescript
import { buildConventionalCommitRegex } from '@helpers4/commit';

buildConventionalCommitRegex(options: ConventionalCommitOptions): RegExp
```

**Parameters:**

- `options: ConventionalCommitOptions` (default: `{}`) — Constrain accepted types/scopes and toggle scope requirement.

**Returns:** `RegExp` — Regex anchored on `^...$` matching the subject line only.

**Examples:**

*Match the default Conventional Commits format*

Returns a regex matching `type(scope)?!?: description` on the subject line.

```typescript
const regex = buildConventionalCommitRegex();
regex.test('feat(api): add endpoint') // => true
regex.test('not a commit') // => false
```

*Restrict accepted types and require a scope*

Constrain accepted types and force the scope segment to be present.

```typescript
const regex = buildConventionalCommitRegex({
  types: ['feat', 'fix'],
  requireScope: true,
});
regex.test('feat(api): x') // => true
regex.test('feat: missing scope') // => false
regex.test('chore(api): wrong type') // => false
```

---

### `isConventionalCommit`

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.

```typescript
import { isConventionalCommit } from '@helpers4/commit';

isConventionalCommit(message: string, options?: ConventionalCommitOptions): boolean
```

**Parameters:**

- `message: string` — Full commit message or just its subject line.
- `options?: ConventionalCommitOptions` — Optional constraints (allowed types/scopes, scope requirement).

**Returns:** `boolean` — `true` when the subject line matches; `false` otherwise.

**Examples:**

*Validate a commit subject*

Returns `true` when the first line follows the Conventional Commits format.

```typescript
isConventionalCommit('feat(api): add endpoint') // => true
isConventionalCommit('hello world') // => false
```

*Restrict accepted types*

Reject any commit whose type is not in the supplied allowlist.

```typescript
isConventionalCommit('chore: x', { types: ['feat', 'fix'] }) // => false
isConventionalCommit('feat: x', { types: ['feat', 'fix'] }) // => true
```

---

### `parseConventionalCommit`

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: ...`).

```typescript
import { parseConventionalCommit } from '@helpers4/commit';

parseConventionalCommit(message: string, options?: ConventionalCommitOptions): ParsedConventionalCommit | null
```

**Parameters:**

- `message: string` — Full commit message (subject + optional body/footer).
- `options?: ConventionalCommitOptions` — Optional constraints forwarded to the regex builder.

**Returns:** `ParsedConventionalCommit | null` — Parsed commit object, or `null` when the subject is not conventional.

**Examples:**

*Parse a Conventional Commits subject*

Extracts type, scope, breaking flag, and description.

```typescript
parseConventionalCommit('feat(api)!: add v2')
// => { type: 'feat', scope: 'api', breaking: true, description: 'add v2', body: '', footer: '' }
```

*Detect breaking changes from the footer*

A `BREAKING CHANGE:` footer flags the commit as breaking even without the `!` marker.

```typescript
parseConventionalCommit('feat: add option\n\nBREAKING CHANGE: drops old config').breaking
// => true
```

*Returns null on a non-conventional message*

Non-matching subjects return `null` rather than throwing.

```typescript
parseConventionalCommit('hello world') // => null
```

---
