From 059933a0c6d0e6468077468afcca1e237d702681 Mon Sep 17 00:00:00 2001 From: goncaloalves Date: Mon, 21 Oct 2024 20:14:35 +0100 Subject: [PATCH] Added GitHub push functionality --- app/components/workbench/Workbench.client.tsx | 25 ++++ app/lib/stores/workbench.ts | 106 ++++++++++++++ package.json | 2 + pnpm-lock.yaml | 136 +++++++++++++++++- 4 files changed, 263 insertions(+), 6 deletions(-) diff --git a/app/components/workbench/Workbench.client.tsx b/app/components/workbench/Workbench.client.tsx index 839e9a80..98f95ef7 100644 --- a/app/components/workbench/Workbench.client.tsx +++ b/app/components/workbench/Workbench.client.tsx @@ -141,6 +141,31 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
Toggle Terminal + { + const repoName = prompt("Please enter a name for your new GitHub repository:", "bolt-generated-project"); + if (!repoName) { + alert("Repository name is required. Push to GitHub cancelled."); + return; + } + const githubUsername = prompt("Please enter your GitHub username:"); + if (!githubUsername) { + alert("GitHub username is required. Push to GitHub cancelled."); + return; + } + const githubToken = prompt("Please enter your GitHub personal access token:"); + if (!githubToken) { + alert("GitHub token is required. Push to GitHub cancelled."); + return; + } + + workbenchStore.pushToGitHub(repoName, githubUsername, githubToken); + }} + > +
+ Push to GitHub + )} { + 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: filePath.replace(/^\/home\/project\//, ''), sha: blob.sha }; + } + }) + ); + + const validBlobs = blobs.filter(Boolean); // Filter out any undefined blobs + + if (validBlobs.length === 0) { + throw new Error('No valid files to push'); + } + + // 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 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, + })), + }); + + // Create a new commit + const { data: newCommit } = await octokit.git.createCommit({ + owner: repo.owner.login, + repo: repo.name, + message: 'Initial commit from your app', + tree: newTree.sha, + parents: [latestCommitSha], + }); + + // 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, + }); + + alert(`Repository created and code pushed: ${repo.html_url}`); + } catch (error) { + console.error('Error pushing to GitHub:', error instanceof Error ? error.message : String(error)); + } + } } export const workbenchStore = new WorkbenchStore(); diff --git a/package.json b/package.json index 737ca05c..e5595d70 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "@iconify-json/svg-spinners": "^1.1.2", "@lezer/highlight": "^1.2.0", "@nanostores/react": "^0.7.2", + "@octokit/rest": "^21.0.2", + "@octokit/types": "^13.6.1", "@openrouter/ai-sdk-provider": "^0.0.5", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46dc9dbf..21dd5a1a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,12 @@ importers: '@nanostores/react': specifier: ^0.7.2 version: 0.7.2(nanostores@0.10.3)(react@18.3.1) + '@octokit/rest': + specifier: ^21.0.2 + version: 21.0.2 + '@octokit/types': + specifier: ^13.6.1 + version: 13.6.1 '@openrouter/ai-sdk-provider': specifier: ^0.0.5 version: 0.0.5(zod@3.23.8) @@ -1230,6 +1236,58 @@ packages: resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@octokit/auth-token@5.1.1': + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.2': + resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.1': + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.1.1': + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@22.2.0': + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + + '@octokit/plugin-paginate-rest@11.3.5': + resolution: {integrity: sha512-cgwIRtKrpwhLoBi0CUNuY83DPGRMaWVjqVI/bGKsLJ4PzyWZNaEmhHroI2xlrVXkk6nFv0IsZpOp+ZWSWUS2AQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@5.3.1': + resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@13.2.6': + resolution: {integrity: sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@6.1.5': + resolution: {integrity: sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==} + engines: {node: '>= 18'} + + '@octokit/request@9.1.3': + resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==} + engines: {node: '>= 18'} + + '@octokit/rest@21.0.2': + resolution: {integrity: sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==} + engines: {node: '>= 18'} + + '@octokit/types@13.6.1': + resolution: {integrity: sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==} + '@openrouter/ai-sdk-provider@0.0.5': resolution: {integrity: sha512-AfxXQhISpxQSeUjU/4jo9waM5GRNX6eIkfTFS9l7vHkD1TKDP81Y/dXrE0ttJeN/Kap3tPF3Jwh49me0gWwjSw==} engines: {node: '>=18'} @@ -2211,6 +2269,9 @@ packages: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -4970,9 +5031,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} @@ -5053,6 +5111,9 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -6363,6 +6424,67 @@ snapshots: dependencies: which: 3.0.1 + '@octokit/auth-token@5.1.1': {} + + '@octokit/core@6.1.2': + dependencies: + '@octokit/auth-token': 5.1.1 + '@octokit/graphql': 8.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.5 + '@octokit/types': 13.6.1 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.2 + + '@octokit/endpoint@10.1.1': + dependencies: + '@octokit/types': 13.6.1 + universal-user-agent: 7.0.2 + + '@octokit/graphql@8.1.1': + dependencies: + '@octokit/request': 9.1.3 + '@octokit/types': 13.6.1 + universal-user-agent: 7.0.2 + + '@octokit/openapi-types@22.2.0': {} + + '@octokit/plugin-paginate-rest@11.3.5(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.6.1 + + '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + + '@octokit/plugin-rest-endpoint-methods@13.2.6(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.6.1 + + '@octokit/request-error@6.1.5': + dependencies: + '@octokit/types': 13.6.1 + + '@octokit/request@9.1.3': + dependencies: + '@octokit/endpoint': 10.1.1 + '@octokit/request-error': 6.1.5 + '@octokit/types': 13.6.1 + universal-user-agent: 7.0.2 + + '@octokit/rest@21.0.2': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/plugin-paginate-rest': 11.3.5(@octokit/core@6.1.2) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.2) + '@octokit/plugin-rest-endpoint-methods': 13.2.6(@octokit/core@6.1.2) + + '@octokit/types@13.6.1': + dependencies: + '@octokit/openapi-types': 22.2.0 + '@openrouter/ai-sdk-provider@0.0.5(zod@3.23.8)': dependencies: '@ai-sdk/provider': 0.0.12 @@ -7530,6 +7652,8 @@ snapshots: safe-buffer: 5.1.2 optional: true + before-after-hook@3.0.2: {} + binary-extensions@2.3.0: {} binaryextensions@6.11.0: @@ -9972,7 +10096,7 @@ snapshots: dependencies: destr: 2.0.3 node-fetch-native: 1.6.4 - ufo: 1.5.3 + ufo: 1.5.4 ollama-ai-provider@0.15.2(zod@3.23.8): dependencies: @@ -11002,8 +11126,6 @@ snapshots: typescript@5.5.2: {} - ufo@1.5.3: {} - ufo@1.5.4: {} unconfig@0.3.13: @@ -11122,6 +11244,8 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + universal-user-agent@7.0.2: {} + universalify@2.0.1: {} unocss@0.61.3(postcss@8.4.38)(rollup@4.18.0)(vite@5.3.1(@types/node@20.14.9)(sass@1.77.6)):