-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/azure devops support #627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- 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.
There was a problem hiding this 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
ProjectMapperclass with provider-specificURLBuildersto 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_PROVIDERenvironment 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}`) |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`) | |
| throw new Error("Unsupported PROJECT_SOURCE_PROVIDER. Supported values are: github, azure-devops.") |
| tenantId: azureDevOpsCredentials.tenantId | ||
| }) | ||
| } | ||
| throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`) |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| } else if (azureDevOpsClient) { | ||
| return new AzureDevOpsBlobProvider({ client: azureDevOpsClient }) | ||
| } | ||
| throw new Error(`No blob provider available for PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`) |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| 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'.") |
| remoteConfigEncoder: remoteConfigEncoder | ||
| }) | ||
| } | ||
| throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`) |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| throw new Error(`Unsupported PROJECT_SOURCE_PROVIDER: ${projectSourceProvider}`) | |
| throw new Error("Unsupported PROJECT_SOURCE_PROVIDER. Supported values are: 'github', 'azure-devops'.") |
| import { OAuthToken, IOAuthTokenRefresher } from "../domain" | ||
|
|
||
| // Microsoft's registered Application ID for Azure DevOps | ||
| const AZURE_DEVOPS_RESOURCE_ID = "499b84ac-1321-427f-aa17-267ca6975798" |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
|
|
||
| const gitHubURLBuilders: URLBuilders<RepositoryWithRefs> = { | ||
| getImageRef(repository: RepositoryWithRefs): string { | ||
| return repository.defaultBranchRef.id! |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| 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; |
| return repository.defaultBranchRef.id! | ||
| }, | ||
| getBlobRef(ref: RepositoryRef): string { | ||
| return ref.id! |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| 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; |
Description
Motivation and Context
Screenshots (if appropriate):
Types of changes