Public/Invoke-RepoWatch.ps1
|
function Invoke-RepoWatch { <# .SYNOPSIS Main orchestrator: checks GitHub repos and PSGallery, builds digest, sends email. .DESCRIPTION Calls Get-RepoActivity to check all repositories for new issues, comments, PRs, star and fork changes. Optionally calls Get-PSGalleryStats for download tracking. Generates an HTML digest report and optionally sends it via email. Updates the state file for delta tracking between runs. .PARAMETER Owner GitHub username or organization name. .PARAMETER Token GitHub personal access token. Falls back to $env:GITHUB_TOKEN. .PARAMETER SmtpServer SMTP server hostname. Required when -SendEmail is specified. .PARAMETER EmailTo Recipient email address(es). Required when -SendEmail is specified. .PARAMETER EmailFrom Sender email address. Required when -SendEmail is specified. .PARAMETER Port SMTP port. Default 587. .PARAMETER UseSsl Use SSL/TLS for the SMTP connection. .PARAMETER SmtpCredential PSCredential for SMTP authentication. .PARAMETER SinceHours Number of hours to look back for activity. Default 24. Range 1-720. .PARAMETER StatePath Path to the state JSON file. Default: $env:USERPROFILE\.repowatch\state.json .PARAMETER SendEmail Send the digest via email. .PARAMETER OutputPath Save the HTML report to a local file. .PARAMETER IncludePSGallery Include PowerShell Gallery download stats in the digest. .PARAMETER PSGalleryAuthor Author name to search on PSGallery. Defaults to Owner. .PARAMETER RepoFilter Specific repository names to check. .PARAMETER ExcludeRepos Repository names to skip. .PARAMETER IncludeForks Include forked repositories. .PARAMETER SkipIfEmpty Do not send email if there is no activity to report. .EXAMPLE Invoke-RepoWatch -Owner "larro1991" -SendEmail -SmtpServer "smtp.gmail.com" -EmailTo "me@example.com" -EmailFrom "me@example.com" -IncludePSGallery .EXAMPLE Invoke-RepoWatch -Owner "larro1991" -OutputPath "C:\Reports\digest.html" #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Owner, [Parameter()] [string]$Token, [Parameter()] [string]$SmtpServer, [Parameter()] [string[]]$EmailTo, [Parameter()] [string]$EmailFrom, [Parameter()] [int]$Port = 587, [Parameter()] [switch]$UseSsl, [Parameter()] [System.Management.Automation.PSCredential]$SmtpCredential, [Parameter()] [ValidateRange(1, 720)] [int]$SinceHours = 24, [Parameter()] [string]$StatePath = (Join-Path $env:USERPROFILE '.repowatch\state.json'), [Parameter()] [switch]$SendEmail, [Parameter()] [string]$OutputPath, [Parameter()] [switch]$IncludePSGallery, [Parameter()] [string]$PSGalleryAuthor, [Parameter()] [string[]]$RepoFilter, [Parameter()] [string[]]$ExcludeRepos, [Parameter()] [switch]$IncludeForks, [Parameter()] [switch]$SkipIfEmpty ) # Validate email parameters when SendEmail is specified if ($SendEmail) { if (-not $SmtpServer) { Write-Error '-SmtpServer is required when -SendEmail is specified.'; return } if (-not $EmailTo) { Write-Error '-EmailTo is required when -SendEmail is specified.'; return } if (-not $EmailFrom) { Write-Error '-EmailFrom is required when -SendEmail is specified.'; return } } Write-Host "RepoWatch: Checking activity for '$Owner' (last $SinceHours hours)..." -ForegroundColor Cyan # ----- Step 1: Get repository activity ----- $activityParams = @{ Owner = $Owner SinceHours = $SinceHours StatePath = $StatePath } if ($Token) { $activityParams['Token'] = $Token } if ($RepoFilter) { $activityParams['RepoFilter'] = $RepoFilter } if ($ExcludeRepos) { $activityParams['ExcludeRepos'] = $ExcludeRepos } if ($IncludeForks) { $activityParams['IncludeForks'] = $true } $activity = Get-RepoActivity @activityParams # ----- Step 2: Optionally get PSGallery stats ----- $galleryStats = $null if ($IncludePSGallery) { $galleryAuthor = if ($PSGalleryAuthor) { $PSGalleryAuthor } else { $Owner } Write-Host "RepoWatch: Checking PowerShell Gallery for '$galleryAuthor'..." -ForegroundColor Cyan $galleryStats = Get-PSGalleryStats -Author $galleryAuthor -StatePath $StatePath } # ----- Step 3: Calculate summary ----- $activeRepos = @($activity | Where-Object { $_.HasActivity }) $totalNewIssues = 0 $totalNewComments = 0 $totalNewPRs = 0 foreach ($repo in $activeRepos) { if ($repo.NewIssues) { $totalNewIssues += @($repo.NewIssues).Count } if ($repo.NewComments) { $totalNewComments += @($repo.NewComments).Count } if ($repo.NewPRs) { $totalNewPRs += @($repo.NewPRs).Count } } $totalDownloads = 0 if ($galleryStats) { foreach ($mod in $galleryStats) { $totalDownloads += $mod.DownloadChange } } # Console summary $summaryParts = @("$($activeRepos.Count) repos with activity") if ($totalNewIssues -gt 0) { $summaryParts += "$totalNewIssues new issue$(if ($totalNewIssues -ne 1){'s'})" } if ($totalNewComments -gt 0) { $summaryParts += "$totalNewComments new comment$(if ($totalNewComments -ne 1){'s'})" } if ($totalNewPRs -gt 0) { $summaryParts += "$totalNewPRs new PR$(if ($totalNewPRs -ne 1){'s'})" } if ($totalDownloads -gt 0) { $summaryParts += "$totalDownloads new PSGallery download$(if ($totalDownloads -ne 1){'s'})" } Write-Host "RepoWatch: $($summaryParts -join ', ')" -ForegroundColor Green # ----- Step 4: Generate HTML digest ----- $htmlContent = New-HtmlDigest -Activity $activity -PSGalleryStats $galleryStats # ----- Step 5: Save HTML report locally if OutputPath specified ----- if ($OutputPath) { $outputDir = Split-Path -Path $OutputPath -Parent if ($outputDir -and -not (Test-Path -Path $outputDir)) { New-Item -Path $outputDir -ItemType Directory -Force | Out-Null } [System.IO.File]::WriteAllText( $OutputPath, $htmlContent, [System.Text.UTF8Encoding]::new($true) ) Write-Host "RepoWatch: HTML report saved to $OutputPath" -ForegroundColor Cyan } # ----- Step 6: Send email if requested ----- $emailResult = $null if ($SendEmail) { $emailParams = @{ Activity = $activity SmtpServer = $SmtpServer EmailTo = $EmailTo EmailFrom = $EmailFrom Port = $Port } if ($galleryStats) { $emailParams['PSGalleryStats'] = $galleryStats } if ($UseSsl) { $emailParams['UseSsl'] = $true } if ($SmtpCredential) { $emailParams['Credential'] = $SmtpCredential } if ($SkipIfEmpty) { $emailParams['SkipIfEmpty'] = $true } $emailResult = Send-ActivityDigest @emailParams if ($emailResult.Status -eq 'Sent') { Write-Host "RepoWatch: Digest email sent to $($EmailTo -join ', ')" -ForegroundColor Green } elseif ($emailResult.Status -eq 'Skipped') { Write-Host 'RepoWatch: No activity - email skipped.' -ForegroundColor Yellow } else { Write-Host "RepoWatch: Email send failed - $($emailResult.Reason)" -ForegroundColor Red } } # ----- Step 7: Update state file ----- $reposState = @{} foreach ($repo in $activity) { $reposState[$repo.RepoName] = @{ stars = $repo.Stars forks = $repo.Forks } } $galleryState = @{} if ($galleryStats) { foreach ($mod in $galleryStats) { $galleryState[$mod.ModuleName] = @{ downloads = $mod.TotalDownloads } } } $stateData = @{ last_check = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') repos = $reposState psgallery = $galleryState } Get-LastCheckTime -StatePath $StatePath -Owner $Owner -Write -Data $stateData Write-Verbose "State file updated at: $StatePath" # ----- Return summary ----- return [PSCustomObject]@{ Owner = $Owner TotalRepos = @($activity).Count ActiveRepos = $activeRepos.Count NewIssues = $totalNewIssues NewComments = $totalNewComments NewPRs = $totalNewPRs PSGalleryModules = if ($galleryStats) { @($galleryStats).Count } else { 0 } NewDownloads = $totalDownloads EmailStatus = if ($emailResult) { $emailResult.Status } else { 'NotRequested' } HtmlReportPath = $OutputPath Activity = $activity PSGalleryStats = $galleryStats } } |