diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..77bbfb2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + branches: [ main, 'release/v*' ] + pull_request: + branches: [ main, 'release/v*' ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + - name: Install dependencies + run: go mod tidy + - name: Run tests + run: make check-coverage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0d62225 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + - name: Install dependencies + run: go mod tidy + - name: Run tests + run: make check-coverage + - name: Announce release + run: echo "Release ${{ github.ref_name }} is ready." diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b19ad7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Go build and test artifacts +bin/ +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +coverage.out + +# IDE/editor files +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store + +# Dependency directories +vendor/ + +# Logs +*.log diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8199cc2 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +COVERAGE_FILE=coverage.out +MIN_COVERAGE=80 + +.PHONY: test coverage check-coverage clean release + +test: + go test ./... + +coverage: + go test -coverprofile=$(COVERAGE_FILE) ./... + go tool cover -func=$(COVERAGE_FILE) + +check-coverage: + go test -coverprofile=$(COVERAGE_FILE) ./... + go tool cover -func=$(COVERAGE_FILE) + @cov=$(shell go tool cover -func=$(COVERAGE_FILE) | awk '/^total:/ {print $$3+0}') ; \ + if [ $$(echo "$$cov < $(MIN_COVERAGE)" | bc) -eq 1 ]; then \ + echo "FAIL: coverage below $(MIN_COVERAGE)% ($$cov%)"; exit 1; \ + else \ + echo "PASS: coverage is $$cov%"; \ + fi + +clean: + rm -f $(COVERAGE_FILE) + +release: + git tag v$(VERSION) + git push origin v$(VERSION) diff --git a/README.md b/README.md index 52ef7d0..8d7de08 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,110 @@ You can use it to: - Build **decoupled applications** that scale safely - Model systems with **type safety and explicit intent** - Experiment with **Clean**, **Hexagonal**, **DDD**, or **Message-Driven** styles + +## Serialization + +The `foundation` package provides transport-agnostic serialization via the `Serializable` interface: + +```go +// Serializable defines a transport-agnostic interface for serialization. +type Serializable interface { + ToData() ([]byte, error) + FromData([]byte) error +} +``` + +A JSON implementation is provided: + +```go +// JSONSerializable implements Serializable using JSON encoding. +type JSONSerializable struct { + Value interface{} +} +``` + +### Testing + +Tests for the JSON implementation are in `json_serializable_test.go`. +Run all tests with: + +``` +make test +``` + +Show coverage: + +``` +make coverage +``` + +Check and enforce minimum coverage (default 80%): + +``` +make check-coverage +``` + +Automate release tagging: + +``` +make release VERSION=0.1.0 +``` + +## Contributor Instructions + +- Run all tests before submitting a PR: + ``` + make test + ``` +- Check and enforce code coverage: + ``` + make check-coverage + ``` +- Do not commit coverage.out or build artifacts; these are ignored via .gitignore. +- Use idiomatic Go formatting and linting. +- For new features or bug fixes, add or update tests as needed. + +## 🚀 Release Automation + +### Prerequisites +- [git-chglog](https://github.com/git-chglog/git-chglog) must be installed for changelog generation. + +### Release Workflow + +1. **Automate the release process:** + ```bash + ./scripts/release.sh # Real release flow + ./scripts/release.sh --dry-run # Preview actions only (no changes made) + ``` + This script will: + - Ensure you are on the main branch and up to date + - Determine the next version automatically + - Generate and commit the changelog for the new version + - Create a branch named `release/v` from main + - Push the release branch to the remote + - Open a PR to main with the changelog as the PR body (requires GitHub CLI) + +2. **After PR review and merge:** + - Tag the main branch with the new version (e.g., `git tag vX.X.X && git push origin vX.X.X`) + - The release workflow will run automatically on the new tag + +### Example +```bash +./scripts/release.sh # For a real release +./scripts/release.sh --dry-run # To preview the release process +``` + +### Guidelines +- Always run tests and check coverage before releasing: + ```bash + make check-coverage + ``` +- The release branch and tag are pushed automatically. +- Update documentation and changelog as needed before running the release script. + +### For Contributors +- Do not manually edit the changelog for releases; use the automated scripts. +- Submit PRs from feature branches (e.g., `feature/your-feature`). +- Releases are managed by maintainers using the scripts above. + +--- diff --git a/pkg/forging/foundation/serialization/json_serializable.go b/pkg/forging/foundation/serialization/json_serializable.go new file mode 100644 index 0000000..c310335 --- /dev/null +++ b/pkg/forging/foundation/serialization/json_serializable.go @@ -0,0 +1,17 @@ +package foundation + +import ( + "encoding/json" +) + +type JSONSerializable struct { + Value interface{} +} + +func (j *JSONSerializable) ToData() ([]byte, error) { + return json.Marshal(j.Value) +} + +func (j *JSONSerializable) FromData(data []byte) error { + return json.Unmarshal(data, &j.Value) +} diff --git a/pkg/forging/foundation/serialization/json_serializable_test.go b/pkg/forging/foundation/serialization/json_serializable_test.go new file mode 100644 index 0000000..dd2548e --- /dev/null +++ b/pkg/forging/foundation/serialization/json_serializable_test.go @@ -0,0 +1,32 @@ +package foundation + +import ( + "testing" +) + +type testStruct struct { + Name string + Age int +} + +func TestJSONSerializable_ToDataAndFromData(t *testing.T) { + original := &JSONSerializable{Value: testStruct{Name: "Alice", Age: 30}} + data, err := original.ToData() + if err != nil { + t.Fatalf("ToData failed: %v", err) + } + + copy := &JSONSerializable{Value: &testStruct{}} + err = copy.FromData(data) + if err != nil { + t.Fatalf("FromData failed: %v", err) + } + + result, ok := copy.Value.(*testStruct) + if !ok { + t.Fatalf("Type assertion failed") + } + if result.Name != "Alice" || result.Age != 30 { + t.Errorf("Expected Alice, 30; got %s, %d", result.Name, result.Age) + } +} diff --git a/pkg/forging/foundation/serialization/serializable.go b/pkg/forging/foundation/serialization/serializable.go new file mode 100644 index 0000000..54d9186 --- /dev/null +++ b/pkg/forging/foundation/serialization/serializable.go @@ -0,0 +1,6 @@ +package serialization + +type Serializable interface { + ToData() ([]byte, error) + FromData([]byte) error +} diff --git a/scripts/next_version.sh b/scripts/next_version.sh new file mode 100644 index 0000000..8ee949b --- /dev/null +++ b/scripts/next_version.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +# Get latest tag from remote +git fetch --tags +LATEST_TAG=$(git tag --sort=-v:refname | head -n1) + +if [ -z "$LATEST_TAG" ]; then + echo "0.1.0" + exit 0 +fi + +# Parse version +IFS='.' read -r MAJOR MINOR PATCH <<< "${LATEST_TAG#v}" + +# Bump patch version by default +PATCH=$((PATCH+1)) +NEXT_VERSION="$MAJOR.$MINOR.$PATCH" +echo "$NEXT_VERSION" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100644 index 0000000..5b3877d --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# +# Automated release script for the Forging toolkit +# +# Usage: +# ./scripts/release.sh [--dry-run] +# +# - Must be run from the main branch after all implementation PRs are merged. +# - Determines the next version automatically. +# - Generates and commits the changelog. +# - Creates a release branch from main. +# - Pushes the release branch to origin. +# - Opens a PR to main with the changelog as the PR body (requires GitHub CLI). +# - In --dry-run mode, prints actions without making changes. +# +# Prerequisites: +# - git-chglog (https://github.com/git-chglog/git-chglog) +# - GitHub CLI (https://cli.github.com/) +# +# Example: +# ./scripts/release.sh # Real release flow +# ./scripts/release.sh --dry-run # Preview actions only +set -e + +DRY_RUN=false +while [[ "$1" =~ ^- ]]; do + case $1 in + --dry-run) DRY_RUN=true ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac + shift +done + +# Check for required tools +gh_installed=$(command -v gh || true) +chglog_installed=$(command -v git-chglog || true) +if [ -z "$gh_installed" ]; then + echo "Error: GitHub CLI (gh) is required." + exit 1 +fi +if [ -z "$chglog_installed" ]; then + echo "Error: git-chglog is required." + exit 1 +fi + +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [ "$CURRENT_BRANCH" != "main" ]; then + echo "Error: Release script must be run from the main branch." + exit 1 +fi + +echo "Syncing with remote..." +$DRY_RUN || git pull + +VERSION=$(./scripts/next_version.sh) +BRANCH="release/v$VERSION" + +echo "Creating release branch $BRANCH..." +$DRY_RUN || git checkout -b "$BRANCH" + +echo "Generating changelog for v$VERSION..." +$DRY_RUN || git-chglog -o CHANGELOG.md "$VERSION" + +$DRY_RUN || git add CHANGELOG.md +$DRY_RUN || git commit -m "chore: update CHANGELOG for v$VERSION" + +$DRY_RUN || git push origin "$BRANCH" + +CHANGELOG_BODY=$($DRY_RUN && echo "[DRY RUN]" || awk '/^## /{flag=1;next}/^$/{flag=0}flag' CHANGELOG.md | head -n -1) + +echo "Opening PR to main..." +$DRY_RUN || gh pr create --base main --head "$BRANCH" --title "Release v$VERSION" --body "$CHANGELOG_BODY" + +echo "Release branch and PR created for v$VERSION. Merge the PR to main, then tag the release."