Skip to content

Conversation

@ulrikandersen
Copy link
Contributor

Summary

  • Extract repository-to-project mapping logic into a dedicated ProjectMapper class
  • Add generic type support for provider-specific repository types
  • Support optional diff URL generation

Copilot AI review requested due to automatic review settings December 8, 2025 09:24
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 PR extracts the repository-to-project mapping logic from GitHubProjectDataSource into a dedicated, reusable ProjectMapper class. This refactoring introduces generic type support to enable the mapper to work with different Git providers, while moving provider-specific URL generation logic into configurable URL builders. The change improves code organization by separating concerns: the data source now focuses on repository retrieval, while the mapper handles the transformation logic.

Key Changes:

  • Created a new ProjectMapper class with generic type support for provider-agnostic repository mapping
  • Introduced URLBuilders interface for provider-specific URL generation with optional diff and PR URL support
  • Simplified GitHubProjectDataSource to delegate mapping logic to ProjectMapper

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/features/projects/domain/ProjectMapper.ts New file containing extracted mapping logic with generic types (RepositoryRef, RepositoryWithRefs, URLBuilders, and ProjectMapper class) and utility functions
src/features/projects/data/GitHubProjectDataSource.ts Refactored to use ProjectMapper with GitHub-specific URL builders, removing ~260 lines of mapping logic

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

}

export function getBlobURL(owner: string, repository: string, path: string, ref: string): string {
return `/api/blob/${owner}/${repository}/${path}?ref=${ref}`
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

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

The getBlobURL function is missing path encoding that was present in the original implementation. The old code properly encoded each path segment using path.split('/').map(segment => encodeURIComponent(segment)).join('/'), but the new implementation directly uses the unencoded path. This could cause issues with files containing special characters in their names (e.g., spaces, #, ?, etc.).

Consider updating the function to:

export function getBlobURL(owner: string, repository: string, path: string, ref: string): string {
  const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/')
  return `/api/blob/${owner}/${repository}/${encodedPath}?ref=${ref}`
}
Suggested change
return `/api/blob/${owner}/${repository}/${path}?ref=${ref}`
const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/')
return `/api/blob/${owner}/${repository}/${encodedPath}?ref=${ref}`

Copilot uses AI. Check for mistakes.

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

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

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

Using non-null assertion operator (!) on repository.defaultBranchRef.id is unsafe. The RepositoryWithRefs type defines id as optional (readonly id?: string), but the actual GitHubRepository type from the domain layer requires it to be non-optional (readonly id: string). This mismatch could cause runtime errors if the mapper is used with other repository types that don't guarantee an id.

Consider either:

  1. Making the id required in RepositoryRef and RepositoryWithRefs types if it's always expected, or
  2. Providing a fallback value or throwing a descriptive error when id is missing
Suggested change
return repository.defaultBranchRef.id!
if (!repository.defaultBranchRef.id) {
throw new Error("Repository defaultBranchRef.id is missing");
}
return repository.defaultBranchRef.id;

Copilot uses AI. Check for mistakes.
return repository.defaultBranchRef.id!
},
getBlobRef(ref: RepositoryRef): string {
return ref.id!
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

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

Using non-null assertion operator (!) on ref.id is unsafe. The RepositoryRef type defines id as optional (readonly id?: string), but the actual GitHubRepositoryRef type from the domain layer requires it to be non-optional (readonly id: string). This mismatch could cause runtime errors if the mapper is used with other repository types that don't guarantee an id.

Consider either:

  1. Making the id required in RepositoryRef type if it's always expected for GitHub, or
  2. Providing a fallback value or throwing a descriptive error when id is missing
Suggested change
return ref.id!
if (!ref.id) {
throw new Error("RepositoryRef.id is required for getBlobRef but was undefined");
}
return ref.id;

Copilot uses AI. Check for mistakes.
return `https://github.com/${repository.owner}/${repository.name}/edit/${ref.name}/${encodeURIComponent(fileName)}`
},
getDiffUrl(repository: RepositoryWithRefs, ref: RepositoryRef, fileName: string): string | undefined {
if (!ref.baseRefOid || !ref.id) {
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

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

The condition !ref.id is a new addition not present in the original implementation. The old code only checked if (!baseRefOid) before returning undefined. While this additional check may be intentional for safety given the optional nature of ref.id in the new types, it changes the behavior from the original implementation and could cause diff URLs to not be generated in cases where they previously would have been. If this is intentional, it should be documented; otherwise, consider matching the original logic.

Suggested change
if (!ref.baseRefOid || !ref.id) {
if (!ref.baseRefOid) {

Copilot uses AI. Check for mistakes.
.sort((a, b) => a.name.localeCompare(b.name))
}

mapRepositoryToProject(repository: T): Project {
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

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

[nitpick] The mapRepositoryToProject method is public, but the equivalent method in the original implementation (mapProject) was private. Consider marking this as private unless there's a specific need to expose it as part of the public API, to maintain better encapsulation.

Suggested change
mapRepositoryToProject(repository: T): Project {
private mapRepositoryToProject(repository: T): Project {

Copilot uses AI. Check for mistakes.
@ulrikandersen ulrikandersen added the enhancement New feature or request label Dec 10, 2025
@ulrikandersen ulrikandersen force-pushed the feature/extract-project-mapper branch 2 times, most recently from d90ecdc to 3ce2d6b Compare December 11, 2025 14:46
@ulrikandersen ulrikandersen force-pushed the feature/extract-project-mapper branch from 3ce2d6b to 8a37284 Compare December 11, 2025 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants