panrelease 0.19.0

Utility to release software
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# Panrelease

[![Test Status](https://github.com/dghilardi/panrelease/workflows/Tests/badge.svg?event=push)](https://github.com/dghilardi/panrelease/actions)
[![Crate](https://img.shields.io/crates/v/panrelease.svg)](https://crates.io/crates/panrelease)
[![API](https://docs.rs/panrelease/badge.svg)](https://docs.rs/panrelease)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![npm version](https://badge.fury.io/js/panrelease.svg)](https://www.npmjs.com/package/panrelease)

**Panrelease** is a versatile release automation tool that manages version bumping, changelog updates, and Git operations across multiple package managers. It supports **Cargo** (Rust), **npm** (Node.js), **Maven**, **Gradle** (Java), and **custom file formats** (JSON/XML/TOML) in a unified workflow.

## Features

- **Multi-Language Support** - Unified version management for Cargo, npm, Maven, Gradle, and custom file formats
- **Semantic Versioning** - Automatic major, minor, patch, and post-release version bumping
- **Changelog Automation** - Automatically updates CHANGELOG.md following [Keep a Changelog]https://keepachangelog.com/ format
- **Changelog from Commits** - Auto-populate changelog sections from commit messages (Conventional Commits, Gitmoji)
- **Git Integration** - Commits version changes, creates tags, and supports GPG signing
- **Multi-Module Projects** - Manages monorepos with multiple packages
- **Custom Hooks** - Execute arbitrary commands after releases (build, test, deploy)
- **Cross-Platform** - Available as native CLI binary and npm package (via WebAssembly)
- **Configurable Tag Templates** - Customize Git tag naming (e.g., `v{{version}}`)

## Table of Contents

- [Installation]#installation
- [Quick Start]#quick-start
- [Initialization]#initialization
- [Configuration]#configuration
- [Usage]#usage
- [Package Manager Support]#package-manager-support
- [Multi-Module Projects]#multi-module-projects
- [Hooks]#hooks
- [Changelog from Commits]#changelog-from-commits
- [Strict Mode]#strict-mode
- [Git Integration]#git-integration
- [Documentation]#documentation
- [Contributing]#contributing
- [License]#license

## Installation

### Using Cargo (Rust)

```bash
cargo install panrelease
```

### Using npm/yarn/pnpm (Node.js)

```bash
# npm
npm install -g panrelease

# yarn
yarn global add panrelease

# pnpm
pnpm add -g panrelease
```

### From Source

```bash
git clone https://github.com/dghilardi/panrelease.git
cd panrelease
cargo build --release
```

The binary will be available at `target/release/panrelease`.

## Quick Start

1. **Initialize your project** by running `panrelease init` in your repository root:

```bash
panrelease init
```

This auto-detects the package manager (Cargo, npm, Maven, Gradle) and writes a `.panproject.toml`. Pass `--interactive` to be prompted for optional settings (tag template, GPG signing, strict mode, changelog).

2. **Create a release** with a version bump:

```bash
# Patch release (1.0.0 -> 1.0.1)
panrelease release patch

# Minor release (1.0.0 -> 1.1.0)
panrelease release minor

# Major release (1.0.0 -> 2.0.0)
panrelease release major

# Set explicit version
panrelease release 2.0.0
```

That's it! Panrelease will:
- Update version in your package manifest (Cargo.toml, package.json, pom.xml, etc.)
- Update CHANGELOG.md with the new version and date
- Commit the changes
- Create a Git tag

## Initialization

```bash
panrelease init [--interactive]
```

`panrelease init` auto-detects the package manager in the current repository (looking for `Cargo.toml`, `package.json`, `pom.xml`, or `gradle.properties`) and writes a minimal `.panproject.toml`. It fails if the file already exists.

### Non-interactive mode (default)

Writes a minimal config with Git defaults and the detected package manager. Suitable for CI bootstrap or simple projects.

### Interactive mode

```bash
panrelease init --interactive
```

Prompts for each optional setting before writing the file:

| Prompt | Default |
|--------|---------|
| Tag template | `{{version}}` |
| Force GPG sign? | no |
| Strict mode mainline branch (blank to disable) | *(disabled)* |
| Generate changelog from commits? | no |
| Commit format (if enabled) | `auto` |
| Include scope in entries (if enabled) | yes |
| Include unmatched commits (if enabled) | no |

## Configuration

Panrelease is configured via a `.panproject.toml` file in your project root.

### Basic Configuration

```toml
[vcs]
software = "Git"
force_sign = false              # Require GPG-signed commits
tag_template = "{{version}}"    # Git tag format (e.g., "v{{version}}" -> "v1.0.0")

[modules.myapp]
path = "."
packageManager = "Cargo"        # Cargo | Npm | Maven | Gradle | Generic
main = true                     # Designates primary module for version detection
```

### Configuration Options

| Section | Field | Type | Description |
|---------|-------|------|-------------|
| `vcs` | `software` | string | Version control system (`Git`) |
| `vcs` | `force_sign` | bool | Require GPG-signed commits (default: `false`) |
| `vcs` | `tag_template` | string | Git tag template (default: `{{version}}`) |
| `vcs.strict` | `mainline` | string | Mainline branch name for [strict mode]#strict-mode validation |
| `modules.<name>` | `path` | string | Path to module relative to project root |
| `modules.<name>` | `packageManager` | string | Package manager: `Cargo`, `Npm`, `Maven`, `Gradle`, `Generic` |
| `modules.<name>` | `main` | bool | Mark as primary module for version extraction |
| `changelog` | `from_commits` | bool | Enable changelog population from commit messages (default: `false`) |
| `changelog` | `commit_format` | string | Commit format: `auto`, `conventional`, `gitmoji` (default: `auto`) |
| `changelog` | `include_scope` | bool | Include scope in changelog entries (default: `true`) |
| `changelog` | `include_unmatched` | bool | Include non-matching commits in "Other" section (default: `false`) |

For detailed configuration options, see [docs/CONFIGURATION.md](docs/CONFIGURATION.md).

## Usage

### Command Line Interface

```
panrelease [--path <PATH>] <SUBCOMMAND>

Subcommands:
  init [--interactive]          Initialize .panproject.toml
  release <LEVEL|VERSION>       Bump and release a new version
  show [--format <FORMAT>]      Print the current project version
```

### Release Levels

| Level | Description | Example |
|-------|-------------|---------|
| `major` | Increment major version | `1.2.3` -> `2.0.0` |
| `minor` | Increment minor version | `1.2.3` -> `1.3.0` |
| `patch` | Increment patch version | `1.2.3` -> `1.2.4` |
| `post` | Create post-release with build metadata | `1.2.3` -> `1.2.3+dev.r1` |
| `X.Y.Z` | Set explicit version | `1.2.3` -> `2.0.0` |

> **Note — Non-standard semver usage:** The `post` bump type uses the **build metadata** field (`+`) to represent in-progress feature or hotfix releases (e.g., `1.2.3+user-reg.r1`). This is deliberately non-standard: the [semver specification §10]https://semver.org/#spec-item-10 states that build metadata **must be ignored** when determining version precedence, meaning `1.2.3+feat.r1` and `1.2.3+feat.r2` are considered equal to `1.2.3` by any spec-compliant tool.
>
> **Slug behaviour:**
> - **Without strict mode** — the slug is derived from the existing build metadata if present, otherwise defaults to `dev` (e.g., `1.2.3` → `1.2.3+dev.r1`, then `1.2.3+dev.r2`, ...)
> - **With strict mode** — the slug is automatically inferred from the branch name (e.g., on `feat/user-reg` → `1.2.3+user-reg.r1`). See [Strict Mode]#strict-mode for details.
>
> **Practical implications by ecosystem:**
> - **crates.io** — rejects versions with build metadata at publish time
> - **npm** — silently strips build metadata (`1.2.3+feat.r1` → `1.2.3`), causing conflicts if the base version was already published
> - **Maven / Gradle** — do not natively support semver build metadata; parsing behaviour is undefined
> - **Version-sorting tools** — may treat all post-releases as identical to the base version
>
> **When this is useful:**
> - **Parallel feature development** — multiple branches can each produce versioned builds (`1.3.4+login.r1`, `1.3.4+payments.r1`) without advancing the official version
> - **Staging / QA traceability** — a deployed artefact carries its exact origin (feature name + iteration) in the version string, making it easy to identify what is running in a non-production environment
> - **Hotfix iterations** — ship `1.3.4+fix-timeout.r1`, `r2`, `r3` to staging while the fix is being validated, then do a single official `1.3.5` patch once confirmed
>
> Post-releases are intended for **internal development and staging workflows** and should not be published to public package registries. Use the standard `major` / `minor` / `patch` bumps for official releases.

### Global Options

| Flag | Description |
|------|-------------|
| `-p`, `--path <PATH>` | Path to project root (default: current directory) |
| `-h`, `--help` | Print help information |
| `-V`, `--version` | Print version information |

### Environment Variables

| Variable | Description |
|----------|-------------|
| `RUST_LOG` | Set log level (`error`, `warn`, `info`, `debug`, `trace`) |

### Examples

```bash
# Standard patch release
panrelease release patch

# Minor release with debug logging
RUST_LOG=debug panrelease release minor

# Set specific version
panrelease release 1.5.0

# Post-release (defaults to 'dev' slug when strict mode is off)
panrelease release post

# Show current version
panrelease show

# Show current version as a Docker-compatible tag
panrelease show --format docker-tag

# Run from a different directory
panrelease --path /path/to/project release patch
```

## Package Manager Support

### Cargo (Rust)

Updates `Cargo.toml` and runs `cargo check` to update `Cargo.lock`.

```toml
[modules.rust-lib]
path = "."
packageManager = "Cargo"
main = true
```

### npm (Node.js)

Updates `package.json` and automatically detects/updates the appropriate lockfile (`package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml`).

```toml
[modules.node-app]
path = "."
packageManager = "Npm"
main = true
```

> For Node.js-specific usage (installing via npm/yarn/pnpm, using panrelease in `package.json` scripts, WebAssembly distribution), see the [Node.js package README]nodejs/README.md.

### Maven (Java)

Updates `pom.xml`, supporting both direct version fields and property-based versions.

```toml
[modules.java-lib]
path = "."
packageManager = "Maven"
main = true
```

### Gradle (Java/Kotlin)

Updates `gradle.properties` file.

```toml
[modules.gradle-app]
path = "."
packageManager = "Gradle"
main = true
```

### Generic (Custom Files)

For any project that stores its version in a JSON, XML, or TOML file not covered by the built-in managers. Specify the file name, format, and the dot-separated path to the version field.

```toml
[modules.tauri-conf]
path = "src-tauri"
packageManager = "Generic"
file = "tauri.conf.json"
format = "json"
versionField = "version"
```

Supported formats: `json`, `xml`, `toml`. See [Configuration Guide](docs/CONFIGURATION.md#generic) for details.

## Multi-Module Projects

Panrelease excels at managing monorepos with multiple packages:

```toml
[vcs]
software = "Git"
tag_template = "v{{version}}"

[modules.core]
path = "packages/core"
packageManager = "Cargo"
main = true  # Primary module determines project version

[modules.cli]
path = "packages/cli"
packageManager = "Cargo"

[modules.web]
path = "packages/web"
packageManager = "Npm"

[modules.server]
path = "packages/server"
packageManager = "Maven"
```

All modules are updated simultaneously during a release.

## Hooks

Execute custom commands after releases using hooks:

```toml
[modules.myapp]
path = "."
packageManager = "Cargo"
main = true

[modules.myapp.hooks.after_rel]
build = ["cargo", "build", "--release"]
test = ["cargo", "test"]
publish = ["cargo", "publish"]
```

Hooks are executed in the order they are defined.

## Changelog from Commits

Panrelease can automatically populate your CHANGELOG.md with entries derived from commit messages. When enabled, it reads commits since the last version tag, parses them according to a configurable commit format, and maps them into [Keep a Changelog](https://keepachangelog.com/) sections.

### Enabling Changelog from Commits

Add a `[changelog]` section to your `.panproject.toml`:

```toml
[changelog]
from_commits = true
commit_format = "auto"       # "auto" | "conventional" | "gitmoji"
include_scope = true         # include scope in entries (default: true)
include_unmatched = false    # add non-matching commits to "Other" section (default: false)
```

### Supported Commit Formats

#### Conventional Commits

Follows the [Conventional Commits](https://www.conventionalcommits.org/) specification: `type(scope): description`

| Commit Type | Changelog Section |
|-------------|-------------------|
| `feat` | Added |
| `fix` | Fixed |
| `refactor`, `perf` | Changed |
| `deprecate` | Deprecated |
| `security` | Security |
| `docs`, `chore`, `ci`, `build`, `style`, `test` | *Ignored* |

Breaking changes (indicated by `!` after the type or `BREAKING CHANGE:` in the body) are placed in the **Changed** section with a `[BREAKING]` prefix.

```
feat(auth): add OAuth2 support       -> ### Added  - (auth) Add OAuth2 support
fix: resolve timeout error           -> ### Fixed  - Resolve timeout error
feat!: remove legacy API             -> ### Changed - [BREAKING] Remove legacy API
```

#### Gitmoji

Supports [Gitmoji](https://gitmoji.dev/) shortcodes and unicode emoji: `:emoji: description`

| Emoji | Changelog Section |
|-------|-------------------|
| `:sparkles:` | Added |
| `:bug:` | Fixed |
| `:recycle:`, `:zap:` | Changed |
| `:wastebasket:` | Deprecated |
| `:fire:` | Removed |
| `:lock:` | Security |
| `:boom:` | Changed (breaking) |
| `:memo:`, `:wrench:`, `:construction_worker:`, `:green_heart:`, `:white_check_mark:`, `:pencil2:` | *Ignored* |

#### Auto Mode (Default)

When `commit_format = "auto"`, Panrelease analyzes all commits since the last tag and selects the single format (Conventional Commits or Gitmoji) that matches the most commits. On a tie, Conventional Commits wins. This ensures consistent formatting within a single release.

### Smart Merging

If the `## [Unreleased]` section already contains manually written entries, Panrelease preserves them and only adds new entries from commits that are not already represented. It uses `git blame` to determine which commit introduced each existing entry, preventing duplicates. New entries are interleaved with existing ones by commit date, with the most recent entries appearing first.

### Examples

Given these commits since the last tag:

```
feat(auth): add OAuth2 support
fix: resolve crash on empty input
docs: update API reference
chore: update dependencies
```

Panrelease generates:

```markdown
## [Unreleased]

### Added
- (auth) Add OAuth2 support

### Fixed
- Resolve crash on empty input
```

The `docs` and `chore` commits are ignored as they are not relevant for a user-facing changelog.

## Strict Mode

Strict mode is an opt-in feature that enforces branch-aware release validation. When enabled, Panrelease checks which branch you are on, restricts what kind of release is allowed, and automatically infers the build metadata slug from the branch name.

### Enabling Strict Mode

Add a `[vcs.strict]` section to your `.panproject.toml`:

```toml
[vcs]
software = "Git"

[vcs.strict]
mainline = "main"   # your mainline branch name (e.g., "main", "master")
```

### Branch Classification

Strict mode classifies the current branch into one of three kinds:

| Branch Pattern | Kind | Example |
|----------------|------|---------|
| Matches `mainline` exactly | Mainline | `main` |
| `feat/*` or `feature/*` | Feature | `feat/user-registration` |
| `hotfix/*` or `fix/*` | Hotfix | `fix/timeout-error` |

Any other branch name is rejected with an error.

### Release Rules

Strict mode enforces different rules depending on the branch kind:

**Mainline branches** can only produce clean releases (major, minor, patch). Post-releases are not allowed from mainline.

```bash
# On main branch:
panrelease patch   # OK  -> 1.2.4
panrelease minor   # OK  -> 1.3.0
panrelease post    # ERROR: cannot release a post-version from mainline
```

**Feature and hotfix branches** can only produce post-releases. The build metadata slug is automatically derived from the branch name.

```bash
# On feat/user-registration:
panrelease post    # OK  -> 1.2.3+user-registration.r1
panrelease patch   # ERROR: feature branches can only produce post-releases
```

### Slug Inference

The branch name after the prefix is sanitized into a slug used as build metadata:

- Non-alphanumeric characters are replaced with hyphens
- Consecutive hyphens are collapsed
- Leading/trailing hyphens are removed
- Hotfix branches get a `fix-` prefix in the slug

| Branch | Inferred Slug |
|--------|--------------|
| `feat/user-registration` | `user-registration` |
| `feature/hello@.-world` | `hello-world` |
| `hotfix/npe-fix` | `fix-npe-fix` |
| `fix/timeout` | `fix-timeout` |

### Additional Validations

When releasing from a feature or hotfix branch, strict mode also verifies:

- The version base (major.minor.patch) matches the latest tagged version reachable from the merge-base with mainline
- The mainline version at the branch point is a clean version (not a post-release)
- The build metadata slug matches the expected slug for the current branch
- Releases from detached HEAD are not allowed

If any check fails, Panrelease prints a descriptive error message with a suggested fix (e.g., rebase to latest mainline).

## Git Integration

### Automatic Operations

During a release, Panrelease automatically:
1. Verifies the staging area is clean
2. Updates version in package manifests
3. Updates CHANGELOG.md
4. Executes post-release hooks
5. Commits all changes with the version as the commit message
6. Creates a Git tag based on `tag_template`

### GPG Signing

Enable GPG-signed commits and tags:

```toml
[vcs]
software = "Git"
force_sign = true
```

### Tag Templates

Customize Git tag naming:

```toml
[vcs]
tag_template = "v{{version}}"     # v1.0.0
# or
tag_template = "release-{{version}}"  # release-1.0.0
# or
tag_template = "{{version}}"      # 1.0.0 (default)
```

## Documentation

- [Architecture Overview]docs/ARCHITECTURE.md - Technical design and code structure
- [Configuration Guide]docs/CONFIGURATION.md - Detailed configuration reference
- [Contributing Guidelines]CONTRIBUTING.md - How to contribute
- [Security Policy]SECURITY.md - Reporting vulnerabilities
- [Changelog]CHANGELOG.md - Release history

## Requirements

- **Git** - Required for version control operations
- **Rust 1.70+** (for building from source)
- Package manager CLI (optional):
  - `cargo` for Rust projects
  - `npm`/`yarn`/`pnpm` for Node.js projects
  - `mvn` for Maven projects (optional, only for hooks)
  - `gradle` for Gradle projects (optional, only for hooks)

## Contributing

Contributions are welcome! Please read our [Contributing Guidelines](CONTRIBUTING.md) before submitting a pull request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

- Built with [Rust]https://www.rust-lang.org/
- CLI powered by [clap]https://github.com/clap-rs/clap
- Semantic versioning via [semver]https://github.com/dtolnay/semver

---

Made with care by [dghilardi](https://github.com/dghilardi)