diff --git a/REPOSITORIES.md b/REPOSITORIES.md index 2e3abbc..dd2761f 100644 --- a/REPOSITORIES.md +++ b/REPOSITORIES.md @@ -116,3 +116,8 @@ Track implementation progress in GitHub issue #9. ## webapp - Description: Web application interface for worlddriven - Topics: webapp, web, frontend, worlddriven + +## migration-test +- Description: Test repository for migration automation +- Topics: test, migration, automation +- Origin: TooAngel/worlddriven-migration-test diff --git a/scripts/check-transfer-permissions.js b/scripts/check-transfer-permissions.js index 1ae97c0..93788d8 100644 --- a/scripts/check-transfer-permissions.js +++ b/scripts/check-transfer-permissions.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /** - * Check if worlddriven organization has admin permission on a repository + * Check if worlddrivenbot has admin permission on a repository * Required for repository transfer automation */ @@ -9,7 +9,7 @@ const GITHUB_API_BASE = 'https://api.github.com'; const ORG_NAME = 'worlddriven'; /** - * Check if worlddriven org has admin permission on the origin repository + * Check if the authenticated user (worlddrivenbot) has admin permission on the origin repository * * @param {string} token - GitHub token (WORLDDRIVEN_GITHUB_TOKEN) * @param {string} originRepo - Repository in format "owner/repo-name" @@ -31,8 +31,9 @@ export async function checkTransferPermission(token, originRepo) { } try { - // Check if worlddriven org has admin permission on the origin repository - const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/collaborators/${ORG_NAME}/permission`; + // Check the authenticated user's permission on the origin repository + // The repo endpoint returns permissions for the authenticated user + const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}`; const response = await fetch(url, { headers: { @@ -44,11 +45,11 @@ export async function checkTransferPermission(token, originRepo) { // Handle different response scenarios if (response.status === 404) { - // Repository doesn't exist or worlddriven doesn't have any permission + // Repository doesn't exist or user doesn't have any access return { hasPermission: false, permissionLevel: 'none', - details: `Repository ${originRepo} not found or worlddriven has no access`, + details: `Repository ${originRepo} not found or no access`, }; } @@ -64,16 +65,19 @@ export async function checkTransferPermission(token, originRepo) { const data = await response.json(); - // GitHub returns permission level: "admin", "write", "read", or "none" - const permissionLevel = data.permission || 'none'; - const hasPermission = permissionLevel === 'admin'; + // The repo response includes permissions object for the authenticated user + const permissions = data.permissions || {}; + const hasPermission = permissions.admin === true; + const permissionLevel = hasPermission ? 'admin' : + permissions.push ? 'write' : + permissions.pull ? 'read' : 'none'; return { hasPermission, permissionLevel, details: hasPermission - ? `✅ ${ORG_NAME} has admin access to ${originRepo}` - : `❌ ${ORG_NAME} has "${permissionLevel}" access to ${originRepo} (admin required)`, + ? `✅ worlddrivenbot has admin access to ${originRepo}` + : `❌ worlddrivenbot has "${permissionLevel}" access to ${originRepo} (admin required)`, }; } catch (error) { diff --git a/scripts/sync-repositories.js b/scripts/sync-repositories.js index b465baf..6ca303b 100755 --- a/scripts/sync-repositories.js +++ b/scripts/sync-repositories.js @@ -232,6 +232,39 @@ async function updateRepositoryTopics(token, repoName, topics) { return await response.json(); } +/** + * Transfer a repository to the worlddriven organization + * @param {string} token - GitHub token with admin access + * @param {string} originRepo - Source repository in format "owner/repo" + * @param {string} newName - Name for the repository in worlddriven org + * @returns {Promise} Transfer result + */ +async function transferRepository(token, originRepo, newName) { + const [owner, repo] = originRepo.split('/'); + const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/transfer`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + new_owner: ORG_NAME, + new_name: newName, + }), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`Transfer failed (${response.status}): ${error}`); + } + + return await response.json(); +} + /** * Delete a repository from the GitHub organization */ @@ -540,8 +573,19 @@ async function executeSyncPlan(token, plan, dryRun) { break; case 'transfer': - // Transfer API not yet implemented - throw new Error('Repository transfer is not yet implemented. See issue #9 for progress.'); + result = await transferRepository(token, action.origin, action.repo); + // Apply standard settings after transfer completes + // Note: Transfer is async on GitHub's side, settings may need retry + try { + await ensureStandardConfiguration(token, action.repo); + if (action.data.topics && action.data.topics.length > 0) { + await updateRepositoryTopics(token, action.repo, action.data.topics); + } + } catch (configError) { + // Transfer succeeded but config failed - log but don't fail + console.error(`Warning: Transfer succeeded but post-transfer config failed: ${configError.message}`); + } + break; default: throw new Error(`Unknown action type: ${action.type}`);