Merge pull request #237 from christianlouis/copilot/fix-autorelease-version-sync
fix(build): sync VERSION file with semantic-release tags and add build time
This commit is contained in:
@@ -49,14 +49,16 @@ jobs:
|
|||||||
semantic-release version
|
semantic-release version
|
||||||
semantic-release publish
|
semantic-release publish
|
||||||
|
|
||||||
- name: Update VERSION file if changed
|
- name: Update build metadata files if changed
|
||||||
run: |
|
run: |
|
||||||
if [ -f VERSION ]; then
|
for f in VERSION BUILD_DATE GIT_SHA RUNTIME_INFO; do
|
||||||
git add VERSION
|
if [ -f "$f" ]; then
|
||||||
if ! git diff --staged --quiet; then
|
git add "$f"
|
||||||
git commit -m "chore(release): update VERSION file [skip ci]"
|
|
||||||
git push
|
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
if ! git diff --staged --quiet; then
|
||||||
|
git commit -m "chore(release): update build metadata files [skip ci]"
|
||||||
|
git push
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Trigger Docker Build on Tag
|
- name: Trigger Docker Build on Tag
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
2026-02-07
|
2026-02-11T14:40:00Z
|
||||||
|
|||||||
+1
-1
@@ -284,7 +284,7 @@ class Settings(BaseSettings):
|
|||||||
return f.read().strip()
|
return f.read().strip()
|
||||||
|
|
||||||
# Default version if not found
|
# Default version if not found
|
||||||
return "0.5.0-dev"
|
return "unknown"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def git_sha(self) -> str:
|
def git_sha(self) -> str:
|
||||||
|
|||||||
+49
-44
@@ -8,10 +8,10 @@ DocuElevate automatically generates and embeds build metadata (version, build da
|
|||||||
|
|
||||||
The following metadata is automatically generated and included in every build:
|
The following metadata is automatically generated and included in every build:
|
||||||
|
|
||||||
1. **Build Date** - The UTC date when the build was created (format: YYYY-MM-DD)
|
1. **Build Date** - The UTC date and time when the build was created (format: YYYY-MM-DDTHH:MM:SSZ)
|
||||||
2. **Git Commit SHA** - The short Git commit hash (7 characters) of the code being built
|
2. **Git Commit SHA** - The short Git commit hash (7 characters) of the code being built
|
||||||
3. **Runtime Information** - A comprehensive summary including version, dates, commit info, and more
|
3. **Runtime Information** - A comprehensive summary including version, dates, commit info, and more
|
||||||
4. **Application Version** - Read from the `VERSION` file (manually updated for releases)
|
4. **Application Version** - Read from the `VERSION` file (automatically updated by semantic-release)
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
@@ -21,11 +21,12 @@ When the Docker image is built (locally or in CI/CD), the following happens:
|
|||||||
|
|
||||||
1. **GitHub Actions runs the metadata script** (`scripts/generate_build_metadata.sh`) before building the Docker image
|
1. **GitHub Actions runs the metadata script** (`scripts/generate_build_metadata.sh`) before building the Docker image
|
||||||
2. **The script generates three files:**
|
2. **The script generates three files:**
|
||||||
- `BUILD_DATE` - Contains the build date in YYYY-MM-DD format
|
- `BUILD_DATE` - Contains the build date and time in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)
|
||||||
- `GIT_SHA` - Contains the short Git commit hash
|
- `GIT_SHA` - Contains the short Git commit hash
|
||||||
- `RUNTIME_INFO` - Contains detailed build information
|
- `RUNTIME_INFO` - Contains detailed build information
|
||||||
3. **Docker copies these files** into the image during the build process
|
3. **The script syncs VERSION** from the latest git tag if out of sync
|
||||||
4. **The application reads the files** at runtime via `app/config.py` properties
|
4. **Docker copies these files** into the image during the build process
|
||||||
|
5. **The application reads the files** at runtime via `app/config.py` properties
|
||||||
|
|
||||||
### File Structure
|
### File Structure
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ DocuElevate/
|
|||||||
|
|
||||||
**BUILD_DATE:**
|
**BUILD_DATE:**
|
||||||
```
|
```
|
||||||
2026-02-07
|
2026-02-11T14:38:06Z
|
||||||
```
|
```
|
||||||
|
|
||||||
**GIT_SHA:**
|
**GIT_SHA:**
|
||||||
@@ -55,13 +56,13 @@ DocuElevate/
|
|||||||
```
|
```
|
||||||
DocuElevate Build Information
|
DocuElevate Build Information
|
||||||
==============================
|
==============================
|
||||||
Version: 0.4.5-dev
|
Version: 0.9.1
|
||||||
Build Date: 2026-02-07
|
Build Date: 2026-02-11T14:38:06Z
|
||||||
Git Commit: 6812d0d2c42a4782e78840b052f0e6da24ec8543
|
Git Commit: 6812d0d2c42a4782e78840b052f0e6da24ec8543
|
||||||
Git Short SHA: 6812d0d
|
Git Short SHA: 6812d0d
|
||||||
Git Branch: main
|
Git Branch: main
|
||||||
Commit Date: 2026-02-07T19:32:04Z
|
Commit Date: 2026-02-07T19:32:04Z
|
||||||
Build Timestamp: 2026-02-07T19:34:10Z
|
Build Timestamp: 2026-02-11T14:38:06Z
|
||||||
==============================
|
==============================
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ The `app/config.py` Settings class provides these properties for accessing build
|
|||||||
Returns the application version with the following priority:
|
Returns the application version with the following priority:
|
||||||
1. `APP_VERSION` environment variable
|
1. `APP_VERSION` environment variable
|
||||||
2. Contents of `VERSION` file
|
2. Contents of `VERSION` file
|
||||||
3. Default: `"0.5.0-dev"`
|
3. Default: `"unknown"`
|
||||||
|
|
||||||
### `settings.build_date` (property)
|
### `settings.build_date` (property)
|
||||||
Returns the build date with the following priority:
|
Returns the build date with the following priority:
|
||||||
@@ -100,10 +101,10 @@ Returns detailed runtime information:
|
|||||||
from app.config import settings
|
from app.config import settings
|
||||||
|
|
||||||
# Get version
|
# Get version
|
||||||
version = settings.version # "0.4.5-dev"
|
version = settings.version # "0.9.1"
|
||||||
|
|
||||||
# Get build date
|
# Get build date
|
||||||
build_date = settings.build_date # "2026-02-07"
|
build_date = settings.build_date # "2026-02-11T14:38:06Z"
|
||||||
|
|
||||||
# Get Git SHA
|
# Get Git SHA
|
||||||
git_sha = settings.git_sha # "6812d0d"
|
git_sha = settings.git_sha # "6812d0d"
|
||||||
@@ -165,9 +166,9 @@ To generate build metadata locally:
|
|||||||
|
|
||||||
# Output:
|
# Output:
|
||||||
# Generating build metadata...
|
# Generating build metadata...
|
||||||
# ✓ BUILD_DATE: 2026-02-07
|
# ✓ BUILD_DATE: 2026-02-11T14:38:06Z
|
||||||
# ✓ GIT_SHA: 6812d0d
|
# ✓ GIT_SHA: 6812d0d
|
||||||
# ✓ VERSION: 0.4.5-dev
|
# ✓ VERSION (from file): 0.9.1
|
||||||
# ✓ RUNTIME_INFO generated
|
# ✓ RUNTIME_INFO generated
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -203,7 +204,7 @@ You can override any metadata value using environment variables:
|
|||||||
export APP_VERSION="1.0.0-custom"
|
export APP_VERSION="1.0.0-custom"
|
||||||
|
|
||||||
# Override build date
|
# Override build date
|
||||||
export BUILD_DATE="2026-01-15"
|
export BUILD_DATE="2026-01-15T10:30:00Z"
|
||||||
|
|
||||||
# Override Git SHA
|
# Override Git SHA
|
||||||
export GIT_COMMIT_SHA="abc1234"
|
export GIT_COMMIT_SHA="abc1234"
|
||||||
@@ -219,20 +220,14 @@ This is useful for:
|
|||||||
|
|
||||||
## Updating the Version
|
## Updating the Version
|
||||||
|
|
||||||
The `VERSION` file must be **manually updated** for releases:
|
The `VERSION` file is **automatically updated** by semantic-release based on conventional commits. The `generate_build_metadata.sh` script also syncs the `VERSION` file from the latest git tag if it's out of date.
|
||||||
|
|
||||||
```bash
|
The semantic-release workflow:
|
||||||
# Update version for a new release
|
1. Analyzes conventional commit messages on the `main` branch
|
||||||
echo "0.5.0" > VERSION
|
2. Determines the next version number (major, minor, or patch bump)
|
||||||
|
3. Creates a git tag (e.g., `v0.10.0`)
|
||||||
# Commit the change
|
4. Runs `generate_build_metadata.sh` as the build command, which syncs the `VERSION` file
|
||||||
git add VERSION
|
5. Commits and pushes updated build metadata files
|
||||||
git commit -m "Bump version to 0.5.0"
|
|
||||||
git tag v0.5.0
|
|
||||||
git push origin main --tags
|
|
||||||
```
|
|
||||||
|
|
||||||
The build metadata script will automatically include this version in all builds.
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -274,27 +269,39 @@ docker build --no-cache -t docuelevate .
|
|||||||
|
|
||||||
1. **Always run the script before building** - The CI/CD pipeline does this automatically
|
1. **Always run the script before building** - The CI/CD pipeline does this automatically
|
||||||
2. **Don't commit generated files** - `GIT_SHA` and `RUNTIME_INFO` are in `.gitignore`
|
2. **Don't commit generated files** - `GIT_SHA` and `RUNTIME_INFO` are in `.gitignore`
|
||||||
3. **Update VERSION manually** - Only update for actual releases
|
3. **Use conventional commits** - Semantic-release determines versions from commit messages
|
||||||
4. **Use semantic versioning** - Follow `MAJOR.MINOR.PATCH` format
|
4. **Use semantic versioning** - Follow `MAJOR.MINOR.PATCH` format
|
||||||
5. **Tag releases in Git** - Create Git tags for version releases
|
5. **Don't manually edit VERSION** - It's managed by semantic-release and the build script
|
||||||
|
|
||||||
## CI/CD Pipeline Flow
|
## CI/CD Pipeline Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ GitHub Actions Workflow │
|
│ Semantic Release Workflow (on push to main) │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 1. Analyze conventional commits │
|
||||||
|
│ 2. Determine next version (major/minor/patch) │
|
||||||
|
│ 3. Create git tag (e.g., v0.10.0) │
|
||||||
|
│ 4. Run build_command (generate_build_metadata.sh) │
|
||||||
|
│ - Syncs VERSION file from latest git tag │
|
||||||
|
│ - Creates BUILD_DATE (with time) │
|
||||||
|
│ - Creates GIT_SHA │
|
||||||
|
│ - Creates RUNTIME_INFO │
|
||||||
|
│ 5. Commit and push updated metadata files │
|
||||||
|
│ 6. Create GitHub Release │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Docker CI Workflow (on push to main) │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────┤
|
||||||
│ 1. Checkout Code │
|
│ 1. Checkout Code │
|
||||||
│ 2. Generate Build Metadata (run script) │
|
│ 2. Generate Build Metadata (run script) │
|
||||||
│ - Creates BUILD_DATE │
|
│ - Syncs VERSION from git tag │
|
||||||
│ - Creates GIT_SHA │
|
│ - Creates BUILD_DATE, GIT_SHA, RUNTIME_INFO │
|
||||||
│ - Creates RUNTIME_INFO │
|
|
||||||
│ 3. Build Docker Image │
|
│ 3. Build Docker Image │
|
||||||
│ - Copies VERSION (from git) │
|
│ - Copies VERSION, BUILD_DATE, GIT_SHA, RUNTIME_INFO │
|
||||||
│ - Copies BUILD_DATE (generated) │
|
│ 4. Push to Docker Hub │
|
||||||
│ - Copies GIT_SHA (generated) │
|
|
||||||
│ - Copies RUNTIME_INFO (generated) │
|
|
||||||
│ 4. Push to Docker Hub / GHCR │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
@@ -315,11 +322,9 @@ docker build --no-cache -t docuelevate .
|
|||||||
|
|
||||||
Potential improvements to the build metadata system:
|
Potential improvements to the build metadata system:
|
||||||
|
|
||||||
1. **Automated version bumping** - Automatically increment version based on commits
|
1. **Build number tracking** - Track sequential build numbers
|
||||||
2. **Changelog generation** - Auto-generate changelog from Git history
|
2. **Deployment tracking** - Record when/where each build was deployed
|
||||||
3. **Build number tracking** - Track sequential build numbers
|
3. **Performance metrics** - Include build time, image size, etc.
|
||||||
4. **Deployment tracking** - Record when/where each build was deployed
|
|
||||||
5. **Performance metrics** - Include build time, image size, etc.
|
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ cd "${PROJECT_ROOT}"
|
|||||||
|
|
||||||
echo "Generating build metadata..."
|
echo "Generating build metadata..."
|
||||||
|
|
||||||
# Generate BUILD_DATE in ISO 8601 format (UTC)
|
# Generate BUILD_DATE in ISO 8601 format with time (UTC)
|
||||||
BUILD_DATE=$(date -u '+%Y-%m-%d')
|
BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||||
echo "${BUILD_DATE}" > BUILD_DATE
|
echo "${BUILD_DATE}" > BUILD_DATE
|
||||||
echo "✓ BUILD_DATE: ${BUILD_DATE}"
|
echo "✓ BUILD_DATE: ${BUILD_DATE}"
|
||||||
|
|
||||||
@@ -58,12 +58,28 @@ fi
|
|||||||
# Read VERSION file
|
# Read VERSION file
|
||||||
if [ -f "VERSION" ]; then
|
if [ -f "VERSION" ]; then
|
||||||
VERSION=$(cat VERSION | tr -d '\n')
|
VERSION=$(cat VERSION | tr -d '\n')
|
||||||
echo "✓ VERSION: ${VERSION}"
|
echo "✓ VERSION (from file): ${VERSION}"
|
||||||
else
|
else
|
||||||
echo "⚠ Warning: VERSION file not found"
|
echo "⚠ Warning: VERSION file not found"
|
||||||
VERSION="unknown"
|
VERSION="unknown"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Try to derive version from the latest git tag (semantic-release creates vX.Y.Z tags)
|
||||||
|
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
|
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||||
|
if [ -n "${LATEST_TAG}" ]; then
|
||||||
|
# Strip the 'v' prefix from the tag (e.g., v0.9.1 -> 0.9.1)
|
||||||
|
TAG_VERSION="${LATEST_TAG#v}"
|
||||||
|
if [ "${TAG_VERSION}" != "${VERSION}" ]; then
|
||||||
|
echo "⚠ VERSION file (${VERSION}) is out of sync with latest tag (${TAG_VERSION})"
|
||||||
|
echo " Updating VERSION file to ${TAG_VERSION}"
|
||||||
|
echo "${TAG_VERSION}" > VERSION
|
||||||
|
VERSION="${TAG_VERSION}"
|
||||||
|
echo "✓ VERSION (updated from tag): ${VERSION}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Generate RUNTIME_INFO with combined metadata
|
# Generate RUNTIME_INFO with combined metadata
|
||||||
cat > RUNTIME_INFO << EOF
|
cat > RUNTIME_INFO << EOF
|
||||||
DocuElevate Build Information
|
DocuElevate Build Information
|
||||||
|
|||||||
@@ -118,6 +118,22 @@ class TestBuildMetadataConfiguration:
|
|||||||
)
|
)
|
||||||
assert config.build_date == "2026-01-15"
|
assert config.build_date == "2026-01-15"
|
||||||
|
|
||||||
|
def test_build_date_with_time_from_environment(self, monkeypatch):
|
||||||
|
"""Test that build_date supports ISO 8601 format with time."""
|
||||||
|
monkeypatch.setenv("BUILD_DATE", "2026-01-15T10:30:00Z")
|
||||||
|
config = Settings(
|
||||||
|
database_url="sqlite:///test.db",
|
||||||
|
redis_url="redis://localhost:6379",
|
||||||
|
openai_api_key="test",
|
||||||
|
azure_ai_key="test",
|
||||||
|
azure_region="test",
|
||||||
|
azure_endpoint="https://test.example.com",
|
||||||
|
gotenberg_url="http://localhost:3000",
|
||||||
|
workdir="/tmp",
|
||||||
|
auth_enabled=False
|
||||||
|
)
|
||||||
|
assert config.build_date == "2026-01-15T10:30:00Z"
|
||||||
|
|
||||||
def test_git_sha_from_environment(self, monkeypatch):
|
def test_git_sha_from_environment(self, monkeypatch):
|
||||||
"""Test that git_sha is read from GIT_COMMIT_SHA environment variable."""
|
"""Test that git_sha is read from GIT_COMMIT_SHA environment variable."""
|
||||||
monkeypatch.setenv("GIT_COMMIT_SHA", "abc1234")
|
monkeypatch.setenv("GIT_COMMIT_SHA", "abc1234")
|
||||||
@@ -154,6 +170,25 @@ class TestBuildMetadataConfiguration:
|
|||||||
# When no file or env var exists, should return "unknown"
|
# When no file or env var exists, should return "unknown"
|
||||||
assert config.git_sha == "unknown"
|
assert config.git_sha == "unknown"
|
||||||
|
|
||||||
|
def test_version_default_when_no_file_or_env(self, monkeypatch, tmp_path):
|
||||||
|
"""Test that version defaults to 'unknown' when no VERSION file or env var exists."""
|
||||||
|
import app.config
|
||||||
|
monkeypatch.setattr(app.config.os.path, 'dirname', lambda x: str(tmp_path))
|
||||||
|
|
||||||
|
config = Settings(
|
||||||
|
database_url="sqlite:///test.db",
|
||||||
|
redis_url="redis://localhost:6379",
|
||||||
|
openai_api_key="test",
|
||||||
|
azure_ai_key="test",
|
||||||
|
azure_region="test",
|
||||||
|
azure_endpoint="https://test.example.com",
|
||||||
|
gotenberg_url="http://localhost:3000",
|
||||||
|
workdir="/tmp",
|
||||||
|
auth_enabled=False
|
||||||
|
)
|
||||||
|
# When no file or env var exists, should return "unknown"
|
||||||
|
assert config.version == "unknown"
|
||||||
|
|
||||||
def test_runtime_info_property(self):
|
def test_runtime_info_property(self):
|
||||||
"""Test that runtime_info returns build information."""
|
"""Test that runtime_info returns build information."""
|
||||||
config = Settings(
|
config = Settings(
|
||||||
|
|||||||
Reference in New Issue
Block a user