feat(versioning): unified version from VERSION.json

- status.atlilith.com now sources version from VERSION.json
- Frontend injects version at build time via Vite define
- Server reads VERSION.json instead of package.json
- release-deploy.sh increments builds before sync to releases
- version-bump.sh updated for <major>.<merges>.<builds> format
- Starting version: 0.0.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-25 20:50:56 -08:00
parent 1da1f88954
commit 0c6572d716
6 changed files with 189 additions and 128 deletions

View file

@ -1,8 +1,8 @@
{
"major": 1,
"major": 0,
"merges": 0,
"builds": 0,
"version": "1.0.0",
"version": "0.0.0",
"lastMerge": null,
"lastBuild": null
}

View file

@ -8,11 +8,12 @@ import { AdminDashboard } from './AdminDashboard';
import { LoginPage } from './LoginPage';
import { HostsPage } from './HostsPage';
const APP_VERSION = '1.1.0-ui-refactor';
const BUILD_TIME = '2025-12-25T15:30:00Z';
// Injected at build time from VERSION.json
declare const __APP_VERSION__: string;
declare const __BUILD_TIME__: string;
console.log(
`%c Lilith Status Dashboard v${APP_VERSION} %c Built: ${BUILD_TIME} `,
`%c Lilith Status Dashboard v${__APP_VERSION__} %c Built: ${__BUILD_TIME__} `,
'background: #ff00ff; color: #000; font-weight: bold; padding: 4px 8px;',
'background: #00ffff; color: #000; padding: 4px 8px;'
);

View file

@ -1,12 +1,52 @@
import { defineConfig, loadEnv } from 'vite';
import { defineConfig, loadEnv, Plugin } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { execFileSync } from 'child_process';
import { readFileSync, writeFileSync } from 'fs';
// Read authoritative version from monorepo root
function getMonorepoVersion(): string {
try {
const versionPath = path.resolve(__dirname, '../../../VERSION.json');
const content = readFileSync(versionPath, 'utf-8');
return JSON.parse(content).version || '0.0.0';
} catch {
return '0.0.0';
}
}
// Generate build info at build time
function buildInfoPlugin(): Plugin {
return {
name: 'build-info',
writeBundle(options, bundle) {
const gitCommit = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf-8' }).trim();
const gitBranch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf-8' }).trim();
// Extract bundle hash from output filenames
const jsBundle = Object.keys(bundle).find(f => f.startsWith('assets/index-') && f.endsWith('.js'));
const buildHash = jsBundle?.match(/index-([^.]+)\.js/)?.[1] || 'unknown';
const buildInfo = {
buildHash,
gitCommit,
gitBranch,
buildTime: new Date().toISOString(),
app: 'status-dashboard-frontend',
};
const outDir = options.dir || 'dist';
writeFileSync(path.join(outDir, 'build-info.json'), JSON.stringify(buildInfo, null, 2));
console.log(`\n📦 Build info generated: ${buildHash} (${gitCommit})`);
},
};
}
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, __dirname, '');
return {
plugins: [react()],
plugins: [react(), buildInfoPlugin()],
server: {
port: 3000,
proxy: {
@ -26,6 +66,9 @@ export default defineConfig(({ mode }) => {
},
},
define: {
// Inject authoritative version at build time
'__APP_VERSION__': JSON.stringify(getMonorepoVersion()),
'__BUILD_TIME__': JSON.stringify(new Date().toISOString()),
// Production: Use empty string for same-origin (HTTPS) - nginx proxies /api and /socket.io
// Development: Use proxy config above
'import.meta.env.VITE_API_URL': mode === 'production'

View file

@ -20,16 +20,15 @@ interface VersionInfo {
node: string;
}
// Read version from package.json at startup
const packageJsonPath = path.join(__dirname, '../../package.json');
let packageInfo = { name: 'status-dashboard-server', version: '0.0.0' };
// Read version from authoritative VERSION.json at monorepo root
const versionJsonPath = path.join(__dirname, '../../../../../VERSION.json');
let versionInfo = { version: '0.0.0', major: 0, merges: 0, builds: 0 };
try {
const content = fs.readFileSync(packageJsonPath, 'utf-8');
const pkg = JSON.parse(content);
packageInfo = { name: pkg.name || packageInfo.name, version: pkg.version || packageInfo.version };
const content = fs.readFileSync(versionJsonPath, 'utf-8');
versionInfo = JSON.parse(content);
} catch {
// Use defaults if package.json cannot be read
// Use defaults if VERSION.json cannot be read
}
// Capture build time at startup
@ -41,8 +40,8 @@ export class VersionController {
@Get('version')
getVersion(): VersionInfo {
return {
name: packageInfo.name,
version: packageInfo.version,
name: 'lilith-platform',
version: versionInfo.version,
buildTime: BUILD_TIME,
environment: process.env.NODE_ENV || 'development',
uptime: Math.floor((Date.now() - START_TIME) / 1000),

View file

@ -1,126 +1,145 @@
#!/bin/bash
#
# Version Bumping Library - Semantic Versioning
# Version Bumping Library - Lilith Platform Versioning
#
# Handles semantic version management (vX.Y.Z) based on conventional commits.
# Analyzes commit messages to determine appropriate version bump type.
# Format: <major>.<merges>.<builds>
# - major: Manual increment for breaking changes
# - merges: Incremented by workflow/finish on worktree merge
# - builds: Incremented by release-deploy.sh before sync to releases
#
# Source of truth: VERSION.json at repository root
#
set -e
set -u
# Find VERSION.json from script location
find_version_file() {
local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
echo "$PROJECT_ROOT/VERSION.json"
}
VERSION_FILE="$(find_version_file)"
get_last_version() {
# Get most recent tag matching v*.*.*
local LAST_TAG=$(git describe --tags --abbrev=0 --match 'v*.*.*' 2>/dev/null || echo "")
if [ -z "$LAST_TAG" ]; then
echo "v0.0.0"
if [ -f "$VERSION_FILE" ]; then
jq -r '.version' "$VERSION_FILE"
else
echo "$LAST_TAG"
echo "0.0.0"
fi
}
parse_version() {
local VERSION="$1"
# Remove 'v' prefix and split into components
echo "${VERSION#v}" | tr '.' ' '
read_version_json() {
if [ -f "$VERSION_FILE" ]; then
cat "$VERSION_FILE"
else
echo '{"major": 0, "merges": 0, "builds": 0, "version": "0.0.0"}'
fi
}
# Increment builds counter (called before sync to releases)
increment_builds() {
local JSON=$(read_version_json)
local MAJOR=$(echo "$JSON" | jq -r '.major')
local MERGES=$(echo "$JSON" | jq -r '.merges')
local BUILDS=$(echo "$JSON" | jq -r '.builds')
BUILDS=$((BUILDS + 1))
local NEW_VERSION="$MAJOR.$MERGES.$BUILDS"
jq --arg v "$NEW_VERSION" \
--argjson builds "$BUILDS" \
--arg lastBuild "$(date -Iseconds)" \
'.builds = $builds | .version = $v | .lastBuild = $lastBuild' \
"$VERSION_FILE" > "$VERSION_FILE.tmp" && mv "$VERSION_FILE.tmp" "$VERSION_FILE"
echo "$NEW_VERSION"
}
# Increment merges counter (called by workflow/finish)
increment_merges() {
local WORKTREE_NAME="${1:-unknown}"
local JSON=$(read_version_json)
local MAJOR=$(echo "$JSON" | jq -r '.major')
local MERGES=$(echo "$JSON" | jq -r '.merges')
local BUILDS=$(echo "$JSON" | jq -r '.builds')
MERGES=$((MERGES + 1))
local NEW_VERSION="$MAJOR.$MERGES.$BUILDS"
jq --arg v "$NEW_VERSION" \
--argjson merges "$MERGES" \
--arg lastMerge "$WORKTREE_NAME" \
'.merges = $merges | .version = $v | .lastMerge = $lastMerge' \
"$VERSION_FILE" > "$VERSION_FILE.tmp" && mv "$VERSION_FILE.tmp" "$VERSION_FILE"
echo "$NEW_VERSION"
}
# Bump major version (manual, resets counters)
bump_major() {
local JSON=$(read_version_json)
local MAJOR=$(echo "$JSON" | jq -r '.major')
MAJOR=$((MAJOR + 1))
local NEW_VERSION="$MAJOR.0.0"
jq --arg v "$NEW_VERSION" \
--argjson major "$MAJOR" \
'.major = $major | .merges = 0 | .builds = 0 | .version = $v' \
"$VERSION_FILE" > "$VERSION_FILE.tmp" && mv "$VERSION_FILE.tmp" "$VERSION_FILE"
echo "$NEW_VERSION"
}
# Legacy compatibility functions
determine_bump_type() {
local LAST_TAG="$1"
local COMMITS=$(git log ${LAST_TAG}..HEAD --oneline --no-merges 2>/dev/null || echo "")
if [ -z "$COMMITS" ]; then
# No commits since last tag
echo "none"
return
fi
# Check for breaking changes (BREAKING CHANGE: or ! after type)
if echo "$COMMITS" | grep -qE '(BREAKING CHANGE:|!:)'; then
echo "major"
return
fi
# Check for features (feat: or feat(scope):)
if echo "$COMMITS" | grep -qE '^[a-f0-9]+ feat(\(.+\))?:'; then
echo "minor"
return
fi
# Default to patch for fixes, chores, docs, etc.
echo "patch"
# Always return "build" - we use builds counter now
echo "build"
}
bump_version() {
local CURRENT="$1"
local BUMP_TYPE="$2"
if [ "$BUMP_TYPE" = "none" ]; then
echo "$CURRENT"
return
fi
read -r MAJOR MINOR PATCH <<< $(parse_version "$CURRENT")
case "$BUMP_TYPE" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
*)
echo "$CURRENT"
return
;;
esac
echo "v${MAJOR}.${MINOR}.${PATCH}"
# For release process, increment builds
increment_builds
}
create_version_tag() {
local NEW_VERSION="$1"
local COMMIT_MESSAGE="$2"
# Create annotated tag with commit message as annotation
git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION
# Create annotated tag
git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION
$COMMIT_MESSAGE"
echo "$NEW_VERSION"
echo "v$NEW_VERSION"
}
get_version_changelog() {
local LAST_TAG="$1"
local NEW_TAG="$2"
# Generate concise changelog
cat <<EOF
## Changes from $LAST_TAG to $NEW_TAG
### Features
$(git log ${LAST_TAG}..HEAD --oneline --no-merges --grep='^feat' | sed 's/^/- /' || echo "- None")
### Commits
$(git log ${LAST_TAG}..HEAD --oneline --no-merges 2>/dev/null | head -20 | sed 's/^/- /' || echo "- None")
### Fixes
$(git log ${LAST_TAG}..HEAD --oneline --no-merges --grep='^fix' | sed 's/^/- /' || echo "- None")
### Other
$(git log ${LAST_TAG}..HEAD --oneline --no-merges --grep='^chore\|^refactor\|^docs\|^test' | head -5 | sed 's/^/- /' || echo "- None")
Total commits: $(git log ${LAST_TAG}..HEAD --oneline --no-merges | wc -l)
Total commits: $(git log ${LAST_TAG}..HEAD --oneline --no-merges 2>/dev/null | wc -l || echo "0")
EOF
}
# Export functions for use in other scripts
# Export functions
export -f find_version_file
export -f get_last_version
export -f parse_version
export -f read_version_json
export -f increment_builds
export -f increment_merges
export -f bump_major
export -f determine_bump_type
export -f bump_version
export -f create_version_tag

View file

@ -67,15 +67,22 @@ main() {
cd "$PROJECT_ROOT"
# Step 1: Sync to releases repository
log_step "1. Syncing to releases repository..."
# Step 1: Increment build version
log_step "1. Incrementing build version..."
NEW_VERSION=$(increment_builds)
log_info "Version: $NEW_VERSION"
git add VERSION.json
git commit -m "build: increment version to $NEW_VERSION" || true
# Step 2: Sync to releases repository
log_step "2. Syncing to releases repository..."
sync_to_releases
# Step 2: Generate commit message
log_step "2. Generating ML commit message..."
LAST_TAG=$(get_last_version)
CHANGED_FILES=$(list_changed_files "$LAST_TAG")
DIFF_SUMMARY=$(git diff --stat ${LAST_TAG}..HEAD)
# Step 3: Generate commit message
log_step "3. Generating ML commit message..."
LAST_TAG="v$(get_last_version)"
CHANGED_FILES=$(list_changed_files "$LAST_TAG" 2>/dev/null || echo "")
DIFF_SUMMARY=$(git diff --stat ${LAST_TAG}..HEAD 2>/dev/null || echo "Initial release")
COMMIT_MESSAGE=$(generate_commit_message "$CHANGED_FILES" "$DIFF_SUMMARY")
echo ""
@ -83,46 +90,38 @@ main() {
echo "$COMMIT_MESSAGE"
echo ""
# Step 3: Create version tag
log_step "3. Creating version tag..."
BUMP_TYPE=$(determine_bump_type "$LAST_TAG")
NEW_TAG=$(bump_version "$LAST_TAG" "$BUMP_TYPE")
log_info "Version: $LAST_TAG$NEW_TAG (bump: $BUMP_TYPE)"
if [ "$BUMP_TYPE" = "none" ]; then
log_warn "No changes detected since last tag"
read -p "Continue with same version? (y/N) " -n 1 -r
echo
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
fi
# Step 4: Create version tag
log_step "4. Creating version tag..."
NEW_TAG="v$NEW_VERSION"
log_info "Version tag: $NEW_TAG"
# Commit to releases repo
cd "$RELEASES_DIR"
git add -A
git commit -m "$COMMIT_MESSAGE" || log_warn "No changes to commit"
create_version_tag "$NEW_TAG" "$COMMIT_MESSAGE"
create_version_tag "$NEW_VERSION" "$COMMIT_MESSAGE"
cd "$PROJECT_ROOT"
# Step 4: Generate release notes
log_step "4. Generating release notes..."
# Step 5: Generate release notes
log_step "5. Generating release notes..."
RELEASE_NOTES_FILE="/tmp/release-notes-$NEW_TAG.md"
CHANGED_SERVICES=$(detect_changed_services "$LAST_TAG")
CHANGED_SERVICES=$(detect_changed_services "$LAST_TAG" 2>/dev/null || echo "")
generate_release_notes "$LAST_TAG" "$NEW_TAG" "$CHANGED_SERVICES" > "$RELEASE_NOTES_FILE"
# Step 5: Push to GitHub (for code sharing, not CI/CD)
log_step "5. Pushing to GitHub (code sharing)..."
# Step 6: Push to GitHub (for code sharing, not CI/CD)
log_step "6. Pushing to GitHub (code sharing)..."
cd "$RELEASES_DIR"
push_to_github "$NEW_TAG" "$RELEASE_NOTES_FILE"
cd "$PROJECT_ROOT"
# Step 6: Detect changed services
log_step "6. Detecting changed services..."
# Step 7: Detect changed services
log_step "7. Detecting changed services..."
log_info "Changed services: $(echo $CHANGED_SERVICES | wc -w) service(s)"
get_change_summary "$LAST_TAG" "$CHANGED_SERVICES"
get_change_summary "$LAST_TAG" "$CHANGED_SERVICES" 2>/dev/null || true
# Step 7: Prompt for deployment
# Step 8: Prompt for deployment
echo ""
log_step "7. Ready to deploy"
log_step "8. Ready to deploy"
echo ""
log_info "Services to deploy: $CHANGED_SERVICES"
echo ""
@ -136,12 +135,12 @@ main() {
exit 0
fi
# Step 8: Deploy services
log_step "8. Deploying services..."
# Step 9: Deploy services
log_step "9. Deploying services..."
deploy_changed_services "$CHANGED_SERVICES"
# Step 9: Verify deployment
log_step "9. Verifying deployment..."
# Step 10: Verify deployment
log_step "10. Verifying deployment..."
verify_deployment
echo ""