|
Clear-Host |
|
$ErrorActionPreference = 'Stop' |
|
|
|
# Public parameters (to be changed) |
|
$bitbucketUser = "yourBitbucketUser" |
|
$bitbucketToken = "yourBitbucketToken" |
|
$solutions = "SolutionA.sln, SolutionB.sln" |
|
$packagesToExclude = "Aspose, PdfTools" |
|
$packagesToInclude = "" |
|
$versionLock = "None" # use "None", "Major" or "Minor" |
|
$commitMessage = "chore: automatically update NuGet packages" |
|
$pushAndCreatePullRequest = $true |
|
|
|
# Internal parameters (not to be changed) |
|
$allProjectsIdentifier = "ALL_PROJECTS" |
|
$pullRequestUpdateIdentifier = "Automatic update of NuGet packages" |
|
$guid = New-Guid |
|
$newBranchWithUpdates = "feature/${guid}" |
|
$defaultBranch = git branch --show-current |
|
$remoteOriginUrl = git config --get remote.origin.url |
|
$remoteOriginUrl -match "(?<HostWithProtocol>(?<ProtocolWithSlash>(?<Protocol>http|https):\/\/)(?<Host>[^\/]*))\/scm\/(?<Project>[^\/]*)\/(?<Repository>[^\/]*).git" |
|
$bitbucketHostWithProtocol = $Matches.HostWithProtocol |
|
$bitbucketHost = $Matches.Host |
|
$bitbucketProtocolWithSlash = $Matches.ProtocolWithSlash |
|
$bitbucketProject = $Matches.Project |
|
$bitbucketRepo = $Matches.Repository |
|
$defaultHeaders = @{ |
|
Authorization = "Bearer ${bitbucketToken}" |
|
Accept = "application/json" |
|
} |
|
|
|
# Uncomment the following code if you have to disable certificate validation (e. g. due to self-signed certificates) |
|
# |
|
# [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 |
|
# Add-Type -TypeDefinition @" |
|
# using System.Net; |
|
# using System.Security.Cryptography.X509Certificates; |
|
# public class TrustAllCertsPolicy : ICertificatePolicy |
|
# { |
|
# public bool CheckValidationResult(ServicePoint sp, X509Certificate cert, WebRequest req, int certProblem) |
|
# { |
|
# return true; |
|
# } |
|
# } |
|
# "@ |
|
# [System.Net.ServicePointManager]::CertificatePolicy = New-Object -TypeName TrustAllCertsPolicy |
|
|
|
function installDotnetOutdatedTool { |
|
dotnet tool install --global dotnet-outdated-tool |
|
} |
|
|
|
function runDotnetOutdatedTool { |
|
if ($solutions -eq $allProjectsIdentifier) { |
|
createSolutionWithAllProjects |
|
runDotnetOutdatedToolForSolution("${allProjectsIdentifier}.sln") |
|
deleteSolutionWithAllProjects |
|
} |
|
else { |
|
foreach ($solution in $solutions.Split(",")) { |
|
runDotnetOutdatedToolForSolution($solution) |
|
} |
|
} |
|
} |
|
|
|
function createSolutionWithAllProjects { |
|
& "dotnet" @("new", "sln", "--name", $allProjectsIdentifier) |
|
Get-ChildItem -Path .\ -Filter *.csproj -Recurse -File -Name | ForEach-Object { |
|
& "dotnet" @("sln", "${allProjectsIdentifier}.sln", "add", $_) |
|
} |
|
Write-Host "Created new solution '${allProjectsIdentifier}.sln' with all available C# projects" |
|
} |
|
|
|
function deleteSolutionWithAllProjects { |
|
Remove-Item ".\${allProjectsIdentifier}.sln" |
|
} |
|
|
|
function runDotnetOutdatedToolForSolution($solution) { |
|
$solution = $solution.Trim() |
|
Write-Host "Running 'dotnet outdated' for '${solution}'" |
|
$arguments = @("outdated", "--upgrade", "--version-lock", $versionLock) |
|
|
|
if (![string]::IsNullOrWhiteSpace($packagesToExclude) -and ![string]::IsNullOrWhiteSpace($packagesToInclude)) { |
|
Write-Error "Using both inclusion and exclusion of packages is not supported" |
|
} |
|
|
|
if (![string]::IsNullOrWhiteSpace($packagesToExclude)) { |
|
foreach ($packageToExclude in $packagesToExclude.Split(",")) { |
|
$packageToExclude = $packageToExclude.Trim() |
|
$arguments += "--exclude" |
|
$arguments += $packageToExclude |
|
} |
|
} |
|
|
|
if (![string]::IsNullOrWhiteSpace($packagesToInclude)) { |
|
foreach ($packageToInclude in $packagesToInclude.Split(",")) { |
|
$packageToInclude = $packageToInclude.Trim() |
|
$arguments += "--include" |
|
$arguments += $packageToInclude |
|
} |
|
} |
|
|
|
$arguments += ".\${solution}" |
|
|
|
& "dotnet" $arguments |
|
} |
|
|
|
function hasGitChanges { |
|
$hasChanges = git status --porcelain |
|
|
|
return $hasChanges |
|
} |
|
|
|
function pushGitChangesToOrigin { |
|
$bitbucketRepoUrlWithAuth = getRepoUrlWithAuthentication |
|
|
|
# Why is `git push origin` not enough and authentication has to be specified? |
|
# I developed and tested this script with and for the JetBrains CI solution TeamCity. |
|
# TeamCity clones the repo, but somehow doesn't set up the cloned Git working directory 100% correctly. |
|
# Therefore the Git credential manager pops up when using `git push origin'. |
|
# This is solved by manually authenticating against Bitbucket. |
|
git push $bitbucketRepoUrlWithAuth $newBranchWithUpdates |
|
} |
|
|
|
function getRepoUrlWithAuthentication { |
|
$encodedUser = [System.Web.HttpUtility]::UrlEncode($bitbucketUser) |
|
$encodedToken = [System.Web.HttpUtility]::UrlEncode($bitbucketToken) |
|
|
|
return "${bitbucketProtocolWithSlash}${encodedUser}:${encodedToken}@${bitbucketHost}/scm/${bitbucketProject}/${bitbucketRepo}.git" -replace " ","" |
|
} |
|
|
|
function createNewGitBranch { |
|
git checkout -b $newBranchWithUpdates |
|
} |
|
|
|
function commitGitChanges { |
|
git add -A |
|
git commit -m $commitMessage |
|
} |
|
|
|
function switchToDefaultBranch { |
|
git checkout $defaultBranch |
|
} |
|
|
|
function persistGitChanges { |
|
createNewGitBranch |
|
commitGitChanges |
|
pushGitChangesToOrigin |
|
switchToDefaultBranch |
|
} |
|
|
|
function createPullRequestForChanges { |
|
persistGitChanges |
|
createPullRequest |
|
} |
|
|
|
function branchAsRefObject($branchName) { |
|
return @{ |
|
displayId = $branchName |
|
id = "refs/heads/${branchName}" |
|
type = "BRANCH" |
|
} |
|
} |
|
|
|
function getRepoId { |
|
$url = "${bitbucketHostWithProtocol}/rest/api/1.0/projects/${bitbucketProject}/repos/${bitbucketRepo}" |
|
$result = Invoke-RestMethod -Uri $url -Headers $defaultHeaders -ContentType "application/json" |
|
|
|
return $result.id |
|
} |
|
|
|
function getDefaultReviewers ($sourceBranch, $targetBranch) { |
|
$repoId = getRepoId |
|
|
|
$url = "${bitbucketHostWithProtocol}/rest/default-reviewers/1.0/projects/${bitbucketProject}/repos/${bitbucketRepo}/reviewers?sourceRepoId=${repoId}&targetRepoId=${repoId}&sourceRefId=refs/heads/${sourceBranch}&targetRefId=refs/heads/${targetBranch}" |
|
$results = Invoke-RestMethod -Uri $url -Headers $defaultHeaders -ContentType "application/json" |
|
|
|
$reviewers = @() |
|
foreach ($result in $results) { |
|
$reviewers += @{ |
|
user = @{ |
|
name = $result.name |
|
} |
|
} |
|
} |
|
|
|
return $reviewers |
|
} |
|
|
|
function createPullRequest { |
|
$url = "${bitbucketHostWithProtocol}/rest/api/1.0/projects/${bitbucketProject}/repos/${bitbucketRepo}/pull-requests" |
|
$body = @{ |
|
fromRef = branchAsRefObject $newBranchWithUpdates |
|
toRef = branchAsRefObject $defaultBranch |
|
reviewers = getDefaultReviewers $newBranchWithUpdates $defaultBranch |
|
title = $pullRequestUpdateIdentifier |
|
} | ConvertTo-Json -Depth 4 |
|
|
|
Invoke-RestMethod -Method Post -Uri $url -Headers $defaultHeaders -Body $body -ContentType "application/json" |
|
|
|
Write-Host "Created new PR from branch '${newBranchWithUpdates}' to branch '${defaultBranch}'" |
|
} |
|
|
|
function hasPendingPullRequest { |
|
$url = "${bitbucketHostWithProtocol}/rest/api/1.0/projects/${bitbucketProject}/repos/${bitbucketRepo}/pull-requests" |
|
$result = Invoke-RestMethod -Uri $url -Headers $defaultHeaders -ContentType "application/json" |
|
|
|
foreach ($openPullRequest in $result.values) { |
|
$pullRequestName = $openPullRequest.title |
|
Write-Host "Found existing PR '${pullRequestName}'" |
|
|
|
if ($pullRequestName -eq $pullRequestUpdateIdentifier) { |
|
Write-Host "Current PR '${pullRequestName}' matches '${pullRequestUpdateIdentifier}'" |
|
return $true |
|
} |
|
} |
|
|
|
Write-Host "No PR matched '${pullRequestUpdateIdentifier}'" |
|
return $false |
|
} |
|
|
|
function main { |
|
if (hasPendingPullRequest) { |
|
Write-Host "Pending update PR found, exiting" |
|
return |
|
} |
|
|
|
installDotnetOutdatedTool |
|
|
|
runDotnetOutdatedTool |
|
|
|
if (hasGitChanges) { |
|
Write-Host "Git changes detected, creating PR for changes" |
|
if ($pushAndCreatePullRequest) { |
|
createPullRequestForChanges |
|
} |
|
} |
|
} |
|
|
|
# Execute the main logic |
|
main |