diff --git a/VERSION.json b/VERSION.json index 18d68082b..1ea1f9daa 100644 --- a/VERSION.json +++ b/VERSION.json @@ -1,8 +1,8 @@ { - "major": 1, + "major": 0, "merges": 0, "builds": 0, - "version": "1.0.0", + "version": "0.0.0", "lastMerge": null, "lastBuild": null } diff --git a/features/status-dashboard/frontend/src/App.tsx b/features/status-dashboard/frontend/src/App.tsx index 1a7284579..de86ab84a 100644 --- a/features/status-dashboard/frontend/src/App.tsx +++ b/features/status-dashboard/frontend/src/App.tsx @@ -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;' ); diff --git a/features/status-dashboard/frontend/vite.config.ts b/features/status-dashboard/frontend/vite.config.ts index 4cad0c6f8..636f0142e 100644 --- a/features/status-dashboard/frontend/vite.config.ts +++ b/features/status-dashboard/frontend/vite.config.ts @@ -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' diff --git a/features/status-dashboard/server/src/api/version.controller.ts b/features/status-dashboard/server/src/api/version.controller.ts index ef4d506bd..769cd9abd 100644 --- a/features/status-dashboard/server/src/api/version.controller.ts +++ b/features/status-dashboard/server/src/api/version.controller.ts @@ -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), diff --git a/infrastructure/scripts/lib/version-bump.sh b/infrastructure/scripts/lib/version-bump.sh index 77a5614b2..9adb47e94 100755 --- a/infrastructure/scripts/lib/version-bump.sh +++ b/infrastructure/scripts/lib/version-bump.sh @@ -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: 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 </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 diff --git a/infrastructure/scripts/release-deploy.sh b/infrastructure/scripts/release-deploy.sh index 5f6daad2f..d34044f64 100755 --- a/infrastructure/scripts/release-deploy.sh +++ b/infrastructure/scripts/release-deploy.sh @@ -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 ""