Vecchio commited on
Commit
059933a
·
1 Parent(s): cd4ddfd

Added GitHub push functionality

Browse files
app/components/workbench/Workbench.client.tsx CHANGED
@@ -141,6 +141,31 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
141
  <div className="i-ph:terminal" />
142
  Toggle Terminal
143
  </PanelHeaderButton>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  </>
145
  )}
146
  <IconButton
 
141
  <div className="i-ph:terminal" />
142
  Toggle Terminal
143
  </PanelHeaderButton>
144
+ <PanelHeaderButton
145
+ className="mr-1 text-sm"
146
+ onClick={() => {
147
+ const repoName = prompt("Please enter a name for your new GitHub repository:", "bolt-generated-project");
148
+ if (!repoName) {
149
+ alert("Repository name is required. Push to GitHub cancelled.");
150
+ return;
151
+ }
152
+ const githubUsername = prompt("Please enter your GitHub username:");
153
+ if (!githubUsername) {
154
+ alert("GitHub username is required. Push to GitHub cancelled.");
155
+ return;
156
+ }
157
+ const githubToken = prompt("Please enter your GitHub personal access token:");
158
+ if (!githubToken) {
159
+ alert("GitHub token is required. Push to GitHub cancelled.");
160
+ return;
161
+ }
162
+
163
+ workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
164
+ }}
165
+ >
166
+ <div className="i-ph:github-logo" />
167
+ Push to GitHub
168
+ </PanelHeaderButton>
169
  </>
170
  )}
171
  <IconButton
app/lib/stores/workbench.ts CHANGED
@@ -11,6 +11,7 @@ import { PreviewsStore } from './previews';
11
  import { TerminalStore } from './terminal';
12
  import JSZip from 'jszip';
13
  import { saveAs } from 'file-saver';
 
14
 
15
  export interface ArtifactState {
16
  id: string;
@@ -303,6 +304,111 @@ export class WorkbenchStore {
303
  const content = await zip.generateAsync({ type: 'blob' });
304
  saveAs(content, 'project.zip');
305
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  }
307
 
308
  export const workbenchStore = new WorkbenchStore();
 
11
  import { TerminalStore } from './terminal';
12
  import JSZip from 'jszip';
13
  import { saveAs } from 'file-saver';
14
+ import { Octokit } from "@octokit/rest";
15
 
16
  export interface ArtifactState {
17
  id: string;
 
304
  const content = await zip.generateAsync({ type: 'blob' });
305
  saveAs(content, 'project.zip');
306
  }
307
+
308
+ async pushToGitHub(repoName: string, githubUsername: string, ghToken: string) {
309
+
310
+ try {
311
+ // Get the GitHub auth token from environment variables
312
+ const githubToken = ghToken;
313
+
314
+ const owner = githubUsername;
315
+
316
+ if (!githubToken) {
317
+ throw new Error('GitHub token is not set in environment variables');
318
+ }
319
+
320
+ // Initialize Octokit with the auth token
321
+ const octokit = new Octokit({ auth: githubToken });
322
+
323
+ // Check if the repository already exists before creating it
324
+ let repo
325
+ try {
326
+ repo = await octokit.repos.get({ owner: owner, repo: repoName });
327
+ } catch (error) {
328
+ if (error instanceof Error && 'status' in error && error.status === 404) {
329
+ // Repository doesn't exist, so create a new one
330
+ const { data: newRepo } = await octokit.repos.createForAuthenticatedUser({
331
+ name: repoName,
332
+ private: false,
333
+ auto_init: true,
334
+ });
335
+ repo = newRepo;
336
+ } else {
337
+ console.log('cannot create repo!');
338
+ throw error; // Some other error occurred
339
+ }
340
+ }
341
+
342
+ // Get all files
343
+ const files = this.files.get();
344
+ if (!files || Object.keys(files).length === 0) {
345
+ throw new Error('No files found to push');
346
+ }
347
+
348
+ // Create blobs for each file
349
+ const blobs = await Promise.all(
350
+ Object.entries(files).map(async ([filePath, dirent]) => {
351
+ if (dirent?.type === 'file' && dirent.content) {
352
+ const { data: blob } = await octokit.git.createBlob({
353
+ owner: repo.owner.login,
354
+ repo: repo.name,
355
+ content: Buffer.from(dirent.content).toString('base64'),
356
+ encoding: 'base64',
357
+ });
358
+ return { path: filePath.replace(/^\/home\/project\//, ''), sha: blob.sha };
359
+ }
360
+ })
361
+ );
362
+
363
+ const validBlobs = blobs.filter(Boolean); // Filter out any undefined blobs
364
+
365
+ if (validBlobs.length === 0) {
366
+ throw new Error('No valid files to push');
367
+ }
368
+
369
+ // Get the latest commit SHA (assuming main branch, update dynamically if needed)
370
+ const { data: ref } = await octokit.git.getRef({
371
+ owner: repo.owner.login,
372
+ repo: repo.name,
373
+ ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch
374
+ });
375
+ const latestCommitSha = ref.object.sha;
376
+
377
+ // Create a new tree
378
+ const { data: newTree } = await octokit.git.createTree({
379
+ owner: repo.owner.login,
380
+ repo: repo.name,
381
+ base_tree: latestCommitSha,
382
+ tree: validBlobs.map((blob) => ({
383
+ path: blob!.path,
384
+ mode: '100644',
385
+ type: 'blob',
386
+ sha: blob!.sha,
387
+ })),
388
+ });
389
+
390
+ // Create a new commit
391
+ const { data: newCommit } = await octokit.git.createCommit({
392
+ owner: repo.owner.login,
393
+ repo: repo.name,
394
+ message: 'Initial commit from your app',
395
+ tree: newTree.sha,
396
+ parents: [latestCommitSha],
397
+ });
398
+
399
+ // Update the reference
400
+ await octokit.git.updateRef({
401
+ owner: repo.owner.login,
402
+ repo: repo.name,
403
+ ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch
404
+ sha: newCommit.sha,
405
+ });
406
+
407
+ alert(`Repository created and code pushed: ${repo.html_url}`);
408
+ } catch (error) {
409
+ console.error('Error pushing to GitHub:', error instanceof Error ? error.message : String(error));
410
+ }
411
+ }
412
  }
413
 
414
  export const workbenchStore = new WorkbenchStore();
package.json CHANGED
@@ -45,6 +45,8 @@
45
  "@iconify-json/svg-spinners": "^1.1.2",
46
  "@lezer/highlight": "^1.2.0",
47
  "@nanostores/react": "^0.7.2",
 
 
48
  "@openrouter/ai-sdk-provider": "^0.0.5",
49
  "@radix-ui/react-dialog": "^1.1.1",
50
  "@radix-ui/react-dropdown-menu": "^2.1.1",
 
45
  "@iconify-json/svg-spinners": "^1.1.2",
46
  "@lezer/highlight": "^1.2.0",
47
  "@nanostores/react": "^0.7.2",
48
+ "@octokit/rest": "^21.0.2",
49
+ "@octokit/types": "^13.6.1",
50
  "@openrouter/ai-sdk-provider": "^0.0.5",
51
  "@radix-ui/react-dialog": "^1.1.1",
52
  "@radix-ui/react-dropdown-menu": "^2.1.1",
pnpm-lock.yaml CHANGED
@@ -77,6 +77,12 @@ importers:
77
  '@nanostores/react':
78
  specifier: ^0.7.2
79
 
 
 
 
 
 
80
  '@openrouter/ai-sdk-provider':
81
  specifier: ^0.0.5
82
  version: 0.0.5([email protected])
@@ -1230,6 +1236,58 @@ packages:
1230
  resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==}
1231
  engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
1232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1233
  '@openrouter/[email protected]':
1234
  resolution: {integrity: sha512-AfxXQhISpxQSeUjU/4jo9waM5GRNX6eIkfTFS9l7vHkD1TKDP81Y/dXrE0ttJeN/Kap3tPF3Jwh49me0gWwjSw==}
1235
  engines: {node: '>=18'}
@@ -2211,6 +2269,9 @@ packages:
2211
  resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
2212
  engines: {node: '>= 0.8'}
2213
 
 
 
 
2214
2215
  resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
2216
  engines: {node: '>=8'}
@@ -4970,9 +5031,6 @@ packages:
4970
  engines: {node: '>=14.17'}
4971
  hasBin: true
4972
 
4973
4974
- resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
4975
-
4976
4977
  resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
4978
 
@@ -5053,6 +5111,9 @@ packages:
5053
5054
  resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
5055
 
 
 
 
5056
5057
  resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
5058
  engines: {node: '>= 10.0.0'}
@@ -6363,6 +6424,67 @@ snapshots:
6363
  dependencies:
6364
  which: 3.0.1
6365
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6366
6367
  dependencies:
6368
  '@ai-sdk/provider': 0.0.12
@@ -7530,6 +7652,8 @@ snapshots:
7530
  safe-buffer: 5.1.2
7531
  optional: true
7532
 
 
 
7533
7534
 
7535
@@ -9972,7 +10096,7 @@ snapshots:
9972
  dependencies:
9973
  destr: 2.0.3
9974
  node-fetch-native: 1.6.4
9975
- ufo: 1.5.3
9976
 
9977
9978
  dependencies:
@@ -11002,8 +11126,6 @@ snapshots:
11002
 
11003
11004
 
11005
11006
-
11007
11008
 
11009
@@ -11122,6 +11244,8 @@ snapshots:
11122
  unist-util-is: 6.0.0
11123
  unist-util-visit-parents: 6.0.1
11124
 
 
 
11125
11126
 
11127
 
77
  '@nanostores/react':
78
  specifier: ^0.7.2
79
80
+ '@octokit/rest':
81
+ specifier: ^21.0.2
82
+ version: 21.0.2
83
+ '@octokit/types':
84
+ specifier: ^13.6.1
85
+ version: 13.6.1
86
  '@openrouter/ai-sdk-provider':
87
  specifier: ^0.0.5
88
  version: 0.0.5([email protected])
 
1236
  resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==}
1237
  engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
1238
 
1239
+ '@octokit/[email protected]':
1240
+ resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==}
1241
+ engines: {node: '>= 18'}
1242
+
1243
+ '@octokit/[email protected]':
1244
+ resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==}
1245
+ engines: {node: '>= 18'}
1246
+
1247
+ '@octokit/[email protected]':
1248
+ resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==}
1249
+ engines: {node: '>= 18'}
1250
+
1251
+ '@octokit/[email protected]':
1252
+ resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==}
1253
+ engines: {node: '>= 18'}
1254
+
1255
+ '@octokit/[email protected]':
1256
+ resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==}
1257
+
1258
+ '@octokit/[email protected]':
1259
+ resolution: {integrity: sha512-cgwIRtKrpwhLoBi0CUNuY83DPGRMaWVjqVI/bGKsLJ4PzyWZNaEmhHroI2xlrVXkk6nFv0IsZpOp+ZWSWUS2AQ==}
1260
+ engines: {node: '>= 18'}
1261
+ peerDependencies:
1262
+ '@octokit/core': '>=6'
1263
+
1264
+ '@octokit/[email protected]':
1265
+ resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==}
1266
+ engines: {node: '>= 18'}
1267
+ peerDependencies:
1268
+ '@octokit/core': '>=6'
1269
+
1270
+ '@octokit/[email protected]':
1271
+ resolution: {integrity: sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==}
1272
+ engines: {node: '>= 18'}
1273
+ peerDependencies:
1274
+ '@octokit/core': '>=6'
1275
+
1276
+ '@octokit/[email protected]':
1277
+ resolution: {integrity: sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==}
1278
+ engines: {node: '>= 18'}
1279
+
1280
+ '@octokit/[email protected]':
1281
+ resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==}
1282
+ engines: {node: '>= 18'}
1283
+
1284
+ '@octokit/[email protected]':
1285
+ resolution: {integrity: sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==}
1286
+ engines: {node: '>= 18'}
1287
+
1288
+ '@octokit/[email protected]':
1289
+ resolution: {integrity: sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==}
1290
+
1291
  '@openrouter/[email protected]':
1292
  resolution: {integrity: sha512-AfxXQhISpxQSeUjU/4jo9waM5GRNX6eIkfTFS9l7vHkD1TKDP81Y/dXrE0ttJeN/Kap3tPF3Jwh49me0gWwjSw==}
1293
  engines: {node: '>=18'}
 
2269
  resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
2270
  engines: {node: '>= 0.8'}
2271
 
2272
2273
+ resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==}
2274
+
2275
2276
  resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
2277
  engines: {node: '>=8'}
 
5031
  engines: {node: '>=14.17'}
5032
  hasBin: true
5033
 
 
 
 
5034
5035
  resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
5036
 
 
5111
5112
  resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
5113
 
5114
5115
+ resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==}
5116
+
5117
5118
  resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
5119
  engines: {node: '>= 10.0.0'}
 
6424
  dependencies:
6425
  which: 3.0.1
6426
 
6427
+ '@octokit/[email protected]': {}
6428
+
6429
+ '@octokit/[email protected]':
6430
+ dependencies:
6431
+ '@octokit/auth-token': 5.1.1
6432
+ '@octokit/graphql': 8.1.1
6433
+ '@octokit/request': 9.1.3
6434
+ '@octokit/request-error': 6.1.5
6435
+ '@octokit/types': 13.6.1
6436
+ before-after-hook: 3.0.2
6437
+ universal-user-agent: 7.0.2
6438
+
6439
+ '@octokit/[email protected]':
6440
+ dependencies:
6441
+ '@octokit/types': 13.6.1
6442
+ universal-user-agent: 7.0.2
6443
+
6444
+ '@octokit/[email protected]':
6445
+ dependencies:
6446
+ '@octokit/request': 9.1.3
6447
+ '@octokit/types': 13.6.1
6448
+ universal-user-agent: 7.0.2
6449
+
6450
+ '@octokit/[email protected]': {}
6451
+
6452
+ '@octokit/[email protected](@octokit/[email protected])':
6453
+ dependencies:
6454
+ '@octokit/core': 6.1.2
6455
+ '@octokit/types': 13.6.1
6456
+
6457
+ '@octokit/[email protected](@octokit/[email protected])':
6458
+ dependencies:
6459
+ '@octokit/core': 6.1.2
6460
+
6461
+ '@octokit/[email protected](@octokit/[email protected])':
6462
+ dependencies:
6463
+ '@octokit/core': 6.1.2
6464
+ '@octokit/types': 13.6.1
6465
+
6466
+ '@octokit/[email protected]':
6467
+ dependencies:
6468
+ '@octokit/types': 13.6.1
6469
+
6470
+ '@octokit/[email protected]':
6471
+ dependencies:
6472
+ '@octokit/endpoint': 10.1.1
6473
+ '@octokit/request-error': 6.1.5
6474
+ '@octokit/types': 13.6.1
6475
+ universal-user-agent: 7.0.2
6476
+
6477
+ '@octokit/[email protected]':
6478
+ dependencies:
6479
+ '@octokit/core': 6.1.2
6480
+ '@octokit/plugin-paginate-rest': 11.3.5(@octokit/[email protected])
6481
+ '@octokit/plugin-request-log': 5.3.1(@octokit/[email protected])
6482
+ '@octokit/plugin-rest-endpoint-methods': 13.2.6(@octokit/[email protected])
6483
+
6484
+ '@octokit/[email protected]':
6485
+ dependencies:
6486
+ '@octokit/openapi-types': 22.2.0
6487
+
6488
6489
  dependencies:
6490
  '@ai-sdk/provider': 0.0.12
 
7652
  safe-buffer: 5.1.2
7653
  optional: true
7654
 
7655
7656
+
7657
7658
 
7659
 
10096
  dependencies:
10097
  destr: 2.0.3
10098
  node-fetch-native: 1.6.4
10099
+ ufo: 1.5.4
10100
 
10101
10102
  dependencies:
 
11126
 
11127
11128
 
 
 
11129
11130
 
11131
 
11244
  unist-util-is: 6.0.0
11245
  unist-util-visit-parents: 6.0.1
11246
 
11247
11248
+
11249
11250
 
11251