Files
k3nny a14c3f1181
ci / vet, staticcheck, test, build (push) Successful in 3m2s
docs / Build and deploy docs (push) Failing after 13s
release / Build and publish release (push) Successful in 4m29s
feat(pyproject): release v1.7.0 — Python pyproject.toml version bump
Add internal/pyproject package with ReadVersion and WriteVersion for
pyproject.toml files. Reads [project].version (PEP 621) first, then
falls back to [tool.poetry].version. Section boundaries are detected
via TOML's rule that headers always start at the beginning of a line
(\n[ pattern), so inline arrays with [ characters don't interfere.

Config follows the established multi-value pattern:
- python.pyproject_toml: single path (opt-in, no default)
- python.pyproject_tomls: list for monorepos (overrides single)
- --pyproject flag overrides pyproject_toml and clears pyproject_tomls

100% per-package statement coverage maintained across all 14 packages;
FuzzReadVersion and FuzzWriteVersion added per fuzzing guidelines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 00:22:49 +02:00

5.2 KiB

CLAUDE.md — project guidelines for releaser

Overview

releaser is a single-binary Go tool for GitFlow-based release automation. It targets Conventional Commits, versioned release branches (release/X.Y), and GitLab / GitHub release creation.

Architecture

cmd/main.go          — CLI entrypoint (cobra), run() pipeline, verbose output
internal/branch/     — branch name parser → major/minor
internal/changelog/  — CHANGELOG.md writer
internal/commits/    — Conventional Commits parser (non-strict)
internal/config/     — .releaser.yml loader + env var overlay + source tracking
internal/ghclient/   — minimal GitHub Releases API client
internal/gitutil/    — go-git helpers: tag discovery, commit walker, push
internal/glclient/   — minimal GitLab Releases API client
internal/maven/      — pom.xml version reader/writer
internal/node/       — package.json version reader/writer
internal/notes/      — release notes body generator
internal/version/    — semver next-version calculator

Code conventions

  • No third-party test frameworks — stdlib testing only.
  • No interfaces for mocking — inject function variables (var absPath = filepath.Abs) to test error paths.
  • No comments explaining what — only comments explaining why (hidden constraints, invariants, non-obvious workarounds).
  • No error handling for impossible paths — trust internal invariants; only validate at system boundaries.
  • No abstractions ahead of need — three similar lines beats a premature helper.

Test coverage

100% per-package statement coverage is required across all packages. Run:

go test ./... -cover

Every package must show coverage: 100.0% of statements.

Strategies used in this project:

  • Error path injection: override var absPath, var gitAllCommits, etc. to return injected errors.
  • Filesystem tricks: os.Mkdir where a file is expected (invisible to go-git dirty check; fails os.WriteFile/os.ReadFile); os.Chmod(..., 0444) to make files read-only.
  • Null byte paths: "path\x00name" causes os.Stat to return EINVAL (not ErrNotExist), useful for testing stat-error paths that differ from file-not-found.
  • In-memory git repos: use go-git PlainInit + local bare remote for push tests.
  • Direct function calls: call unexported helpers (e.g. printVerboseConfig) directly with crafted inputs to cover branches that are dead via normal CLI flow.

Fuzzing

Every package that parses free-form text or reads/writes arbitrary file content must have at least one fuzz test. Run the full seed corpus with:

go test -run='^Fuzz' ./...

All seed cases must pass. The table below is authoritative — keep it in sync when adding packages or parsers:

Package Fuzz target(s) Why
internal/branch FuzzParse parses branch name strings
internal/changelog FuzzUpdate rewrites arbitrary existing file content
internal/commits FuzzParse parses arbitrary commit message strings
internal/gradle FuzzReadVersion, FuzzWriteVersion reads/rewrites arbitrary Gradle build file content
internal/pyproject FuzzReadVersion, FuzzWriteVersion reads/rewrites arbitrary pyproject.toml content
internal/glclient FuzzEncodeProjectPath encodes arbitrary project path strings
internal/maven FuzzReadVersion, FuzzReplaceProjectVersion reads/rewrites arbitrary XML file content
internal/node FuzzReadVersion, FuzzWriteVersion reads/rewrites arbitrary JSON file content
internal/notes FuzzGenerate generates notes from arbitrary commit messages

Packages not requiring fuzz tests (no free-form text parsing): internal/config (yaml.v3 handles parsing), internal/ghclient (HTTP client, no text parsing), internal/gitutil (git operations), internal/version (typed inputs only), cmd (CLI orchestration). When adding a new package, check whether it parses text or rewrites files — if yes, add a row above.

Fuzz seed corpus guidelines:

  • Include a realistic happy-path input as the first seed.
  • Include empty string, binary/non-UTF-8 bytes ("\x00\xff"), and inputs that stress known branches (e.g. existing ## [version] heading for changelog).
  • The fuzz body must never assert on return values — only verify no panic.

Dependency rules

  • No new external dependencies unless absolutely necessary. The project deliberately avoids pulling in large ecosystems.
  • go-git (github.com/go-git/go-git/v5) for all git operations.
  • cobra for CLI parsing.
  • gopkg.in/yaml.v3 for config.

Config design

  • New config fields go in the appropriate *Config struct in internal/config/config.go.
  • defaultSources() must be updated to include every new key.
  • LoadWithSources overlay detection must cover every new field.
  • printVerboseConfig in cmd/main.go must show every new config value.

Multi-value config pattern

When a config supports both a single value and multiple values (like pom_path / pom_paths):

  • Single field: PomPath string
  • Multi field: PomPaths []string
  • EffectivePomPaths() method: PomPaths wins if non-empty, else PomPath if set, else default.
  • --pom CLI flag clears PomPaths and sets PomPath only.