Skip to content

Conversation

@ulrikandersen
Copy link
Contributor

Description

Motivation and Context

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

- Add Microsoft Entra ID authentication via next-auth
- Implement AzureDevOpsClient for REST API interactions
- Add AzureDevOpsProjectDataSource and AzureDevOpsRepositoryDataSource
- Create unified IBlobProvider interface for file content fetching
- Support binary image files in blob API
- Configure via PROJECT_SOURCE_PROVIDER env var (github or azure-devops)
Move repository-to-project mapping logic into a dedicated ProjectMapper
class. The mapper handles filtering, sorting, and all mapping transformations
with generic type support for provider-specific repository types.
Add comprehensive tests for ProjectMapper, Azure DevOps client, blob
providers, and repository data sources. Fix AzureDevOpsError prototype
chain to ensure instanceof checks work correctly.
Copilot AI review requested due to automatic review settings December 6, 2025 20:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds Azure DevOps support to the application, enabling it to work with both GitHub and Azure DevOps as project source providers. The implementation introduces a clean abstraction layer that allows the application to support multiple version control platforms without duplicating business logic.

Key Changes

  • Provider abstraction: Extracted common repository mapping logic into a generic ProjectMapper class with provider-specific URLBuilders to handle platform differences
  • Azure DevOps integration: Added complete Azure DevOps client implementation including OAuth token management, repository data source, and blob provider
  • Configuration-based provider selection: Uses PROJECT_SOURCE_PROVIDER environment variable to switch between GitHub and Azure DevOps at runtime

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/features/projects/domain/ProjectMapper.ts New generic mapper that extracts common project mapping logic from provider-specific implementations
src/features/projects/data/GitHubProjectDataSource.ts Refactored to use ProjectMapper, significantly reducing code duplication
src/features/projects/data/AzureDevOpsProjectDataSource.ts New Azure DevOps project data source using ProjectMapper with Azure-specific URL builders
src/features/projects/data/AzureDevOpsRepositoryDataSource.ts Implements repository fetching and enrichment for Azure DevOps
src/common/azure-devops/AzureDevOpsClient.ts Core Azure DevOps REST API client with authentication and error handling
src/common/azure-devops/OAuthTokenRefreshingAzureDevOpsClient.ts Wrapper that automatically refreshes OAuth tokens on auth errors
src/common/azure-devops/AzureDevOpsError.ts Custom error class for Azure DevOps API failures
src/features/auth/data/AzureDevOpsOAuthTokenRefresher.ts Handles OAuth token refresh using Microsoft Entra ID
src/common/blob/IBlobProvider.ts New abstraction for blob/file content retrieval
src/common/blob/GitHubBlobProvider.ts GitHub implementation of blob provider
src/common/blob/AzureDevOpsBlobProvider.ts Azure DevOps implementation of blob provider
src/composition.ts Updated dependency injection to conditionally instantiate GitHub or Azure DevOps components based on configuration
src/app/api/blob/[owner]/[repository]/[...path]/route.ts Refactored to use generic blob provider abstraction
src/app/api/hooks/github/route.ts Added null check for GitHub webhook handler when using Azure DevOps provider
src/app/auth/signin/page.tsx Dynamic sign-in button that adapts to configured provider
src/features/auth/domain/log-in/LogInHandler.ts Extended to support Microsoft Entra ID provider
.env.example Added documentation for Azure DevOps configuration variables
__test__/** Comprehensive test coverage for new components and refactored code

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

})
]
}
throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`)
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message includes the raw projectSourceProvider value which could come from user input. Consider whether this could expose sensitive information or be used for injection attacks. The error message should be more specific about what values are supported.

Suggested change
throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`)
throw new Error("Unsupported PROJECT_SOURCE_PROVIDER. Supported values are: github, azure-devops.")

Copilot uses AI. Check for mistakes.
tenantId: azureDevOpsCredentials.tenantId
})
}
throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`)
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message includes the raw projectSourceProvider value which could come from user input. Consider whether this could expose sensitive information or be used for injection attacks. The error message should be more specific about what values are supported.

Copilot uses AI. Check for mistakes.
} else if (azureDevOpsClient) {
return new AzureDevOpsBlobProvider({ client: azureDevOpsClient })
}
throw new Error(`No blob provider available for PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`)
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message includes the raw projectSourceProvider value which could come from user input. Consider whether this could expose sensitive information. The error message should be more specific about what values are supported.

Suggested change
throw new Error(`No blob provider available for PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`)
throw new Error("No blob provider available for the configured PROJECT_SOURCE_PROVIDER. Supported values are: 'github', 'azure-devops'.")

Copilot uses AI. Check for mistakes.
remoteConfigEncoder: remoteConfigEncoder
})
}
throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`)
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message includes the raw projectSourceProvider value which could come from user input. Consider whether this could expose sensitive information. The error message should be more specific about what values are supported.

Suggested change
throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`)
throw new Error("Unsupported PROJECT_SOURCE_PROVIDER. Supported values are: 'github', 'azure-devops'.")

Copilot uses AI. Check for mistakes.
import { OAuthToken, IOAuthTokenRefresher } from "../domain"

// Microsoft's registered Application ID for Azure DevOps
const AZURE_DEVOPS_RESOURCE_ID = "499b84ac-1321-427f-aa17-267ca6975798"
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constant AZURE_DEVOPS_RESOURCE_ID is duplicated in composition.ts (line 72). Consider extracting this to a shared constant file to avoid duplication and ensure consistency across the codebase.

Copilot uses AI. Check for mistakes.

const gitHubURLBuilders: URLBuilders<RepositoryWithRefs> = {
getImageRef(repository: RepositoryWithRefs): string {
return repository.defaultBranchRef.id!
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-null assertion operator (!) is used on repository.defaultBranchRef.id, but the type definition allows id to be undefined. This could cause a runtime error if id is not present. Consider handling the undefined case or ensuring the GitHub API always returns an ID.

Suggested change
return repository.defaultBranchRef.id!
if (!repository.defaultBranchRef.id) {
throw new Error("Default branch ref id is undefined for repository: " + repository.name);
}
return repository.defaultBranchRef.id;

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +15
return repository.defaultBranchRef.id!
},
getBlobRef(ref: RepositoryRef): string {
return ref.id!
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-null assertion operator (!) is used on ref.id, but the type definition allows id to be undefined. This could cause a runtime error. Consider handling the undefined case or ensuring refs always have an ID for GitHub.

Suggested change
return repository.defaultBranchRef.id!
},
getBlobRef(ref: RepositoryRef): string {
return ref.id!
if (repository.defaultBranchRef.id === undefined) {
throw new Error("GitHub ref is missing an ID (defaultBranchRef.id is undefined)");
}
return repository.defaultBranchRef.id;
},
getBlobRef(ref: RepositoryRef): string {
if (ref.id === undefined) {
throw new Error("GitHub ref is missing an ID (ref.id is undefined)");
}
return ref.id;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants