873cafa46f
## Summary - Add support for organization repositories by including org members in repo list API - Add repository reference normalization to handle full GitHub URLs, SSH URLs, and owner/repo format - Apply normalization to all GitHub API calls to ensure consistent behavior ## Changes Made ### 1. Enhanced repo listing (repository-handlers.ts) - Updated `/user/repos` endpoint to include affiliation parameter - Now fetches: owner, collaborator, and organization_member repos - Fixes #20: Organization repos now appear in repository list ### 2. Added URL normalization utility (utils.ts) - New `normalizeRepoReference()` function handles: - owner/repo format (already normalized) - https://github.com/owner/repo URLs - https://github.com/owner/repo.git URLs - git@github.com:owner/repo.git SSH URLs - Prevents 404 errors from malformed repository references ### 3. Applied normalization consistently - Updated repository-handlers.ts to normalize repo refs before API calls - Updated issue-handlers.ts to normalize repo refs before API calls - All GitHub API calls now use normalized repository format ## Testing - Verified with organization repository: imaginationeverywhere/ppsv-charities - Tested with various URL formats - GitHub CLI authentication continues to work seamlessly ## Related Issues Fixes #20: Auto Claude doesn't work with GitHub Organization repositories
185 lines
5.2 KiB
TypeScript
185 lines
5.2 KiB
TypeScript
/**
|
|
* GitHub issue-related IPC handlers
|
|
*/
|
|
|
|
import { ipcMain } from 'electron';
|
|
import { IPC_CHANNELS } from '../../../shared/constants';
|
|
import type { IPCResult, GitHubIssue } from '../../../shared/types';
|
|
import { projectStore } from '../../project-store';
|
|
import { getGitHubConfig, githubFetch, normalizeRepoReference } from './utils';
|
|
import type { GitHubAPIIssue, GitHubAPIComment } from './types';
|
|
|
|
/**
|
|
* Transform GitHub API issue to application format
|
|
*/
|
|
function transformIssue(issue: GitHubAPIIssue, repoFullName: string): GitHubIssue {
|
|
return {
|
|
id: issue.id,
|
|
number: issue.number,
|
|
title: issue.title,
|
|
body: issue.body,
|
|
state: issue.state,
|
|
labels: issue.labels,
|
|
assignees: issue.assignees.map(a => ({
|
|
login: a.login,
|
|
avatarUrl: a.avatar_url
|
|
})),
|
|
author: {
|
|
login: issue.user.login,
|
|
avatarUrl: issue.user.avatar_url
|
|
},
|
|
milestone: issue.milestone,
|
|
createdAt: issue.created_at,
|
|
updatedAt: issue.updated_at,
|
|
closedAt: issue.closed_at,
|
|
commentsCount: issue.comments,
|
|
url: issue.url,
|
|
htmlUrl: issue.html_url,
|
|
repoFullName
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get list of issues from repository
|
|
*/
|
|
export function registerGetIssues(): void {
|
|
ipcMain.handle(
|
|
IPC_CHANNELS.GITHUB_GET_ISSUES,
|
|
async (_, projectId: string, state: 'open' | 'closed' | 'all' = 'open'): Promise<IPCResult<GitHubIssue[]>> => {
|
|
const project = projectStore.getProject(projectId);
|
|
if (!project) {
|
|
return { success: false, error: 'Project not found' };
|
|
}
|
|
|
|
const config = getGitHubConfig(project);
|
|
if (!config) {
|
|
return { success: false, error: 'No GitHub token or repository configured' };
|
|
}
|
|
|
|
try {
|
|
const normalizedRepo = normalizeRepoReference(config.repo);
|
|
if (!normalizedRepo) {
|
|
return {
|
|
success: false,
|
|
error: 'Invalid repository format. Use owner/repo or GitHub URL.'
|
|
};
|
|
}
|
|
|
|
const issues = await githubFetch(
|
|
config.token,
|
|
`/repos/${normalizedRepo}/issues?state=${state}&per_page=100&sort=updated`
|
|
) as GitHubAPIIssue[];
|
|
|
|
// Filter out pull requests
|
|
const issuesOnly = issues.filter(issue => !issue.pull_request);
|
|
|
|
const result: GitHubIssue[] = issuesOnly.map(issue =>
|
|
transformIssue(issue, normalizedRepo)
|
|
);
|
|
|
|
return { success: true, data: result };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to fetch issues'
|
|
};
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get a single issue by number
|
|
*/
|
|
export function registerGetIssue(): void {
|
|
ipcMain.handle(
|
|
IPC_CHANNELS.GITHUB_GET_ISSUE,
|
|
async (_, projectId: string, issueNumber: number): Promise<IPCResult<GitHubIssue>> => {
|
|
const project = projectStore.getProject(projectId);
|
|
if (!project) {
|
|
return { success: false, error: 'Project not found' };
|
|
}
|
|
|
|
const config = getGitHubConfig(project);
|
|
if (!config) {
|
|
return { success: false, error: 'No GitHub token or repository configured' };
|
|
}
|
|
|
|
try {
|
|
const normalizedRepo = normalizeRepoReference(config.repo);
|
|
if (!normalizedRepo) {
|
|
return {
|
|
success: false,
|
|
error: 'Invalid repository format. Use owner/repo or GitHub URL.'
|
|
};
|
|
}
|
|
|
|
const issue = await githubFetch(
|
|
config.token,
|
|
`/repos/${normalizedRepo}/issues/${issueNumber}`
|
|
) as GitHubAPIIssue;
|
|
|
|
const result = transformIssue(issue, normalizedRepo);
|
|
|
|
return { success: true, data: result };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to fetch issue'
|
|
};
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get comments for a specific issue
|
|
*/
|
|
export function registerGetIssueComments(): void {
|
|
ipcMain.handle(
|
|
IPC_CHANNELS.GITHUB_GET_ISSUE_COMMENTS,
|
|
async (_, projectId: string, issueNumber: number): Promise<IPCResult<GitHubAPIComment[]>> => {
|
|
const project = projectStore.getProject(projectId);
|
|
if (!project) {
|
|
return { success: false, error: 'Project not found' };
|
|
}
|
|
|
|
const config = getGitHubConfig(project);
|
|
if (!config) {
|
|
return { success: false, error: 'No GitHub token or repository configured' };
|
|
}
|
|
|
|
try {
|
|
const normalizedRepo = normalizeRepoReference(config.repo);
|
|
if (!normalizedRepo) {
|
|
return {
|
|
success: false,
|
|
error: 'Invalid repository format. Use owner/repo or GitHub URL.'
|
|
};
|
|
}
|
|
|
|
const comments = await githubFetch(
|
|
config.token,
|
|
`/repos/${normalizedRepo}/issues/${issueNumber}/comments`
|
|
) as GitHubAPIComment[];
|
|
|
|
return { success: true, data: comments };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to fetch issue comments'
|
|
};
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register all issue-related handlers
|
|
*/
|
|
export function registerIssueHandlers(): void {
|
|
registerGetIssues();
|
|
registerGetIssue();
|
|
registerGetIssueComments();
|
|
}
|