chore(release): add changelog generation and release automation

This commit is contained in:
Deivid Soto 2026-04-06 10:16:01 +02:00
parent aa6acbabc9
commit eaf9d9d1c9
4 changed files with 391 additions and 43 deletions

View file

@ -8,50 +8,158 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Init wizard with daemon install step (`unarr init`, replaces `unarr setup`)
- Interactive config menu with 7 categories (`unarr config [category]`)
- Migration wizard from Sonarr/Radarr/Prowlarr (`unarr migrate`) [pre-beta]
- Auto-detect instances via Docker, config files, port scan, Prowlarr
- Import download history and blocklist to avoid re-downloading
- Detect Plex/Jellyfin/Emby media servers and library paths
- Extract debrid tokens from *arr download clients
- JSON export with `--dry-run --json`
- Media server detection in `unarr init` (suggests library paths as download directory)
- `preferred_quality` setting in config (2160p/1080p/720p)
- Clean command to remove temp files, logs, and cached data (`unarr clean`)
- Daemon mode with background download management (`unarr start`)
- One-shot download command (`unarr download`)
- Stream to media player (`unarr stream`)
- Doctor command for diagnostics (`unarr doctor`)
- Status command for daemon monitoring (`unarr status`)
- Download engine with torrent support (debrid and usenet coming soon)
- File organization (Movies/TV Shows directory structure)
- Post-download verification
- Desktop notifications (Linux, macOS)
- Docker support with multi-stage build
- Cross-platform install scripts (shell, PowerShell)
- Dependabot for automated dependency updates
- golangci-lint configuration with gosec
### Changed
- Renamed `internal/commands/` to `internal/cmd/`
- **organize**: use server metadata for file organization and subtitle handling
- **stream**: add NAT-PMP port mapping for remote downloads
## [0.1.0] - 2025-02-14
## [0.4.1] - 2026-04-01
### Added
- Initial release
- Search across 30+ torrent sources with advanced filters
- TrueSpec torrent inspection (quality, codec, seeds, score)
- Watch command (streaming providers + torrent alternatives)
- Popular and recent content browsing
- System statistics
- Interactive configuration
- JSON output mode (`--json`) for scripting
- Colored terminal output with `--no-color` support
- Homebrew tap distribution
- GoReleaser with UPX compression
- CI pipeline (test, build, lint, vet)
- Lefthook git hooks (gofmt, go vet, conventional commits)
[Unreleased]: https://github.com/torrentclaw/unarr/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/torrentclaw/unarr/releases/tag/v0.1.0
- **cli**: add login command and refactor shared helpers
- **stream**: report watch progress to API via HTTP Range tracking
### Fixed
- **ci**: fix lint errors and pin CI to Go 1.25
- **lint**: remove unused newStubCmd function
### Other
- **cli**: remove moreseed stub command
- **cli**: remove redundant stub commands (monitor, open, add, compare)
## [0.4.0] - 2026-03-31
### Added
- **cli**: upgrade command, rich status, and version cache
### Fixed
- **progress**: always report status transitions and poll for control signals
## [0.3.7] - 2026-03-31
### CI/CD
- **docker**: remove dockerhub-description sync step
## [0.3.6] - 2026-03-31
### CI/CD
- **deps**: bump docker/metadata-action from 5 to 6
- **deps**: bump docker/setup-qemu-action from 3 to 4
- **deps**: bump docker/login-action from 3 to 4
- **deps**: bump docker/build-push-action from 6 to 7
- **deps**: bump codecov/codecov-action from 5 to 6
- **docker**: add Docker Hub description sync and DOCKERHUB.md
### Fixed
- **ci**: upgrade golangci-lint to v2.11.3 for Go 1.25 support
- **docker**: upgrade alpine packages to patch CVE-2025-60876 and CVE-2026-27171
- **lint**: use default:none to disable errcheck, fix all gofmt and exhaustive
- **lint**: disable errcheck, tune gosec/exclusions for codebase state
- **lint**: configure linters for codebase maturity, fix gofmt and ineffassign
- **lint**: exclude common fire-and-forget patterns from errcheck
- **lint**: resolve errcheck and bodyclose warnings for golangci-lint v2
## [0.3.5] - 2026-03-30
### Changed
- migrate lint config to v2, remove daemon auto-upgrade, add trust badges
## [0.3.3] - 2026-03-30
### Fixed
- **ci**: remove go-client checkout steps
## [0.3.2] - 2026-03-30
### Added
- **init**: add 60s countdown, skip key, and cancel detection to browser auth
### CI/CD
- **release**: add Docker Hub publish and VirusTotal scan jobs
### Documentation
- add beta notice, fix install URLs to get.torrentclaw.com
### Fixed
- **ci**: fix virustotal job condition syntax
- **docker**: simplify Dockerfile for CI builds (no local go-client)
- **release**: disable homebrew tap (needs PAT, not GITHUB_TOKEN)
### Other
- re-enable homebrew tap in goreleaser
## [0.3.1] - 2026-03-30
### Fixed
- **build**: unused variable in Windows process check
- **release**: disable homebrew tap until repo is created
### Other
- rename module from torrentclaw-cli to unarr
### Build
- remove UPX compression (antivirus false positives, startup penalty)
## [0.3.0] - 2026-03-29
### Added
- **agent**: add WebSocket transport with HTTP fallback
- **auth**: browser-based CLI authentication (like Claude Code)
- **daemon**: add auto-scan, force start, and stall timeout default
- **debrid**: add HTTPS downloader for debrid direct URLs
- **stream**: UPnP port forwarding for remote video playback
- **usenet**: implement full NNTP download pipeline
- add migrate command, media server detection, and debrid auto-config
- replace setup with init wizard + interactive config menu
- add clean command to remove temp files, logs, and cached data
- add Sentry error reporting
- improve daemon resilience, streaming, and usenet downloads
- initial commit — unarr CLI
### Changed
- extract BuildSyncItems to library package, remove duplication
### Documentation
- improve CLI help, shell completion, and README
### Fixed
- **torrent**: expand tracker list, add DHT persistence and configurable timeouts
- force-start tasks bypass HasCapacity check in dispatch loop
- add panic recovery to auto-scan, cap DHT nodes at 200
- harden usenet/debrid downloaders from critico review
### Build
- add -s -w -trimpath to Makefile, add build-small target with UPX
[Unreleased]: https://github.com/torrentclaw/unarr/compare/v0.4.1...HEAD
[0.4.1]: https://github.com/torrentclaw/unarr/compare/v0.4.0...v0.4.1
[0.4.0]: https://github.com/torrentclaw/unarr/compare/v0.3.7...v0.4.0
[0.3.7]: https://github.com/torrentclaw/unarr/compare/v0.3.6...v0.3.7
[0.3.6]: https://github.com/torrentclaw/unarr/compare/v0.3.5...v0.3.6
[0.3.5]: https://github.com/torrentclaw/unarr/compare/v0.3.3...v0.3.5
[0.3.3]: https://github.com/torrentclaw/unarr/compare/v0.3.2...v0.3.3
[0.3.2]: https://github.com/torrentclaw/unarr/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/torrentclaw/unarr/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/torrentclaw/unarr/releases/tag/v0.3.0

View file

@ -1,4 +1,4 @@
.PHONY: all build test lint coverage clean fmt vet check install-hooks
.PHONY: all build test lint coverage clean fmt vet check install-hooks changelog release release-patch release-minor release-major release-dry
BINARY = unarr
SENTRY_DSN ?=
@ -48,6 +48,29 @@ install-hooks:
install:
go install ./cmd/unarr/
## Preview changelog for next release
changelog:
@git-cliff --unreleased --strip header
## Create a release: make release-patch, release-minor, release-major, or release V=0.5.0
release:
@test -n "$(V)" || { echo "Usage: make release V=0.5.0"; exit 1; }
@./scripts/release.sh $(V)
release-patch:
@./scripts/release.sh patch
release-minor:
@./scripts/release.sh minor
release-major:
@./scripts/release.sh major
## Preview release without making changes
release-dry:
@test -n "$(V)" || { echo "Usage: make release-dry V=patch|minor|major|0.5.0"; exit 1; }
@./scripts/release.sh --dry-run $(V)
## Remove generated files
clean:
rm -f $(BINARY) coverage.out coverage.html

79
cliff.toml Normal file
View file

@ -0,0 +1,79 @@
# git-cliff configuration
# https://git-cliff.org/docs/configuration
[changelog]
header = """# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
"""
body = """
{%- macro remote_url() -%}
https://github.com/torrentclaw/unarr
{%- endmacro -%}
{% if version -%}
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{%- else -%}
## [Unreleased]
{%- endif %}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits
| filter(attribute="scope")
| sort(attribute="scope") %}
- **{{ commit.scope }}**: {{ commit.message }}
{%- if commit.breaking %} (**BREAKING**){% endif %}
{%- endfor -%}
{% for commit in commits %}
{%- if not commit.scope %}
- {{ commit.message }}
{%- if commit.breaking %} (**BREAKING**){% endif %}
{%- endif %}
{%- endfor %}
{% endfor %}
"""
footer = """
{%- macro remote_url() -%}
https://github.com/torrentclaw/unarr
{%- endmacro -%}
{% for release in releases -%}
{% if release.version -%}
{% if release.previous.version -%}
[{{ release.version | trim_start_matches(pat="v") }}]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...{{ release.version }}
{% else -%}
[{{ release.version | trim_start_matches(pat="v") }}]: {{ self::remote_url() }}/releases/tag/{{ release.version }}
{% endif -%}
{% else -%}
{% if release.previous.version -%}
[Unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...HEAD
{% endif -%}
{% endif -%}
{% endfor %}
"""
trim = true
[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_parsers = [
{ message = "^feat", group = "Added" },
{ message = "^fix", group = "Fixed" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Changed" },
{ message = "^style", group = "Changed" },
{ message = "^doc", group = "Documentation" },
{ message = "^ci", group = "CI/CD" },
{ message = "^chore\\(deps\\)", skip = true },
{ message = "^chore", group = "Other" },
{ message = "^test", skip = true },
]
protect_breaking_commits = false
filter_commits = false
tag_pattern = "v[0-9].*"
sort_commits = "newest"

138
scripts/release.sh Executable file
View file

@ -0,0 +1,138 @@
#!/usr/bin/env bash
#
# release.sh — Automate version bump, changelog generation, and tag creation.
#
# Usage:
# ./scripts/release.sh patch|minor|major Auto-bump from latest tag
# ./scripts/release.sh 0.5.0 Explicit version
# ./scripts/release.sh --dry-run patch Preview without changes
#
set -euo pipefail
VERSION_FILE="internal/cmd/version.go"
CHANGELOG_FILE="CHANGELOG.md"
# ── Colors ──────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${CYAN}${NC} $*"; }
ok() { echo -e "${GREEN}${NC} $*"; }
warn() { echo -e "${YELLOW}${NC} $*"; }
error() { echo -e "${RED}${NC} $*" >&2; }
die() { error "$@"; exit 1; }
# ── Args ────────────────────────────────────────────────────────────
DRY_RUN=false
BUMP=""
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
patch|minor|major) BUMP="$arg" ;;
[0-9]*) BUMP="$arg" ;;
-h|--help)
echo "Usage: $0 [--dry-run] <patch|minor|major|X.Y.Z>"
exit 0
;;
*) die "Unknown argument: $arg" ;;
esac
done
[ -z "$BUMP" ] && die "Usage: $0 [--dry-run] <patch|minor|major|X.Y.Z>"
# ── Prerequisites ───────────────────────────────────────────────────
command -v git-cliff >/dev/null 2>&1 || die "git-cliff not found. Install: https://git-cliff.org/docs/installation"
if [ "$DRY_RUN" = false ]; then
[ -n "$(git status --porcelain)" ] && die "Working tree is dirty. Commit or stash changes first."
fi
CURRENT_BRANCH=$(git branch --show-current)
[ "$CURRENT_BRANCH" = "main" ] || warn "Not on main branch (current: $CURRENT_BRANCH)"
# ── Resolve version ────────────────────────────────────────────────
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
LATEST_VERSION="${LATEST_TAG#v}"
bump_version() {
local version="$1" part="$2"
IFS='.' read -r major minor patch <<< "$version"
case "$part" in
major) echo "$((major + 1)).0.0" ;;
minor) echo "$major.$((minor + 1)).0" ;;
patch) echo "$major.$minor.$((patch + 1))" ;;
esac
}
case "$BUMP" in
patch|minor|major) NEXT_VERSION=$(bump_version "$LATEST_VERSION" "$BUMP") ;;
*) NEXT_VERSION="$BUMP" ;;
esac
NEXT_TAG="v${NEXT_VERSION}"
echo ""
echo -e "${BOLD} Release Plan${NC}"
echo -e " ─────────────────────────────"
echo -e " Current tag: ${YELLOW}${LATEST_TAG}${NC}"
echo -e " Next version: ${GREEN}${NEXT_TAG}${NC}"
echo -e " Dry run: ${DRY_RUN}"
echo ""
# ── Preview changelog ───────────────────────────────────────────────
info "Generating changelog for ${NEXT_TAG}..."
CHANGELOG_PREVIEW=$(git-cliff --tag "$NEXT_TAG" --unreleased --strip header)
if [ -z "$CHANGELOG_PREVIEW" ]; then
die "No conventional commits found since ${LATEST_TAG}. Nothing to release."
fi
echo -e "${BOLD} Changes in ${NEXT_TAG}:${NC}"
echo "$CHANGELOG_PREVIEW" | sed 's/^/ /'
echo ""
# ── Dry run stops here ─────────────────────────────────────────────
if [ "$DRY_RUN" = true ]; then
ok "Dry run complete. No changes made."
exit 0
fi
# ── Confirm ─────────────────────────────────────────────────────────
echo -ne "${YELLOW}Proceed with release ${NEXT_TAG}? [y/N]${NC} "
read -r CONFIRM
[[ "$CONFIRM" =~ ^[Yy]$ ]] || { info "Aborted."; exit 0; }
# ── Update version.go ──────────────────────────────────────────────
info "Updating ${VERSION_FILE}..."
sed -i "s/var Version = \".*\"/var Version = \"${NEXT_VERSION}\"/" "$VERSION_FILE"
ok "Version set to ${NEXT_VERSION}"
# ── Update CHANGELOG.md ────────────────────────────────────────────
info "Updating ${CHANGELOG_FILE}..."
git-cliff --tag "$NEXT_TAG" --output "$CHANGELOG_FILE"
ok "Changelog updated"
# ── Commit and tag ──────────────────────────────────────────────────
info "Creating release commit..."
git add "$VERSION_FILE" "$CHANGELOG_FILE"
git commit -m "chore(release): ${NEXT_VERSION}
- Bump version to ${NEXT_VERSION}
- Update CHANGELOG.md"
info "Creating annotated tag ${NEXT_TAG}..."
git tag -a "$NEXT_TAG" -m "Release ${NEXT_TAG}"
echo ""
ok "Release ${NEXT_TAG} created successfully!"
echo ""
echo -e " ${BOLD}Next steps:${NC}"
echo -e " Push to trigger CI release pipeline:"
echo ""
echo -e " ${CYAN}git push origin main --follow-tags${NC}"
echo ""