feat: fix for push private repo (#1618)
* feat: push private repo # GitHub Integration Changelog ## Fixed - Fixed issue where repositories marked as private weren't being created with private visibility - Added support for changing repository visibility (public/private) when pushing to existing repositories - Fixed 404 errors when pushing files after changing repository visibility ## Added - Added clear user warnings when changing repository visibility from public to private or vice versa - Implemented delays after visibility changes to allow GitHub API to fully process the change - Added retry mechanism (up to 3 attempts with increasing delays) for pushing files after visibility changes - Added repository data refresh before pushing to ensure latest reference data ## Improved - Enhanced error logging and handling for all GitHub API operations - Updated return value handling to use actual repository URLs from the API response - Added comprehensive logging to track repository creation and update operations * cleanup * Update Workbench.client.tsx
This commit is contained in:
@@ -600,7 +600,13 @@ export class WorkbenchStore {
|
||||
return syncedFiles;
|
||||
}
|
||||
|
||||
async pushToGitHub(repoName: string, commitMessage?: string, githubUsername?: string, ghToken?: string) {
|
||||
async pushToGitHub(
|
||||
repoName: string,
|
||||
commitMessage?: string,
|
||||
githubUsername?: string,
|
||||
ghToken?: string,
|
||||
isPrivate: boolean = false,
|
||||
) {
|
||||
try {
|
||||
// Use cookies if username and token are not provided
|
||||
const githubToken = ghToken || Cookies.get('githubToken');
|
||||
@@ -610,26 +616,72 @@ export class WorkbenchStore {
|
||||
throw new Error('GitHub token or username is not set in cookies or provided.');
|
||||
}
|
||||
|
||||
// Log the isPrivate flag to verify it's being properly passed
|
||||
console.log(`pushToGitHub called with isPrivate=${isPrivate}`);
|
||||
|
||||
// Initialize Octokit with the auth token
|
||||
const octokit = new Octokit({ auth: githubToken });
|
||||
|
||||
// Check if the repository already exists before creating it
|
||||
let repo: RestEndpointMethodTypes['repos']['get']['response']['data'];
|
||||
let visibilityJustChanged = false;
|
||||
|
||||
try {
|
||||
const resp = await octokit.repos.get({ owner, repo: repoName });
|
||||
repo = resp.data;
|
||||
console.log('Repository already exists, using existing repo');
|
||||
|
||||
// Check if we need to update visibility of existing repo
|
||||
if (repo.private !== isPrivate) {
|
||||
console.log(
|
||||
`Updating repository visibility from ${repo.private ? 'private' : 'public'} to ${isPrivate ? 'private' : 'public'}`,
|
||||
);
|
||||
|
||||
try {
|
||||
// Update repository visibility using the update method
|
||||
const { data: updatedRepo } = await octokit.repos.update({
|
||||
owner,
|
||||
repo: repoName,
|
||||
private: isPrivate,
|
||||
});
|
||||
|
||||
console.log('Repository visibility updated successfully');
|
||||
repo = updatedRepo;
|
||||
visibilityJustChanged = true;
|
||||
|
||||
// Add a delay after changing visibility to allow GitHub to fully process the change
|
||||
console.log('Waiting for visibility change to propagate...');
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000)); // 3 second delay
|
||||
} catch (visibilityError) {
|
||||
console.error('Failed to update repository visibility:', visibilityError);
|
||||
|
||||
// Continue with push even if visibility update fails
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'status' in error && error.status === 404) {
|
||||
// Repository doesn't exist, so create a new one
|
||||
const { data: newRepo } = await octokit.repos.createForAuthenticatedUser({
|
||||
console.log(`Creating new repository with private=${isPrivate}`);
|
||||
|
||||
// Create new repository with specified privacy setting
|
||||
const createRepoOptions = {
|
||||
name: repoName,
|
||||
private: false,
|
||||
private: isPrivate,
|
||||
auto_init: true,
|
||||
});
|
||||
};
|
||||
|
||||
console.log('Create repo options:', createRepoOptions);
|
||||
|
||||
const { data: newRepo } = await octokit.repos.createForAuthenticatedUser(createRepoOptions);
|
||||
|
||||
console.log('Repository created:', newRepo.html_url, 'Private:', newRepo.private);
|
||||
repo = newRepo;
|
||||
|
||||
// Add a small delay after creating a repository to allow GitHub to fully initialize it
|
||||
console.log('Waiting for repository to initialize...');
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000)); // 2 second delay
|
||||
} else {
|
||||
console.log('cannot create repo!');
|
||||
console.error('Cannot create repo:', error);
|
||||
throw error; // Some other error occurred
|
||||
}
|
||||
}
|
||||
@@ -641,68 +693,102 @@ export class WorkbenchStore {
|
||||
throw new Error('No files found to push');
|
||||
}
|
||||
|
||||
// Create blobs for each file
|
||||
const blobs = await Promise.all(
|
||||
Object.entries(files).map(async ([filePath, dirent]) => {
|
||||
if (dirent?.type === 'file' && dirent.content) {
|
||||
const { data: blob } = await octokit.git.createBlob({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
content: Buffer.from(dirent.content).toString('base64'),
|
||||
encoding: 'base64',
|
||||
});
|
||||
return { path: extractRelativePath(filePath), sha: blob.sha };
|
||||
// Function to push files with retry logic
|
||||
const pushFilesToRepo = async (attempt = 1): Promise<string> => {
|
||||
const maxAttempts = 3;
|
||||
|
||||
try {
|
||||
console.log(`Pushing files to repository (attempt ${attempt}/${maxAttempts})...`);
|
||||
|
||||
// Create blobs for each file
|
||||
const blobs = await Promise.all(
|
||||
Object.entries(files).map(async ([filePath, dirent]) => {
|
||||
if (dirent?.type === 'file' && dirent.content) {
|
||||
const { data: blob } = await octokit.git.createBlob({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
content: Buffer.from(dirent.content).toString('base64'),
|
||||
encoding: 'base64',
|
||||
});
|
||||
return { path: extractRelativePath(filePath), sha: blob.sha };
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
|
||||
const validBlobs = blobs.filter(Boolean); // Filter out any undefined blobs
|
||||
|
||||
if (validBlobs.length === 0) {
|
||||
throw new Error('No valid files to push');
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
// Refresh repository reference to ensure we have the latest data
|
||||
const repoRefresh = await octokit.repos.get({ owner, repo: repoName });
|
||||
repo = repoRefresh.data;
|
||||
|
||||
const validBlobs = blobs.filter(Boolean); // Filter out any undefined blobs
|
||||
// Get the latest commit SHA (assuming main branch, update dynamically if needed)
|
||||
const { data: ref } = await octokit.git.getRef({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch
|
||||
});
|
||||
const latestCommitSha = ref.object.sha;
|
||||
|
||||
if (validBlobs.length === 0) {
|
||||
throw new Error('No valid files to push');
|
||||
}
|
||||
// Create a new tree
|
||||
const { data: newTree } = await octokit.git.createTree({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
base_tree: latestCommitSha,
|
||||
tree: validBlobs.map((blob) => ({
|
||||
path: blob!.path,
|
||||
mode: '100644',
|
||||
type: 'blob',
|
||||
sha: blob!.sha,
|
||||
})),
|
||||
});
|
||||
|
||||
// Get the latest commit SHA (assuming main branch, update dynamically if needed)
|
||||
const { data: ref } = await octokit.git.getRef({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch
|
||||
});
|
||||
const latestCommitSha = ref.object.sha;
|
||||
// Create a new commit
|
||||
const { data: newCommit } = await octokit.git.createCommit({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
message: commitMessage || 'Initial commit from your app',
|
||||
tree: newTree.sha,
|
||||
parents: [latestCommitSha],
|
||||
});
|
||||
|
||||
// Create a new tree
|
||||
const { data: newTree } = await octokit.git.createTree({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
base_tree: latestCommitSha,
|
||||
tree: validBlobs.map((blob) => ({
|
||||
path: blob!.path,
|
||||
mode: '100644',
|
||||
type: 'blob',
|
||||
sha: blob!.sha,
|
||||
})),
|
||||
});
|
||||
// Update the reference
|
||||
await octokit.git.updateRef({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch
|
||||
sha: newCommit.sha,
|
||||
});
|
||||
|
||||
// Create a new commit
|
||||
const { data: newCommit } = await octokit.git.createCommit({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
message: commitMessage || 'Initial commit from your app',
|
||||
tree: newTree.sha,
|
||||
parents: [latestCommitSha],
|
||||
});
|
||||
console.log('Files successfully pushed to repository');
|
||||
|
||||
// Update the reference
|
||||
await octokit.git.updateRef({
|
||||
owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch
|
||||
sha: newCommit.sha,
|
||||
});
|
||||
return repo.html_url;
|
||||
} catch (error) {
|
||||
console.error(`Error during push attempt ${attempt}:`, error);
|
||||
|
||||
alert(`Repository created and code pushed: ${repo.html_url}`);
|
||||
// If we've just changed visibility and this is not our last attempt, wait and retry
|
||||
if ((visibilityJustChanged || attempt === 1) && attempt < maxAttempts) {
|
||||
const delayMs = attempt * 2000; // Increasing delay with each attempt
|
||||
console.log(`Waiting ${delayMs}ms before retry...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
|
||||
return pushFilesToRepo(attempt + 1);
|
||||
}
|
||||
|
||||
throw error; // Rethrow if we're out of attempts
|
||||
}
|
||||
};
|
||||
|
||||
// Execute the push function with retry logic
|
||||
const repoUrl = await pushFilesToRepo();
|
||||
|
||||
// Return the repository URL
|
||||
return repoUrl;
|
||||
} catch (error) {
|
||||
console.error('Error pushing to GitHub:', error);
|
||||
throw error; // Rethrow the error for further handling
|
||||
|
||||
Reference in New Issue
Block a user