Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions CopilotCodeReviewV1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ async function run(): Promise<void> {
// Get Azure DevOps authentication settings
const useSystemAccessToken = tl.getBoolInput('useSystemAccessToken', false);
const azureDevOpsPat = tl.getInput('azureDevOpsPat');
const onPremise = tl.getBoolInput('onPremise', false);
const systemCollectionUri = tl.getVariable('System.CollectionUri') ||
process.env['SYSTEM_COLLECTIONURI'] ||
'';

process.env['AZUREDEVOPS_ONPREMISE'] = onPremise ? 'true' : 'false';

if (onPremise && !systemCollectionUri) {
tl.setResult(tl.TaskResult.Failed,
'On-Premise mode is enabled, but System.CollectionUri is not available. ' +
'Ensure the pipeline provides $(System.CollectionUri) or disable On-Premise mode.');
return;
}

// Determine which token and auth type to use
let azureDevOpsToken: string;
Expand Down Expand Up @@ -106,19 +119,30 @@ async function run(): Promise<void> {
let project = tl.getInput('project');
let repository = tl.getInput('repository');

// Auto-detect organization from System.CollectionUri if not provided
// CollectionUri format: https://dev.azure.com/orgname/ or https://orgname.visualstudio.com/
if (!organization) {
const collectionUri = tl.getVariable('System.CollectionUri');
if (collectionUri) {
const devAzureMatch = collectionUri.match(/https:\/\/dev\.azure\.com\/([^\/]+)/);
const vstsMatch = collectionUri.match(/https:\/\/([^\.]+)\.visualstudio\.com/);
if (devAzureMatch) {
organization = devAzureMatch[1];
console.log(`Auto-detected organization from CollectionUri: ${organization}`);
} else if (vstsMatch) {
organization = vstsMatch[1];
console.log(`Auto-detected organization from CollectionUri: ${organization}`);
// Auto-detect organization/collection from System.CollectionUri if not provided
// CollectionUri format examples:
// https://dev.azure.com/orgname/
// https://orgname.visualstudio.com/
// https://tfs.contoso.com/tfs/DefaultCollection/
if (!organization && systemCollectionUri) {
const devAzureMatch = systemCollectionUri.match(/https:\/\/dev\.azure\.com\/([^\/]+)/);
const vstsMatch = systemCollectionUri.match(/https:\/\/([^\.]+)\.visualstudio\.com/);
if (devAzureMatch) {
organization = devAzureMatch[1];
console.log(`Auto-detected organization from CollectionUri: ${organization}`);
} else if (vstsMatch) {
organization = vstsMatch[1];
console.log(`Auto-detected organization from CollectionUri: ${organization}`);
} else {
try {
const url = new URL(systemCollectionUri);
const segments = url.pathname.split('/').filter(Boolean);
if (segments.length > 0) {
organization = segments[segments.length - 1];
console.log(`Auto-detected collection from CollectionUri: ${organization}`);
}
} catch {
// Ignore parse errors; organization may be provided explicitly
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions CopilotCodeReviewV1/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions CopilotCodeReviewV1/scripts/Add-AzureDevOpsPRComment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ param(
[int]$IterationId
)

# Shared URL helpers
. "$PSScriptRoot\AzureDevOpsUrl.ps1"

#region Helper Functions

function Get-AuthorizationHeader {
Expand Down Expand Up @@ -229,7 +232,11 @@ function Format-AzureDevOpsFilePath {
#region Main Logic

$headers = Get-AuthorizationHeader -Token $Token -AuthType $AuthType
$baseUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$Repository/pullrequests/$Id"
$baseUrls = Get-AzureDevOpsBaseUrls -Project $Project -Organization $Organization
if ($null -eq $baseUrls) {
exit 1
}
$baseUrl = "$($baseUrls.ApiBaseUrl)/git/repositories/$Repository/pullrequests/$Id"
$apiVersion = "api-version=7.1"

# First, verify the PR exists
Expand Down Expand Up @@ -401,7 +408,7 @@ else {
}

# Provide link to the PR
$webUrl = "https://dev.azure.com/$Organization/$Project/_git/$Repository/pullrequest/$Id"
$webUrl = "$($baseUrls.WebBaseUrl)/_git/$Repository/pullrequest/$Id"
Write-Host "`nView PR: $webUrl" -ForegroundColor Cyan

#endregion
60 changes: 60 additions & 0 deletions CopilotCodeReviewV1/scripts/AzureDevOpsUrl.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<#
.SYNOPSIS
Shared helpers for building Azure DevOps REST and web base URLs.

.DESCRIPTION
Centralizes URL construction for Azure DevOps Services and Server (on-prem).
Uses $env:AZUREDEVOPS_ONPREMISE to determine whether to use on-prem URLs.
#>

function Get-AzureDevOpsBaseUrls {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Project,

[Parameter(Mandatory = $false)]
[string]$Organization,

[Parameter(Mandatory = $false)]
[switch]$Silent
)

$onPremise = $false
if ($env:AZUREDEVOPS_ONPREMISE -match '^(?i:true|1|yes)$') {
$onPremise = $true
}

$collectionUri = $null
if ($onPremise) {
$collectionUri = $env:SYSTEM_COLLECTIONURI
if ([string]::IsNullOrWhiteSpace($collectionUri)) {
if (-not $Silent) {
Write-Error "SYSTEM_COLLECTIONURI is required when On-Premise mode is enabled."
}
return $null
}
$collectionUri = $collectionUri.Trim()
if (-not $collectionUri.EndsWith('/')) {
$collectionUri += '/'
}
}
else {
if ([string]::IsNullOrWhiteSpace($Organization)) {
if (-not $Silent) {
Write-Error "Organization is required when On-Premise mode is disabled."
}
return $null
}
}

$apiBaseUrl = if ($onPremise) { "$collectionUri$Project/_apis" } else { "https://dev.azure.com/$Organization/$Project/_apis" }
$webBaseUrl = if ($onPremise) { "$collectionUri$Project" } else { "https://dev.azure.com/$Organization/$Project" }

return [PSCustomObject]@{
ApiBaseUrl = $apiBaseUrl
WebBaseUrl = $webBaseUrl
OnPremise = $onPremise
CollectionUri = $collectionUri
}
}
10 changes: 8 additions & 2 deletions CopilotCodeReviewV1/scripts/Delete-CopilotComment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ param(
[int]$CommentId
)

# Shared URL helpers
. "$PSScriptRoot\AzureDevOpsUrl.ps1"

# Wrap entire script in try/catch for silent failure
try {
# Read credentials from environment variables
Expand All @@ -61,7 +64,6 @@ try {

# Validate required environment variables
if ([string]::IsNullOrEmpty($token) -or
[string]::IsNullOrEmpty($organization) -or
[string]::IsNullOrEmpty($project) -or
[string]::IsNullOrEmpty($repository) -or
[string]::IsNullOrEmpty($prId)) {
Expand Down Expand Up @@ -90,7 +92,11 @@ try {
}

# Build the API URL for deleting a comment
$baseUrl = "https://dev.azure.com/$organization/$project/_apis"
$baseUrls = Get-AzureDevOpsBaseUrls -Project $project -Organization $organization -Silent
if ($null -eq $baseUrls) {
exit 0
}
$baseUrl = $baseUrls.ApiBaseUrl
$uri = "$baseUrl/git/repositories/$repository/pullrequests/$prId/threads/$ThreadId/comments/$CommentId`?api-version=7.1"

# Send DELETE request
Expand Down
11 changes: 9 additions & 2 deletions CopilotCodeReviewV1/scripts/Get-AzureDevOpsPR.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ param(
[string]$OutputFile
)

# Shared URL helpers
. "$PSScriptRoot\AzureDevOpsUrl.ps1"

#region Helper Functions

function Write-Output-Line {
Expand Down Expand Up @@ -275,7 +278,11 @@ $script:OutputToFile = -not [string]::IsNullOrEmpty($OutputFile)
$script:OutputBuilder = [System.Text.StringBuilder]::new()

$headers = Get-AuthorizationHeader -Token $Token -AuthType $AuthType
$baseUrl = "https://dev.azure.com/$Organization/$Project/_apis"
$baseUrls = Get-AzureDevOpsBaseUrls -Project $Project -Organization $Organization
if ($null -eq $baseUrls) {
exit 1
}
$baseUrl = $baseUrls.ApiBaseUrl
$apiVersion = "api-version=7.1"

# If a specific PR ID is provided, get detailed information
Expand Down Expand Up @@ -487,7 +494,7 @@ if ($Id -gt 0) {
}

Write-Output-Line "`n[Links]" -ForegroundColor Yellow
$webUrl = "https://dev.azure.com/$Organization/$Project/_git/$($pr.repository.name)/pullrequest/$($pr.pullRequestId)"
$webUrl = "$($baseUrls.WebBaseUrl)/_git/$($pr.repository.name)/pullrequest/$($pr.pullRequestId)"
Write-Output-Line " Web URL: $webUrl"

Write-Output-Line ("`n" + ("=" * 80)) -ForegroundColor DarkGray
Expand Down
11 changes: 9 additions & 2 deletions CopilotCodeReviewV1/scripts/Get-AzureDevOpsPRChanges.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ param(
[string]$OutputFile
)

# Shared URL helpers
. "$PSScriptRoot\AzureDevOpsUrl.ps1"

#region Helper Functions

function Write-Output-Line {
Expand Down Expand Up @@ -186,7 +189,11 @@ $script:OutputToFile = -not [string]::IsNullOrEmpty($OutputFile)
$script:OutputBuilder = [System.Text.StringBuilder]::new()

$headers = Get-AuthorizationHeader -Token $Token -AuthType $AuthType
$baseUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$Repository/pullrequests/$Id"
$baseUrls = Get-AzureDevOpsBaseUrls -Project $Project -Organization $Organization
if ($null -eq $baseUrls) {
exit 1
}
$baseUrl = "$($baseUrls.ApiBaseUrl)/git/repositories/$Repository/pullrequests/$Id"
$apiVersion = "api-version=7.1"

# Verify the PR exists
Expand Down Expand Up @@ -299,7 +306,7 @@ else {
Write-Output-Line ("`n" + ("=" * 80)) -ForegroundColor DarkGray

# Provide link to the PR
$webUrl = "https://dev.azure.com/$Organization/$Project/_git/$Repository/pullrequest/$Id"
$webUrl = "$($baseUrls.WebBaseUrl)/_git/$Repository/pullrequest/$Id"
Write-Host "`nView PR: $webUrl" -ForegroundColor Cyan
if ($script:OutputToFile) {
$script:OutputBuilder.AppendLine("`nView PR: $webUrl") | Out-Null
Expand Down
10 changes: 8 additions & 2 deletions CopilotCodeReviewV1/scripts/Update-CopilotComment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ param(
[string]$Content
)

# Shared URL helpers
. "$PSScriptRoot\AzureDevOpsUrl.ps1"

# Wrap entire script in try/catch for silent failure
try {
# Validate that at least one update is requested
Expand All @@ -93,7 +96,6 @@ try {

# Validate required environment variables
if ([string]::IsNullOrEmpty($token) -or
[string]::IsNullOrEmpty($organization) -or
[string]::IsNullOrEmpty($project) -or
[string]::IsNullOrEmpty($repository) -or
[string]::IsNullOrEmpty($prId)) {
Expand Down Expand Up @@ -121,7 +123,11 @@ try {
}
}

$baseUrl = "https://dev.azure.com/$organization/$project/_apis"
$baseUrls = Get-AzureDevOpsBaseUrls -Project $project -Organization $organization -Silent
if ($null -eq $baseUrls) {
exit 0
}
$baseUrl = $baseUrls.ApiBaseUrl

# Update thread status if specified
if (-not [string]::IsNullOrEmpty($Status)) {
Expand Down
10 changes: 9 additions & 1 deletion CopilotCodeReviewV1/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
"defaultValue": false,
"helpMarkDown": "When enabled, uses the pipeline's System.AccessToken (OAuth) instead of a PAT. This is the Microsoft-recommended authentication method for Azure DevOps Services. **Note**: The Build Service identity must have 'Contribute to pull requests' permission on the repository. Not supported for Azure DevOps Server (on-prem)."
},
{
"name": "onPremise",
"type": "boolean",
"label": "On-Premise (Azure DevOps Server)",
"required": false,
"defaultValue": false,
"helpMarkDown": "Enable for Azure DevOps Server (on-prem). When enabled, the task uses $(System.CollectionUri) as the API base URL."
},
{
"name": "organization",
"type": "string",
Expand Down Expand Up @@ -127,4 +135,4 @@
"allowed": []
}
}
}
}