UNPKG

qraft

Version:

A powerful CLI tool to qraft structured project setups from GitHub template repositories

309 lines 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RepositoryForker = void 0; const permissionChecker_1 = require("./permissionChecker"); class RepositoryForker { constructor(githubToken) { this.permissionChecker = new permissionChecker_1.PermissionChecker(githubToken); } async forkRepository(originalOwner, originalName, options = {}) { try { // Check if we have permission to fork const permissionResult = await this.permissionChecker.checkRepositoryPermissions(originalOwner, originalName); if (!permissionResult.permissions.canFork) { return { success: false, forkUrl: '', forkOwner: '', forkName: '', originalRepo: `${originalOwner}/${originalName}`, message: 'Cannot fork repository - insufficient permissions', nextSteps: [ 'Repository may be private and you don\'t have access', 'Contact the repository owner for access', 'Use a different repository where you have permissions' ] }; } // Check if fork already exists const currentUser = await this.permissionChecker.getCurrentUser(); if (!currentUser) { return { success: false, forkUrl: '', forkOwner: '', forkName: '', originalRepo: `${originalOwner}/${originalName}`, message: 'GitHub authentication required for forking', nextSteps: [ 'Set up GitHub token: export GITHUB_TOKEN=your_token', 'Ensure token has repo permissions', 'Try the operation again' ] }; } const targetOwner = options.organization || currentUser.login; const targetName = options.name || originalName; // Check if fork already exists const existingFork = await this.checkExistingFork(targetOwner, targetName, originalOwner, originalName); if (existingFork) { return { success: true, forkUrl: existingFork.forkUrl, forkOwner: targetOwner, forkName: targetName, originalRepo: `${originalOwner}/${originalName}`, message: 'Fork already exists', nextSteps: [ 'Fork is ready to use', 'You can now create boxes in your fork', 'Consider creating a pull request when ready' ] }; } // Create the fork const forkResult = await this.createFork(originalOwner, originalName, options); if (forkResult.success) { // Wait a moment for GitHub to set up the fork await this.waitForForkReady(forkResult.forkOwner, forkResult.forkName); } return forkResult; } catch (error) { return { success: false, forkUrl: '', forkOwner: '', forkName: '', originalRepo: `${originalOwner}/${originalName}`, message: `Fork failed: ${error}`, nextSteps: [ 'Check your GitHub token permissions', 'Verify the repository exists and is accessible', 'Try again or use a different repository' ] }; } } async checkExistingFork(targetOwner, targetName, originalOwner, originalName) { try { const response = await fetch(`https://api.github.com/repos/${targetOwner}/${targetName}`, { headers: { 'Authorization': `token ${this.getGitHubToken()}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'qraft-cli' } }); if (response.ok) { const data = await response.json(); // Check if this is actually a fork of the original repository if (data.fork && data.parent && data.parent.full_name === `${originalOwner}/${originalName}`) { return { forkUrl: data.html_url }; } } } catch { // Ignore errors - fork doesn't exist } return null; } async createFork(originalOwner, originalName, options) { const forkData = {}; if (options.organization) { forkData.organization = options.organization; } if (options.name) { forkData.name = options.name; } if (options.defaultBranchOnly) { forkData.default_branch_only = true; } const response = await fetch(`https://api.github.com/repos/${originalOwner}/${originalName}/forks`, { method: 'POST', headers: { 'Authorization': `token ${this.getGitHubToken()}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'qraft-cli', 'Content-Type': 'application/json' }, body: JSON.stringify(forkData) }); if (!response.ok) { const errorData = await response.json(); throw new Error(`GitHub API error: ${errorData.message || response.statusText}`); } const data = await response.json(); return { success: true, forkUrl: data.html_url, forkOwner: data.owner.login, forkName: data.name, originalRepo: `${originalOwner}/${originalName}`, message: 'Repository forked successfully', nextSteps: [ 'Fork created and ready to use', 'You can now create boxes in your fork', 'Create a pull request when ready to contribute back' ] }; } async waitForForkReady(forkOwner, forkName, maxWaitTime = 30000) { const startTime = Date.now(); const checkInterval = 2000; // 2 seconds while (Date.now() - startTime < maxWaitTime) { try { const response = await fetch(`https://api.github.com/repos/${forkOwner}/${forkName}`, { headers: { 'Authorization': `token ${this.getGitHubToken()}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'qraft-cli' } }); if (response.ok) { const data = await response.json(); // Check if the fork is ready (has content) if (!data.size || data.size === 0) { // Fork might still be setting up, wait a bit more await new Promise(resolve => setTimeout(resolve, checkInterval)); continue; } return; // Fork is ready } } catch { // Continue waiting } await new Promise(resolve => setTimeout(resolve, checkInterval)); } // Fork setup took too long, but it might still work console.warn('Fork setup took longer than expected, but it should be ready soon'); } getGitHubToken() { // Access the token through the permission checker if (!this.permissionChecker.hasGitHubToken()) { throw new Error('GitHub token required for forking operations'); } // In a real implementation, we'd need to expose the token or use a different approach // For now, we'll assume the token is available through environment variables const token = process.env.GITHUB_TOKEN; if (!token) { throw new Error('GitHub token not found in environment variables'); } return token; } // Check if a repository can be forked async canForkRepository(owner, name) { try { const permissionResult = await this.permissionChecker.checkRepositoryPermissions(owner, name); if (!permissionResult.permissions.canFork) { return { canFork: false, reason: 'Insufficient permissions to fork repository', alternatives: [ 'Request access from repository owner', 'Use a different public repository', 'Create your own repository' ] }; } if (!this.permissionChecker.hasGitHubToken()) { return { canFork: false, reason: 'GitHub authentication required', alternatives: [ 'Set up GitHub token', 'Use GitHub CLI for authentication', 'Fork manually through GitHub web interface' ] }; } return { canFork: true, reason: 'Repository can be forked', alternatives: [] }; } catch (error) { return { canFork: false, reason: `Error checking fork permissions: ${error}`, alternatives: [ 'Verify repository exists', 'Check your GitHub token', 'Try a different repository' ] }; } } // Get fork information for an existing fork async getForkInfo(forkOwner, forkName) { try { const response = await fetch(`https://api.github.com/repos/${forkOwner}/${forkName}`, { headers: { 'Authorization': `token ${this.getGitHubToken()}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'qraft-cli' } }); if (!response.ok) { throw new Error('Repository not found or not accessible'); } const data = await response.json(); if (!data.fork) { return { isFork: false }; } // Get comparison with parent const parentRepo = data.parent.full_name; const compareResponse = await fetch(`https://api.github.com/repos/${parentRepo}/compare/${data.parent.default_branch}...${forkOwner}:${data.default_branch}`, { headers: { 'Authorization': `token ${this.getGitHubToken()}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'qraft-cli' } }); let behindBy = 0; let aheadBy = 0; let isUpToDate = true; if (compareResponse.ok) { const compareData = await compareResponse.json(); behindBy = compareData.behind_by || 0; aheadBy = compareData.ahead_by || 0; isUpToDate = behindBy === 0 && aheadBy === 0; } return { isFork: true, parentRepo, isUpToDate, behindBy, aheadBy }; } catch (error) { throw new Error(`Failed to get fork info: ${error}`); } } // Test method for dry-run forking async forkRepositoryDryRun(originalOwner, originalName, options = {}, mockUser = { login: 'testuser', name: 'Test User' }) { const targetOwner = options.organization || mockUser.login; const targetName = options.name || originalName; return { success: true, forkUrl: `https://github.com/${targetOwner}/${targetName}`, forkOwner: targetOwner, forkName: targetName, originalRepo: `${originalOwner}/${originalName}`, message: '[DRY RUN] Repository would be forked successfully', nextSteps: [ '[DRY RUN] Fork would be created and ready to use', '[DRY RUN] You could create boxes in your fork', '[DRY RUN] Create a pull request when ready to contribute back' ] }; } } exports.RepositoryForker = RepositoryForker; //# sourceMappingURL=repositoryForker.js.map