Fix GitHub organization repository support
## 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
This commit is contained in:
@@ -6,7 +6,7 @@ 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 } from './utils';
|
||||
import { getGitHubConfig, githubFetch, normalizeRepoReference } from './utils';
|
||||
import type { GitHubAPIIssue, GitHubAPIComment } from './types';
|
||||
|
||||
/**
|
||||
@@ -57,16 +57,24 @@ export function registerGetIssues(): void {
|
||||
}
|
||||
|
||||
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/${config.repo}/issues?state=${state}&per_page=100&sort=updated`
|
||||
`/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, config.repo)
|
||||
transformIssue(issue, normalizedRepo)
|
||||
);
|
||||
|
||||
return { success: true, data: result };
|
||||
@@ -98,12 +106,20 @@ export function registerGetIssue(): void {
|
||||
}
|
||||
|
||||
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/${config.repo}/issues/${issueNumber}`
|
||||
`/repos/${normalizedRepo}/issues/${issueNumber}`
|
||||
) as GitHubAPIIssue;
|
||||
|
||||
const result = transformIssue(issue, config.repo);
|
||||
const result = transformIssue(issue, normalizedRepo);
|
||||
|
||||
return { success: true, data: result };
|
||||
} catch (error) {
|
||||
@@ -134,9 +150,17 @@ export function registerGetIssueComments(): void {
|
||||
}
|
||||
|
||||
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/${config.repo}/issues/${issueNumber}/comments`
|
||||
`/repos/${normalizedRepo}/issues/${issueNumber}/comments`
|
||||
) as GitHubAPIComment[];
|
||||
|
||||
return { success: true, data: comments };
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ipcMain } from 'electron';
|
||||
import { IPC_CHANNELS } from '../../../shared/constants';
|
||||
import type { IPCResult, GitHubRepository, GitHubSyncStatus } from '../../../shared/types';
|
||||
import { projectStore } from '../../project-store';
|
||||
import { getGitHubConfig, githubFetch } from './utils';
|
||||
import { getGitHubConfig, githubFetch, normalizeRepoReference } from './utils';
|
||||
import type { GitHubAPIRepository } from './types';
|
||||
|
||||
/**
|
||||
@@ -33,16 +33,28 @@ export function registerCheckConnection(): void {
|
||||
}
|
||||
|
||||
try {
|
||||
// Normalize repo reference (handles full URLs, git URLs, etc.)
|
||||
const normalizedRepo = normalizeRepoReference(config.repo);
|
||||
if (!normalizedRepo) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
connected: false,
|
||||
error: 'Invalid repository format. Use owner/repo or GitHub URL.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch repo info
|
||||
const repoData = await githubFetch(
|
||||
config.token,
|
||||
`/repos/${config.repo}`
|
||||
`/repos/${normalizedRepo}`
|
||||
) as { full_name: string; description?: string };
|
||||
|
||||
// Count open issues
|
||||
const issuesData = await githubFetch(
|
||||
config.token,
|
||||
`/repos/${config.repo}/issues?state=open&per_page=1`
|
||||
`/repos/${normalizedRepo}/issues?state=open&per_page=1`
|
||||
) as unknown[];
|
||||
|
||||
const openCount = Array.isArray(issuesData) ? issuesData.length : 0;
|
||||
@@ -71,7 +83,7 @@ export function registerCheckConnection(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of GitHub repositories
|
||||
* Get list of GitHub repositories (personal + organization)
|
||||
*/
|
||||
export function registerGetRepositories(): void {
|
||||
ipcMain.handle(
|
||||
@@ -88,9 +100,11 @@ export function registerGetRepositories(): void {
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch user's personal + organization repos
|
||||
// affiliation parameter includes: owner, collaborator, organization_member
|
||||
const repos = await githubFetch(
|
||||
config.token,
|
||||
'/user/repos?per_page=100&sort=updated'
|
||||
'/user/repos?per_page=100&sort=updated&affiliation=owner,collaborator,organization_member'
|
||||
) as GitHubAPIRepository[];
|
||||
|
||||
const result: GitHubRepository[] = repos.map(repo => ({
|
||||
|
||||
@@ -54,6 +54,32 @@ export function getGitHubConfig(project: Project): GitHubConfig | null {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a GitHub repository reference to owner/repo format
|
||||
* Handles:
|
||||
* - owner/repo (already normalized)
|
||||
* - https://github.com/owner/repo
|
||||
* - https://github.com/owner/repo.git
|
||||
* - git@github.com:owner/repo.git
|
||||
*/
|
||||
export function normalizeRepoReference(repo: string): string {
|
||||
if (!repo) return '';
|
||||
|
||||
// Remove trailing .git if present
|
||||
let normalized = repo.replace(/\.git$/, '');
|
||||
|
||||
// Handle full GitHub URLs
|
||||
if (normalized.startsWith('https://github.com/')) {
|
||||
normalized = normalized.replace('https://github.com/', '');
|
||||
} else if (normalized.startsWith('http://github.com/')) {
|
||||
normalized = normalized.replace('http://github.com/', '');
|
||||
} else if (normalized.startsWith('git@github.com:')) {
|
||||
normalized = normalized.replace('git@github.com:', '');
|
||||
}
|
||||
|
||||
return normalized.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a request to the GitHub API
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user