Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
# Maintain dependencies for Golang
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
197 changes: 197 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
name: Release

on:
workflow_dispatch:
inputs:
version:
description: "Release version, for example v0.5.1"
required: true
default: "v0.5.1"

permissions:
contents: read

concurrency:
group: release
cancel-in-progress: false

env:
GOTOOLCHAIN: local
RELEASE_VERSION_INPUT: ${{ inputs.version }}

jobs:
validate:
name: Validate release
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.version.outputs.version }}
changelog_version: ${{ steps.version.outputs.changelog_version }}
dispatch_sha: ${{ steps.dispatch.outputs.sha }}
steps:
- uses: actions/checkout@v7

- uses: actions/setup-go@v7
with:
go-version: "1.26"
cache: true

- name: Resolve release version
id: version
shell: bash
env:
RELEASE_VERSION_RAW: ${{ env.RELEASE_VERSION_INPUT }}
run: |
version="$RELEASE_VERSION_RAW"
if [[ ! "$version" =~ ^v.+$ ]]; then
echo "Invalid release version: $version (expected v-prefix SemVer)" >&2
exit 1
fi
changelog_version="${version#v}"
bash scripts/prepare-release.sh "$changelog_version" --semver-only
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "changelog_version=$changelog_version" >> "$GITHUB_OUTPUT"

- name: Record dispatch SHA
id: dispatch
run: echo "sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"

- name: Verify dispatch runs from current main
shell: bash
env:
DISPATCH_SHA: ${{ github.sha }}
run: |
if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then
echo "Release dispatch must run from main (got $GITHUB_REF)" >&2
exit 1
fi
git fetch origin main
origin_main=$(git rev-parse origin/main)
if [[ "$DISPATCH_SHA" != "$origin_main" ]]; then
echo "Dispatch SHA $DISPATCH_SHA is not current origin/main ($origin_main)" >&2
exit 1
fi

- name: Validate release scripts
run: |
bash -n scripts/*.sh
bash scripts/prepare-release-test.sh

- name: Check changelog readiness
env:
CHANGELOG_VERSION: ${{ steps.version.outputs.changelog_version }}
run: bash scripts/prepare-release.sh "$CHANGELOG_VERSION" --check-only

- name: Lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.11.4

- name: Full release validation
run: bash scripts/test-all.sh

publish:
name: Publish GitHub Release
needs: validate
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Finalize changelog on main
shell: bash
env:
RELEASE_VERSION: ${{ needs.validate.outputs.version }}
CHANGELOG_VERSION: ${{ needs.validate.outputs.changelog_version }}
DISPATCH_SHA: ${{ needs.validate.outputs.dispatch_sha }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git fetch origin main
origin_main=$(git rev-parse origin/main)
if [[ "$DISPATCH_SHA" != "$origin_main" ]]; then
echo "origin/main ($origin_main) moved since dispatch ($DISPATCH_SHA)" >&2
exit 1
fi

git checkout main
git pull --ff-only origin main
bash scripts/prepare-release.sh "$CHANGELOG_VERSION"

if ! git diff --quiet -- CHANGELOG.md; then
git add CHANGELOG.md
git commit -m "chore(release): prepare CHANGELOG for ${RELEASE_VERSION}"
git push origin main
fi

- name: Resolve release commit SHA
id: release_sha
shell: bash
env:
DISPATCH_SHA: ${{ needs.validate.outputs.dispatch_sha }}
run: |
release_sha=$(git rev-parse HEAD)
if [[ "$release_sha" == "$DISPATCH_SHA" ]]; then
echo "changelog was already finalized on the validated commit"
elif [[ $(git rev-list --count "${DISPATCH_SHA}..HEAD") -eq 1 ]]; then
parent_sha=$(git rev-parse HEAD^)
changed_files=$(git diff-tree --no-commit-id --name-only -r HEAD)
if [[ "$parent_sha" != "$DISPATCH_SHA" || "$changed_files" != "CHANGELOG.md" ]]; then
echo "release commit must be a single changelog-only child of $DISPATCH_SHA" >&2
exit 1
fi
else
echo "unexpected commits between validated and release state" >&2
exit 1
fi
echo "sha=$release_sha" >> "$GITHUB_OUTPUT"

- name: Verify finalized changelog
env:
CHANGELOG_VERSION: ${{ needs.validate.outputs.changelog_version }}
run: bash scripts/prepare-release.sh "$CHANGELOG_VERSION" --require-final

- name: Verify existing tag
shell: bash
env:
RELEASE_VERSION: ${{ needs.validate.outputs.version }}
RELEASE_SHA: ${{ steps.release_sha.outputs.sha }}
run: |
git fetch origin --tags --force
if git rev-parse --verify "refs/tags/${RELEASE_VERSION}" >/dev/null 2>&1; then
existing_sha=$(git rev-parse "refs/tags/${RELEASE_VERSION}^{commit}")
if [[ "$existing_sha" != "$RELEASE_SHA" ]]; then
echo "tag $RELEASE_VERSION points to $existing_sha, expected $RELEASE_SHA" >&2
exit 1
fi
echo "tag $RELEASE_VERSION already points to the release commit"
fi

- name: Extract release notes
shell: bash
env:
CHANGELOG_VERSION: ${{ needs.validate.outputs.changelog_version }}
run: |
prefix="## [${CHANGELOG_VERSION}] - "
awk -v prefix="$prefix" '
index($0, prefix) == 1 { found = 1; next }
found && /^## \[/ { exit }
found { print }
' CHANGELOG.md > release-notes.md
if [[ ! -s release-notes.md ]]; then
echo "errors ${{ needs.validate.outputs.version }}" > release-notes.md
echo >> release-notes.md
echo "See CHANGELOG.md for details." >> release-notes.md
fi

- name: Publish source-only GitHub Release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.validate.outputs.version }}
target_commitish: ${{ steps.release_sha.outputs.sha }}
name: errors ${{ needs.validate.outputs.version }}
body_path: release-notes.md
prerelease: ${{ contains(needs.validate.outputs.version, '-') }}
34 changes: 25 additions & 9 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,41 @@ on:

jobs:
test:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
GOTOOLCHAIN: local
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v7
with:
go-version: ^1.21
cache-dependency-path: go.sum
id: go
go-version: "1.26"
cache: true

- name: Set up dependencies
run: go mod download
- name: Format check
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "gofmt needed for:"
echo "$unformatted"
exit 1
fi

- name: Vet
run: go vet ./...

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.6.1
version: v2.11.4

- name: Run tests
run: go test -race -v ./...

- name: Module tidy check
run: go mod tidy -diff

- name: Module verify
run: go mod verify
24 changes: 24 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Project agent rules

This repository is `github.com/muonsoft/errors`.

## Sources of truth

- `README.md` — public API and slog integration notes.
- `CHANGELOG.md` — Keep a Changelog record of user-visible changes.
- `docs/release-checklist.md` — release procedure and verification.
- `MIGRATION.md` — breaking-change guidance for v0.4.x → v0.5.0.

## Release policy

- Update `CHANGELOG.md` `[Unreleased]` when behavior or public docs change.
- Local agents and scripts never create or push release tags. The
maintainer-dispatched Release workflow is the only path authorized to push its
changelog-only commit and create a release tag.

## Work discipline

- Preserve unrelated user changes.
- Keep changes focused and commits atomic.
- Run checks proportional to the change (`gofmt`, `go test`, `go test -race`) and
`bash scripts/test-all.sh` before publication.
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- `Log` and `LogLevel` attach a typed `"error"` slog attribute so backends such as
sentry-go/slog can call `SetException`. The value unwraps to the original chain and
does not implement `slog.LogValuer`.
- Maintainer-dispatched GitHub Release workflow with changelog finalization and
CI-owned tag creation.

## [0.5.0] - 2026-02-10

Native `log/slog` integration. This release replaces the custom field system with
`slog.Attr` as the core type for error attributes. The logrus adapter has been
removed.

### Added

- Native `slog.Attr` support in `Wrap` and `Errorf`, including `slog.Group`.
- `Attrs(err)` to extract attributes from an error chain.
- `Log(ctx, logger, err)` and `LogLevel(ctx, logger, level, err)` for slog logging.
- Attribute options `Int64`, `Uint64`, `Float64`, and `Any`.
- `slog.LogValuer` on wrapped errors.

### Changed

- `LoggableError` now requires `Attrs() []slog.Attr` instead of `LogFields(FieldLogger)`.
- `Log(err, logger)` is replaced by `Log(ctx, logger, err)`.
- Minimum Go version is 1.21.

### Deprecated

- `Value` is deprecated in favor of `Any`.

### Removed

- Custom `Field` types and `FieldLogger`.
- logrus adapter. See the [Migration Guide](MIGRATION.md) for a small custom adapter
if needed.

[Unreleased]: https://github.com/muonsoft/errors/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/muonsoft/errors/releases/tag/v0.5.0
14 changes: 9 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
# Contributing

When contributing to this repository, please first discuss the change you wish to
make via [issue](https://github.com/muonsoft/errors/issues), or any other method with the
owners of this repository before making a change.
When contributing to this repository, please first discuss the change you wish to
make via [issue](https://github.com/muonsoft/errors/issues), or any other method with the
owners of this repository before making a change.

Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
all your interactions with the project.

## Pull Request Process

1. Ensure any install or build dependencies are removed before pushing to the git repository.
2. Update the `README.md` with details of changes if necessary.
3. You may merge the Pull Request in once you have the sign-off of at least one developer, or if you
3. Record user-visible changes in the `CHANGELOG.md` `[Unreleased]` section.
4. You may merge the Pull Request in once you have the sign-off of at least one developer, or if you
do not have permission to do that, you may request the reviewer to merge it for you.

Do not create or push release tags. Releases are published by a maintainer through the
GitHub Actions **Release** workflow. See [`docs/release-checklist.md`](docs/release-checklist.md).
Loading