Files
easystream-main/auto-deploy.ps1
SamiAhmed7777 877b9347b6 feat: Add Continuous Delivery system with auto-commit and file watcher
Automatically commit and push changes to GitHub with zero manual intervention.

Features:
- File watcher mode: Auto-detects changes in real-time
- Timer mode: Commits at regular intervals (default 5 minutes)
- Smart exclusions: Ignores temp files, sessions, cache, db files
- Retry logic: Auto-retries failed pushes
- Change summaries: Detailed commit messages with file lists

Components:
- auto-deploy.ps1: Core CD engine with file watcher
- start-cd.ps1: Easy-to-use wrapper with commands
- .cd-config.json: Configuration file
- CONTINUOUS_DELIVERY_GUIDE.md: Complete documentation

Usage:
  .\start-cd.ps1 watch    # Start file watcher (recommended)
  .\start-cd.ps1 start    # Timer mode (every 5 min)
  .\start-cd.ps1 once     # One-time commit

Also removed db_data/ from git tracking (now in .gitignore).
Database runtime files should never be committed.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 02:07:57 -07:00

363 lines
11 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Continuous Delivery Script for EasyStream
# Automatically commits and pushes changes to GitHub
param(
[int]$IntervalSeconds = 300, # Default: 5 minutes
[string]$Branch = "dev",
[string]$CommitPrefix = "auto:",
[switch]$WatchMode,
[switch]$Verbose
)
$ErrorActionPreference = "Continue"
$RepoPath = $PSScriptRoot
# Color output functions
function Write-Success { param($msg) Write-Host "$msg" -ForegroundColor Green }
function Write-Info { param($msg) Write-Host " $msg" -ForegroundColor Cyan }
function Write-Warning { param($msg) Write-Host "$msg" -ForegroundColor Yellow }
function Write-Error { param($msg) Write-Host "$msg" -ForegroundColor Red }
# Load configuration
$configPath = Join-Path $RepoPath ".cd-config.json"
if (Test-Path $configPath) {
$config = Get-Content $configPath | ConvertFrom-Json
if ($config.intervalSeconds) { $IntervalSeconds = $config.intervalSeconds }
if ($config.branch) { $Branch = $config.branch }
if ($config.commitPrefix) { $CommitPrefix = $config.commitPrefix }
if ($config.excludePatterns) { $excludePatterns = $config.excludePatterns }
} else {
$excludePatterns = @(
"f_data/data_sessions/*",
"f_data/data_cache/_c_tpl/*",
".setup_complete",
"*.log",
"db_data/*",
"node_modules/*",
"vendor/*"
)
}
function Test-GitRepo {
Push-Location $RepoPath
try {
$null = git rev-parse --git-dir 2>&1
return $?
} finally {
Pop-Location
}
}
function Get-GitStatus {
Push-Location $RepoPath
try {
$status = git status --porcelain 2>&1
return $status
} finally {
Pop-Location
}
}
function Get-ChangeSummary {
Push-Location $RepoPath
try {
$modified = @(git diff --name-only).Count
$staged = @(git diff --cached --name-only).Count
$untracked = @(git ls-files --others --exclude-standard).Count
return @{
Modified = $modified
Staged = $staged
Untracked = $untracked
Total = $modified + $staged + $untracked
}
} finally {
Pop-Location
}
}
function Update-GitIgnore {
$gitignorePath = Join-Path $RepoPath ".gitignore"
$ignoreContent = @"
# Temporary session files
f_data/data_sessions/sess_*
# Cache files
f_data/data_cache/_c_tpl/*
# Setup marker
.setup_complete
# Database runtime files
db_data/*
# Logs
*.log
# Dependencies
node_modules/
vendor/
# IDE
.vscode/
.idea/
"@
if (-not (Test-Path $gitignorePath)) {
$ignoreContent | Out-File -FilePath $gitignorePath -Encoding UTF8
Write-Success "Created .gitignore"
} else {
# Append if patterns are missing
$existing = Get-Content $gitignorePath -Raw
if ($existing -notmatch "f_data/data_sessions/sess_\*") {
"`n# Auto-generated exclusions`n$ignoreContent" | Add-Content -Path $gitignorePath
Write-Success "Updated .gitignore"
}
}
}
function Invoke-AutoCommit {
param([string]$message)
Push-Location $RepoPath
try {
Write-Info "Checking for changes..."
$changes = Get-ChangeSummary
if ($changes.Total -eq 0) {
if ($Verbose) { Write-Info "No changes detected" }
return $false
}
Write-Info "Found $($changes.Total) changed files (Modified: $($changes.Modified), Staged: $($changes.Staged), Untracked: $($changes.Untracked))"
# Stage all changes
Write-Info "Staging changes..."
git add -A 2>&1 | Out-Null
# Generate commit message if not provided
if (-not $message) {
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$message = "${CommitPrefix} Update at $timestamp"
# Add file change summary
$fileList = @(git diff --cached --name-only | Select-Object -First 5)
if ($fileList.Count -gt 0) {
$message += "`n`nChanged files:"
foreach ($file in $fileList) {
$message += "`n- $file"
}
if ($changes.Total -gt 5) {
$message += "`n- ... and $($changes.Total - 5) more files"
}
}
$message += "`n`n🤖 Generated with Claude Code Continuous Delivery`n`nCo-Authored-By: Claude <noreply@anthropic.com>"
}
# Create commit
Write-Info "Creating commit..."
$commitResult = git commit -m $message 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Success "Committed changes"
return $true
} else {
Write-Warning "Commit failed or nothing to commit"
if ($Verbose) { Write-Host $commitResult }
return $false
}
} finally {
Pop-Location
}
}
function Invoke-AutoPush {
Push-Location $RepoPath
try {
Write-Info "Pushing to origin/$Branch..."
# Check if we're ahead of remote
$ahead = git rev-list --count "origin/$Branch..$Branch" 2>&1
if ($ahead -match "^\d+$" -and [int]$ahead -gt 0) {
Write-Info "Local is $ahead commit(s) ahead of remote"
# Push with retry logic
$maxRetries = 3
$retryCount = 0
while ($retryCount -lt $maxRetries) {
$pushResult = git push origin $Branch 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Success "Successfully pushed to GitHub"
return $true
} else {
$retryCount++
Write-Warning "Push attempt $retryCount failed"
if ($Verbose) { Write-Host $pushResult }
if ($retryCount -lt $maxRetries) {
Write-Info "Retrying in 5 seconds..."
Start-Sleep -Seconds 5
}
}
}
Write-Error "Failed to push after $maxRetries attempts"
return $false
} else {
if ($Verbose) { Write-Info "Already up to date with remote" }
return $false
}
} finally {
Pop-Location
}
}
function Start-ContinuousDelivery {
Write-Info "========================================="
Write-Info "EasyStream Continuous Delivery Started"
Write-Info "========================================="
Write-Info "Repository: $RepoPath"
Write-Info "Branch: $Branch"
Write-Info "Interval: $IntervalSeconds seconds"
Write-Info "========================================="
Write-Info ""
# Verify git repository
if (-not (Test-GitRepo)) {
Write-Error "Not a git repository: $RepoPath"
exit 1
}
# Update .gitignore
Update-GitIgnore
$iteration = 0
while ($true) {
$iteration++
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Host "`n[$timestamp] Check #$iteration" -ForegroundColor Magenta
try {
# Commit changes
$committed = Invoke-AutoCommit
# Push if there was a commit
if ($committed) {
Start-Sleep -Seconds 2
Invoke-AutoPush
}
# Wait for next interval
Write-Info "Next check in $IntervalSeconds seconds... (Press Ctrl+C to stop)"
Start-Sleep -Seconds $IntervalSeconds
} catch {
Write-Error "Error during CD cycle: $_"
Write-Info "Continuing in 30 seconds..."
Start-Sleep -Seconds 30
}
}
}
function Start-FileWatcher {
Write-Info "========================================="
Write-Info "EasyStream File Watcher Started"
Write-Info "========================================="
Write-Info "Watching: $RepoPath"
Write-Info "Branch: $Branch"
Write-Info "Debounce: 30 seconds after last change"
Write-Info "========================================="
Write-Info ""
# Verify git repository
if (-not (Test-GitRepo)) {
Write-Error "Not a git repository: $RepoPath"
exit 1
}
# Update .gitignore
Update-GitIgnore
# Create file system watcher
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $RepoPath
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
# Exclude patterns
$watcher.Filter = "*.*"
$watcher.NotifyFilter = [System.IO.NotifyFilters]::FileName -bor
[System.IO.NotifyFilters]::DirectoryName -bor
[System.IO.NotifyFilters]::LastWrite
$global:lastChangeTime = Get-Date
$global:changedFiles = @{}
$onChange = {
param($sender, $e)
# Skip excluded patterns
$relativePath = $e.FullPath.Replace($RepoPath, "").TrimStart('\', '/')
$exclude = $false
foreach ($pattern in $excludePatterns) {
if ($relativePath -like $pattern) {
$exclude = $true
break
}
}
if (-not $exclude) {
$global:lastChangeTime = Get-Date
$global:changedFiles[$e.FullPath] = $e.ChangeType
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] " -NoNewline -ForegroundColor Gray
Write-Host "$($e.ChangeType): " -NoNewline -ForegroundColor Yellow
Write-Host $relativePath -ForegroundColor White
}
}
# Register events
Register-ObjectEvent -InputObject $watcher -EventName Changed -Action $onChange | Out-Null
Register-ObjectEvent -InputObject $watcher -EventName Created -Action $onChange | Out-Null
Register-ObjectEvent -InputObject $watcher -EventName Deleted -Action $onChange | Out-Null
Register-ObjectEvent -InputObject $watcher -EventName Renamed -Action $onChange | Out-Null
Write-Success "File watcher active. Monitoring for changes..."
Write-Info "Changes will auto-commit 30 seconds after last modification"
Write-Info ""
try {
while ($true) {
Start-Sleep -Seconds 5
# Check if enough time has passed since last change
$timeSinceLastChange = (Get-Date) - $global:lastChangeTime
if ($global:changedFiles.Count -gt 0 -and $timeSinceLastChange.TotalSeconds -ge 30) {
Write-Info "`nDebounce period elapsed. Processing $($global:changedFiles.Count) changes..."
$committed = Invoke-AutoCommit
if ($committed) {
Start-Sleep -Seconds 2
Invoke-AutoPush
}
# Reset
$global:changedFiles = @{}
Write-Info "`nContinuing to watch for changes...`n"
}
}
} finally {
$watcher.Dispose()
Get-EventSubscriber | Unregister-Event
}
}
# Main execution
if ($WatchMode) {
Start-FileWatcher
} else {
Start-ContinuousDelivery
}