Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions REPOSITORIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 15 additions & 11 deletions scripts/check-transfer-permissions.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/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
*/

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"
Expand All @@ -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: {
Expand All @@ -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`,
};
}

Expand All @@ -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) {
Expand Down
48 changes: 46 additions & 2 deletions scripts/sync-repositories.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object>} 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
*/
Expand Down Expand Up @@ -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}`);
Expand Down
Loading