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
63 changes: 4 additions & 59 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -1,68 +1,13 @@
#!/bin/bash
# Pre-push hook for pdfer
# Runs the full test suite before pushing
# Also enforces version bump requirement for main branch
# Pre-push hook for pdfer.
# Runs the full test suite before pushing — fast local feedback before CI.
# Version bumps are handled by the Release workflow, not at push time.

set -e

echo "Running pre-push checks..."

# Check if pushing to main branch
PUSHING_TO_MAIN=false
while read local_ref local_sha remote_ref remote_sha; do
if [[ "$remote_ref" == "refs/heads/main" ]]; then
PUSHING_TO_MAIN=true
break
fi
done

if [ "$PUSHING_TO_MAIN" = true ]; then
echo "Pushing to main branch - checking version bump..."

# Get current version from working directory
if [ ! -f "pdfer.go" ]; then
echo "❌ Error: pdfer.go not found"
exit 1
fi

# Extract version using sed (more portable than grep -P)
CURRENT_VERSION=$(grep 'return "' pdfer.go | sed -n 's/.*return "\([^"]*\)".*/\1/p' | head -1)
if [ -z "$CURRENT_VERSION" ]; then
echo "❌ Error: Could not extract version from pdfer.go"
exit 1
fi

# Get remote version (if remote exists)
REMOTE_VERSION=""
if git rev-parse --verify origin/main >/dev/null 2>&1; then
REMOTE_VERSION=$(git show origin/main:pdfer.go 2>/dev/null | grep 'return "' | sed -n 's/.*return "\([^"]*\)".*/\1/p' | head -1 || echo "")
fi

# If remote version exists and matches current, reject push
if [ -n "$REMOTE_VERSION" ] && [ "$CURRENT_VERSION" = "$REMOTE_VERSION" ]; then
echo ""
echo "❌ ERROR: Version bump required!"
echo " Current version: $CURRENT_VERSION"
echo " Remote version: $REMOTE_VERSION"
echo ""
echo " Version numbers must be incremented before pushing to main."
echo " Update the version in pdfer.go and commit the change."
echo ""
echo " To override (not recommended), use: git push --no-verify"
echo ""
exit 1
fi

if [ -n "$REMOTE_VERSION" ]; then
echo "✓ Version check passed: $REMOTE_VERSION -> $CURRENT_VERSION"
else
echo "✓ Version check passed: $CURRENT_VERSION (no remote version found)"
fi
fi

# Run all tests
echo "Running tests..."
go test ./...

echo ""
echo "All tests passed! Safe to push."
echo "All tests passed! Safe to push."
58 changes: 58 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
test:
name: Test (Go ${{ matrix.go }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go: ['1.21', '1.22', '1.23']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- run: go vet ./...
- run: go test ./...

fmt:
name: gofmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Check formatting
run: |
UNFORMATTED=$(gofmt -l .)
if [ -n "$UNFORMATTED" ]; then
echo "The following files need gofmt:"
echo "$UNFORMATTED"
exit 1
fi

version-guard:
name: No hardcoded library version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Reject literal version string in pdfer.go
run: |
if grep -nE 'return\s+"v?[0-9]+\.[0-9]+\.[0-9]+"' pdfer.go; then
echo ""
echo "ERROR: pdfer.go contains a hardcoded version literal."
echo "Version() must derive from runtime/debug.ReadBuildInfo()."
echo "See CONTRIBUTING.md for the release process."
exit 1
fi
68 changes: 68 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Release

on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., v1.10.0)'
required: true
type: string
ref:
description: 'Branch or commit to release from'
required: false
default: 'main'
type: string

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
if ! [[ "${{ inputs.version }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then
echo "ERROR: version must match vMAJOR.MINOR.PATCH (optional -prerelease suffix)"
echo "Got: ${{ inputs.version }}"
exit 1
fi

- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 0

- name: Fail if tag already exists
run: |
if git rev-parse "refs/tags/${{ inputs.version }}" >/dev/null 2>&1; then
echo "ERROR: tag ${{ inputs.version }} already exists"
exit 1
fi

- uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Verify build and tests
run: |
go vet ./...
go test ./...

- name: Create and push annotated tag
env:
GIT_AUTHOR_NAME: github-actions[bot]
GIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
GIT_COMMITTER_NAME: github-actions[bot]
GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
run: |
git tag -a "${{ inputs.version }}" -m "Release ${{ inputs.version }}"
git push origin "${{ inputs.version }}"

- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ inputs.version }}" \
--title "${{ inputs.version }}" \
--generate-notes
27 changes: 15 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,28 @@ go test ./... # Run tests
This project uses git hooks to maintain code quality:

- **pre-commit**: Runs `gofmt` and `go vet` on staged files
- **pre-push**: Runs the full test suite and enforces version bump requirement
- **pre-push**: Runs the full test suite before pushing

#### Version Bump Requirement
The setup script (`./scripts/setup.sh`) configures these. To skip hooks temporarily:

**Pushing to `main` requires a version bump.** The pre-push hook will reject pushes to `main` if the version in `pdfer.go` hasn't changed from the remote version. This ensures every change to `main` has an associated version increment.

To bump the version:
1. Update the version string in `pdfer.go` (e.g., `0.6.0` → `0.7.0`)
2. Commit the change: `git commit -m "chore: bump version to X.Y.Z"`
3. Push normally - the hook will verify the version changed

To override (not recommended): `git push --no-verify`

The setup script configures these automatically. To skip hooks temporarily:
```bash
git commit --no-verify # Skip pre-commit
git push --no-verify # Skip pre-push
```

## Releases

`Version()` reads the module version at runtime from `runtime/debug.ReadBuildInfo()`, so it always matches the git tag Go's module system resolved. There is no version constant in source to keep in sync.

Releases are cut from `main` via the **Release** GitHub Actions workflow:

1. Open the [Actions tab](../../actions/workflows/release.yml)
2. Click **Run workflow**
3. Enter the version (e.g., `v1.10.0`), following [semantic versioning](https://semver.org/)
4. The workflow validates the version, runs the test suite, creates an annotated tag, pushes it, and publishes a GitHub release with auto-generated notes

Only maintainers with write access can dispatch the workflow.

## Code Style

- Follow standard Go conventions (use `gofmt`)
Expand Down
27 changes: 25 additions & 2 deletions pdfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
package pdfer

import (
"runtime/debug"

"github.com/benedoc-inc/pdfer/types"
)

Expand Down Expand Up @@ -107,7 +109,28 @@ type XFAConfig = types.XFAConfig
// XFALocaleSet represents parsed XFA localization data.
type XFALocaleSet = types.XFALocaleSet

// Version returns the library version.
const modulePath = "github.com/benedoc-inc/pdfer"

// Version returns the library version, derived from Go module metadata.
//
// When consumed via `go get` or `go install`, this returns the resolved
// module version (e.g., "v1.10.0"). When built from a working copy, it
// returns "(devel)". Falls back to "(unknown)" if build info is unavailable.
//
// Versioning is driven by git tags through the release workflow; no manual
// string updates are required.
func Version() string {
return "1.3.1"
info, ok := debug.ReadBuildInfo()
if !ok {
return "(unknown)"
}
if info.Main.Path == modulePath {
return info.Main.Version
}
for _, dep := range info.Deps {
if dep != nil && dep.Path == modulePath {
return dep.Version
}
}
return "(unknown)"
}
Loading