From 7269e8f7cda7a66f471be74c1483cda3582018a7 Mon Sep 17 00:00:00 2001 From: Tobias Wilken Date: Mon, 8 Dec 2025 12:37:18 +0100 Subject: [PATCH 1/3] feat: add migration-test repository for testing transfer automation Add test repository to validate the migration workflow before migrating real repositories like worlddriven core. Origin: TooAngel/worlddriven-migration-test --- REPOSITORIES.md | 5 +++++ 1 file changed, 5 insertions(+) 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 From 90dd0fb7c5f63901e2b4d0b0b19278b501e1312a Mon Sep 17 00:00:00 2001 From: Tobias Wilken Date: Mon, 8 Dec 2025 13:07:51 +0100 Subject: [PATCH 2/3] feat: implement repository transfer API Add transferRepository() function that calls GitHub's transfer API to move repositories from external owners to the worlddriven org. After transfer completes, standard settings and topics are applied. Transfer errors are handled gracefully - if post-transfer config fails, the transfer itself is still considered successful. Closes #9 --- scripts/sync-repositories.js | 48 ++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) 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}`); From a3c48f6083bf8b008a51715aa1507e5590a6550f Mon Sep 17 00:00:00 2001 From: Tobias Wilken Date: Mon, 8 Dec 2025 18:11:09 +0100 Subject: [PATCH 3/3] fix: check worlddrivenbot's permission instead of worlddriven org The permission check was looking for 'worlddriven' org as a collaborator, but we invite 'worlddrivenbot' user. Changed to use the repo endpoint which returns permissions for the authenticated user (worlddrivenbot). --- scripts/check-transfer-permissions.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) 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) {