Stijnus commited on
Commit
b509673
Β·
1 Parent(s): f091409

Update fix

Browse files

more enhanced UI and more details what is fixed, ect

app/components/@settings/tabs/update/UpdateTab.tsx CHANGED
@@ -5,6 +5,7 @@ import { logStore } from '~/lib/stores/logs';
5
  import { toast } from 'react-toastify';
6
  import { Dialog, DialogRoot, DialogTitle, DialogDescription, DialogButton } from '~/components/ui/Dialog';
7
  import { classNames } from '~/utils/classNames';
 
8
 
9
  interface UpdateProgress {
10
  stage: 'fetch' | 'pull' | 'install' | 'build' | 'complete';
@@ -20,6 +21,8 @@ interface UpdateProgress {
20
  currentCommit?: string;
21
  remoteCommit?: string;
22
  updateReady?: boolean;
 
 
23
  };
24
  }
25
 
@@ -50,15 +53,52 @@ const UpdateProgressDisplay = ({ progress }: { progress: UpdateProgress }) => (
50
  {progress.details && (
51
  <div className="mt-2 text-sm text-gray-600">
52
  {progress.details.changedFiles && progress.details.changedFiles.length > 0 && (
53
- <div className="mt-2">
54
- <div className="font-medium">Changed Files:</div>
55
- <ul className="list-disc list-inside">
56
- {progress.details.changedFiles.map((file, index) => (
57
- <li key={index} className="ml-2">
58
- {file}
59
- </li>
60
- ))}
61
- </ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  </div>
63
  )}
64
  {progress.details.totalSize && <div className="mt-1">Total size: {progress.details.totalSize}</div>}
@@ -410,19 +450,100 @@ const UpdateTab = () => {
410
  {error && <div className="mt-4 p-4 bg-red-100 text-red-700 rounded">{error}</div>}
411
 
412
  {/* Show update source information */}
413
- <div className="mt-4 text-sm text-bolt-elements-textSecondary">
414
- <p>
415
- Updates are fetched from: <span className="font-mono">stackblitz-labs/bolt.diy</span> (
416
- {isLatestBranch ? 'main' : 'stable'} branch)
417
- </p>
418
- {updateProgress?.details?.currentCommit && updateProgress?.details?.remoteCommit && (
419
- <p className="mt-1">
420
- Current version: <span className="font-mono">{updateProgress.details.currentCommit}</span>
421
- <span className="mx-2">β†’</span>
422
- Latest version: <span className="font-mono">{updateProgress.details.remoteCommit}</span>
423
- </p>
424
- )}
425
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  </motion.div>
427
 
428
  {/* Update dialog */}
@@ -435,40 +556,58 @@ const UpdateTab = () => {
435
  A new version is available from <span className="font-mono">stackblitz-labs/bolt.diy</span> (
436
  {isLatestBranch ? 'main' : 'stable'} branch)
437
  </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  {updateProgress?.details?.commitMessages && updateProgress.details.commitMessages.length > 0 && (
439
- <div className="mb-4">
440
  <p className="font-medium mb-2">Commit Messages:</p>
441
- <ul className="list-disc list-inside space-y-1">
442
  {updateProgress.details.commitMessages.map((msg, index) => (
443
- <li key={index} className="text-sm text-bolt-elements-textSecondary">
444
- {msg}
445
- </li>
446
- ))}
447
- </ul>
448
- </div>
449
- )}
450
- {updateProgress?.details?.changedFiles && (
451
- <div>
452
- <p className="font-medium mb-2">Changed Files:</p>
453
- <ul className="list-disc list-inside space-y-1">
454
- {updateProgress.details.changedFiles.map((file, index) => (
455
- <li key={index} className="text-sm text-bolt-elements-textSecondary">
456
- {file}
457
- </li>
458
  ))}
459
- </ul>
460
  </div>
461
  )}
 
462
  {updateProgress?.details?.totalSize && (
463
- <p className="mt-4 text-sm text-bolt-elements-textSecondary">
464
- Total size: {updateProgress.details.totalSize}
465
- </p>
466
- )}
467
- {updateProgress?.details?.additions !== undefined && updateProgress?.details?.deletions !== undefined && (
468
- <p className="mt-2 text-sm text-bolt-elements-textSecondary">
469
- Changes: <span className="text-green-600">+{updateProgress.details.additions}</span>{' '}
470
- <span className="text-red-600">-{updateProgress.details.deletions}</span>
471
- </p>
 
 
 
 
 
472
  )}
473
  </div>
474
  </DialogDescription>
 
5
  import { toast } from 'react-toastify';
6
  import { Dialog, DialogRoot, DialogTitle, DialogDescription, DialogButton } from '~/components/ui/Dialog';
7
  import { classNames } from '~/utils/classNames';
8
+ import { Markdown } from '~/components/chat/Markdown';
9
 
10
  interface UpdateProgress {
11
  stage: 'fetch' | 'pull' | 'install' | 'build' | 'complete';
 
21
  currentCommit?: string;
22
  remoteCommit?: string;
23
  updateReady?: boolean;
24
+ changelog?: string;
25
+ compareUrl?: string;
26
  };
27
  }
28
 
 
53
  {progress.details && (
54
  <div className="mt-2 text-sm text-gray-600">
55
  {progress.details.changedFiles && progress.details.changedFiles.length > 0 && (
56
+ <div className="mt-4">
57
+ <div className="font-medium mb-2">Changed Files:</div>
58
+ <div className="space-y-2">
59
+ {/* Group files by type */}
60
+ {['Modified', 'Added', 'Deleted'].map((type) => {
61
+ const filesOfType = progress.details?.changedFiles?.filter((file) => file.startsWith(type)) || [];
62
+
63
+ if (filesOfType.length === 0) {
64
+ return null;
65
+ }
66
+
67
+ return (
68
+ <div key={type} className="space-y-1">
69
+ <div
70
+ className={classNames('text-sm font-medium', {
71
+ 'text-blue-500': type === 'Modified',
72
+ 'text-green-500': type === 'Added',
73
+ 'text-red-500': type === 'Deleted',
74
+ })}
75
+ >
76
+ {type} ({filesOfType.length})
77
+ </div>
78
+ <div className="pl-4 space-y-1">
79
+ {filesOfType.map((file, index) => {
80
+ const fileName = file.split(': ')[1];
81
+ return (
82
+ <div key={index} className="text-sm text-bolt-elements-textSecondary flex items-center gap-2">
83
+ <div
84
+ className={classNames('w-4 h-4', {
85
+ 'i-ph:pencil-simple': type === 'Modified',
86
+ 'i-ph:plus': type === 'Added',
87
+ 'i-ph:trash': type === 'Deleted',
88
+ 'text-blue-500': type === 'Modified',
89
+ 'text-green-500': type === 'Added',
90
+ 'text-red-500': type === 'Deleted',
91
+ })}
92
+ />
93
+ <span className="font-mono text-xs">{fileName}</span>
94
+ </div>
95
+ );
96
+ })}
97
+ </div>
98
+ </div>
99
+ );
100
+ })}
101
+ </div>
102
  </div>
103
  )}
104
  {progress.details.totalSize && <div className="mt-1">Total size: {progress.details.totalSize}</div>}
 
450
  {error && <div className="mt-4 p-4 bg-red-100 text-red-700 rounded">{error}</div>}
451
 
452
  {/* Show update source information */}
453
+ {updateProgress?.details?.currentCommit && updateProgress?.details?.remoteCommit && (
454
+ <div className="mt-4 text-sm text-bolt-elements-textSecondary">
455
+ <div className="flex items-center justify-between">
456
+ <div>
457
+ <p>
458
+ Updates are fetched from: <span className="font-mono">stackblitz-labs/bolt.diy</span> (
459
+ {isLatestBranch ? 'main' : 'stable'} branch)
460
+ </p>
461
+ <p className="mt-1">
462
+ Current version: <span className="font-mono">{updateProgress.details.currentCommit}</span>
463
+ <span className="mx-2">β†’</span>
464
+ Latest version: <span className="font-mono">{updateProgress.details.remoteCommit}</span>
465
+ </p>
466
+ </div>
467
+ {updateProgress?.details?.compareUrl && (
468
+ <a
469
+ href={updateProgress.details.compareUrl}
470
+ target="_blank"
471
+ rel="noopener noreferrer"
472
+ className={classNames(
473
+ 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm',
474
+ 'bg-[#F5F5F5] dark:bg-[#1A1A1A]',
475
+ 'hover:bg-purple-500/10 hover:text-purple-500',
476
+ 'dark:hover:bg-purple-500/20 dark:hover:text-purple-500',
477
+ 'text-bolt-elements-textPrimary',
478
+ 'transition-colors duration-200',
479
+ 'w-fit',
480
+ )}
481
+ >
482
+ <div className="i-ph:github-logo w-4 h-4" />
483
+ View Changes on GitHub
484
+ </a>
485
+ )}
486
+ </div>
487
+ {updateProgress?.details?.additions !== undefined && updateProgress?.details?.deletions !== undefined && (
488
+ <div className="mt-2 flex items-center gap-2">
489
+ <div className="i-ph:git-diff text-purple-500 w-4 h-4" />
490
+ Changes: <span className="text-green-600">+{updateProgress.details.additions}</span>{' '}
491
+ <span className="text-red-600">-{updateProgress.details.deletions}</span>
492
+ </div>
493
+ )}
494
+ </div>
495
+ )}
496
+
497
+ {/* Add this before the changed files section */}
498
+ {updateProgress?.details?.changelog && (
499
+ <div className="mb-6">
500
+ <div className="flex items-center gap-2 mb-2">
501
+ <div className="i-ph:scroll text-purple-500 w-5 h-5" />
502
+ <p className="font-medium">Changelog</p>
503
+ </div>
504
+ <div className="bg-[#F5F5F5] dark:bg-[#1A1A1A] rounded-lg p-4 overflow-auto max-h-[300px]">
505
+ <div className="prose dark:prose-invert prose-sm max-w-none">
506
+ <Markdown>{updateProgress.details.changelog}</Markdown>
507
+ </div>
508
+ </div>
509
+ </div>
510
+ )}
511
+
512
+ {/* Add this in the update status card, after the commit info */}
513
+ {updateProgress?.details?.compareUrl && (
514
+ <div className="mt-4">
515
+ <a
516
+ href={updateProgress.details.compareUrl}
517
+ target="_blank"
518
+ rel="noopener noreferrer"
519
+ className={classNames(
520
+ 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm',
521
+ 'bg-[#F5F5F5] dark:bg-[#1A1A1A]',
522
+ 'hover:bg-purple-500/10 hover:text-purple-500',
523
+ 'dark:hover:bg-purple-500/20 dark:hover:text-purple-500',
524
+ 'text-bolt-elements-textPrimary',
525
+ 'transition-colors duration-200',
526
+ 'w-fit',
527
+ )}
528
+ >
529
+ <div className="i-ph:github-logo w-4 h-4" />
530
+ View Changes on GitHub
531
+ </a>
532
+ </div>
533
+ )}
534
+
535
+ {updateProgress?.details?.commitMessages && updateProgress.details.commitMessages.length > 0 && (
536
+ <div className="mb-6">
537
+ <p className="font-medium mb-2">Changes in this Update:</p>
538
+ <div className="bg-[#F5F5F5] dark:bg-[#1A1A1A] rounded-lg p-4 overflow-auto max-h-[400px]">
539
+ <div className="prose dark:prose-invert prose-sm max-w-none">
540
+ {updateProgress.details.commitMessages.map((section, index) => (
541
+ <Markdown key={index}>{section}</Markdown>
542
+ ))}
543
+ </div>
544
+ </div>
545
+ </div>
546
+ )}
547
  </motion.div>
548
 
549
  {/* Update dialog */}
 
556
  A new version is available from <span className="font-mono">stackblitz-labs/bolt.diy</span> (
557
  {isLatestBranch ? 'main' : 'stable'} branch)
558
  </p>
559
+
560
+ {updateProgress?.details?.compareUrl && (
561
+ <div className="mb-6">
562
+ <a
563
+ href={updateProgress.details.compareUrl}
564
+ target="_blank"
565
+ rel="noopener noreferrer"
566
+ className={classNames(
567
+ 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm',
568
+ 'bg-[#F5F5F5] dark:bg-[#1A1A1A]',
569
+ 'hover:bg-purple-500/10 hover:text-purple-500',
570
+ 'dark:hover:bg-purple-500/20 dark:hover:text-purple-500',
571
+ 'text-bolt-elements-textPrimary',
572
+ 'transition-colors duration-200',
573
+ 'w-fit',
574
+ )}
575
+ >
576
+ <div className="i-ph:github-logo w-4 h-4" />
577
+ View Changes on GitHub
578
+ </a>
579
+ </div>
580
+ )}
581
+
582
  {updateProgress?.details?.commitMessages && updateProgress.details.commitMessages.length > 0 && (
583
+ <div className="mb-6">
584
  <p className="font-medium mb-2">Commit Messages:</p>
585
+ <div className="bg-[#F5F5F5] dark:bg-[#1A1A1A] rounded-lg p-3 space-y-2">
586
  {updateProgress.details.commitMessages.map((msg, index) => (
587
+ <div key={index} className="text-sm text-bolt-elements-textSecondary flex items-start gap-2">
588
+ <div className="i-ph:git-commit text-purple-500 w-4 h-4 mt-0.5 flex-shrink-0" />
589
+ <span>{msg}</span>
590
+ </div>
 
 
 
 
 
 
 
 
 
 
 
591
  ))}
592
+ </div>
593
  </div>
594
  )}
595
+
596
  {updateProgress?.details?.totalSize && (
597
+ <div className="flex items-center gap-4 text-sm text-bolt-elements-textSecondary">
598
+ <div className="flex items-center gap-2">
599
+ <div className="i-ph:file text-purple-500 w-4 h-4" />
600
+ Total size: {updateProgress.details.totalSize}
601
+ </div>
602
+ {updateProgress?.details?.additions !== undefined &&
603
+ updateProgress?.details?.deletions !== undefined && (
604
+ <div className="flex items-center gap-2">
605
+ <div className="i-ph:git-diff text-purple-500 w-4 h-4" />
606
+ Changes: <span className="text-green-600">+{updateProgress.details.additions}</span>{' '}
607
+ <span className="text-red-600">-{updateProgress.details.deletions}</span>
608
+ </div>
609
+ )}
610
+ </div>
611
  )}
612
  </div>
613
  </DialogDescription>
app/routes/api.update.ts CHANGED
@@ -24,6 +24,8 @@ interface UpdateProgress {
24
  currentCommit?: string;
25
  remoteCommit?: string;
26
  updateReady?: boolean;
 
 
27
  };
28
  }
29
 
@@ -231,9 +233,103 @@ export const action: ActionFunction = async ({ request }) => {
231
  // Get commit messages between current and remote
232
  try {
233
  const { stdout: logOutput } = await execAsync(
234
- `git log --oneline ${currentCommit.trim()}..${remoteCommit.trim()}`,
235
  );
236
- commitMessages = logOutput.split('\n').filter(Boolean);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  } catch {
238
  // Handle silently - empty commitMessages array will be used
239
  }
@@ -260,6 +356,15 @@ export const action: ActionFunction = async ({ request }) => {
260
  return;
261
  }
262
 
 
 
 
 
 
 
 
 
 
263
  // We have changes, send the details
264
  sendProgress({
265
  stage: 'fetch',
@@ -274,6 +379,8 @@ export const action: ActionFunction = async ({ request }) => {
274
  currentCommit: currentCommit.trim().substring(0, 7),
275
  remoteCommit: remoteCommit.trim().substring(0, 7),
276
  updateReady: true,
 
 
277
  },
278
  });
279
 
@@ -292,6 +399,8 @@ export const action: ActionFunction = async ({ request }) => {
292
  currentCommit: currentCommit.trim().substring(0, 7),
293
  remoteCommit: remoteCommit.trim().substring(0, 7),
294
  updateReady: true,
 
 
295
  },
296
  });
297
  return;
@@ -378,3 +487,87 @@ export const action: ActionFunction = async ({ request }) => {
378
  );
379
  }
380
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  currentCommit?: string;
25
  remoteCommit?: string;
26
  updateReady?: boolean;
27
+ changelog?: string;
28
+ compareUrl?: string;
29
  };
30
  }
31
 
 
233
  // Get commit messages between current and remote
234
  try {
235
  const { stdout: logOutput } = await execAsync(
236
+ `git log --pretty=format:"%h|%s|%aI" ${currentCommit.trim()}..${remoteCommit.trim()}`,
237
  );
238
+
239
+ // Parse and group commits by type
240
+ const commits = logOutput
241
+ .split('\n')
242
+ .filter(Boolean)
243
+ .map((line) => {
244
+ const [hash, subject, timestamp] = line.split('|');
245
+ let type = 'other';
246
+ let message = subject;
247
+
248
+ if (subject.startsWith('feat:') || subject.startsWith('feature:')) {
249
+ type = 'feature';
250
+ message = subject.replace(/^feat(?:ure)?:/, '').trim();
251
+ } else if (subject.startsWith('fix:')) {
252
+ type = 'fix';
253
+ message = subject.replace(/^fix:/, '').trim();
254
+ } else if (subject.startsWith('docs:')) {
255
+ type = 'docs';
256
+ message = subject.replace(/^docs:/, '').trim();
257
+ } else if (subject.startsWith('style:')) {
258
+ type = 'style';
259
+ message = subject.replace(/^style:/, '').trim();
260
+ } else if (subject.startsWith('refactor:')) {
261
+ type = 'refactor';
262
+ message = subject.replace(/^refactor:/, '').trim();
263
+ } else if (subject.startsWith('perf:')) {
264
+ type = 'perf';
265
+ message = subject.replace(/^perf:/, '').trim();
266
+ } else if (subject.startsWith('test:')) {
267
+ type = 'test';
268
+ message = subject.replace(/^test:/, '').trim();
269
+ } else if (subject.startsWith('build:')) {
270
+ type = 'build';
271
+ message = subject.replace(/^build:/, '').trim();
272
+ } else if (subject.startsWith('ci:')) {
273
+ type = 'ci';
274
+ message = subject.replace(/^ci:/, '').trim();
275
+ }
276
+
277
+ return {
278
+ hash,
279
+ type,
280
+ message,
281
+ timestamp: new Date(timestamp),
282
+ };
283
+ });
284
+
285
+ // Group commits by type
286
+ const groupedCommits = commits.reduce(
287
+ (acc, commit) => {
288
+ if (!acc[commit.type]) {
289
+ acc[commit.type] = [];
290
+ }
291
+
292
+ acc[commit.type].push(commit);
293
+
294
+ return acc;
295
+ },
296
+ {} as Record<string, typeof commits>,
297
+ );
298
+
299
+ // Format commit messages with emojis and timestamps
300
+ const formattedMessages = Object.entries(groupedCommits).map(([type, commits]) => {
301
+ const emoji = {
302
+ feature: '✨',
303
+ fix: 'πŸ›',
304
+ docs: 'πŸ“š',
305
+ style: 'πŸ’Ž',
306
+ refactor: '♻️',
307
+ perf: '⚑',
308
+ test: 'πŸ§ͺ',
309
+ build: 'πŸ› οΈ',
310
+ ci: 'βš™οΈ',
311
+ other: 'πŸ”',
312
+ }[type];
313
+
314
+ const title = {
315
+ feature: 'Features',
316
+ fix: 'Bug Fixes',
317
+ docs: 'Documentation',
318
+ style: 'Styles',
319
+ refactor: 'Code Refactoring',
320
+ perf: 'Performance',
321
+ test: 'Tests',
322
+ build: 'Build',
323
+ ci: 'CI',
324
+ other: 'Other Changes',
325
+ }[type];
326
+
327
+ return `### ${emoji} ${title}\n\n${commits
328
+ .map((c) => `* ${c.message} (${c.hash.substring(0, 7)}) - ${c.timestamp.toLocaleString()}`)
329
+ .join('\n')}`;
330
+ });
331
+
332
+ commitMessages = formattedMessages;
333
  } catch {
334
  // Handle silently - empty commitMessages array will be used
335
  }
 
356
  return;
357
  }
358
 
359
+ // Fetch changelog
360
+ sendProgress({
361
+ stage: 'fetch',
362
+ message: 'Fetching changelog...',
363
+ progress: 95,
364
+ });
365
+
366
+ const changelog = await fetchChangelog(currentCommit.trim(), remoteCommit.trim());
367
+
368
  // We have changes, send the details
369
  sendProgress({
370
  stage: 'fetch',
 
379
  currentCommit: currentCommit.trim().substring(0, 7),
380
  remoteCommit: remoteCommit.trim().substring(0, 7),
381
  updateReady: true,
382
+ changelog,
383
+ compareUrl: `https://github.com/stackblitz-labs/bolt.diy/compare/${currentCommit.trim().substring(0, 7)}...${remoteCommit.trim().substring(0, 7)}`,
384
  },
385
  });
386
 
 
399
  currentCommit: currentCommit.trim().substring(0, 7),
400
  remoteCommit: remoteCommit.trim().substring(0, 7),
401
  updateReady: true,
402
+ changelog,
403
+ compareUrl: `https://github.com/stackblitz-labs/bolt.diy/compare/${currentCommit.trim().substring(0, 7)}...${remoteCommit.trim().substring(0, 7)}`,
404
  },
405
  });
406
  return;
 
487
  );
488
  }
489
  };
490
+
491
+ // Add this function to fetch the changelog
492
+ async function fetchChangelog(currentCommit: string, remoteCommit: string): Promise<string> {
493
+ try {
494
+ // First try to get the changelog.md content
495
+ const { stdout: changelogContent } = await execAsync('git show upstream/main:changelog.md');
496
+
497
+ // If we have a changelog, return it
498
+ if (changelogContent) {
499
+ return changelogContent;
500
+ }
501
+
502
+ // If no changelog.md, generate one in a similar format
503
+ let changelog = '# Changes in this Update\n\n';
504
+
505
+ // Get commit messages grouped by type
506
+ const { stdout: commitLog } = await execAsync(
507
+ `git log --pretty=format:"%h|%s|%b" ${currentCommit.trim()}..${remoteCommit.trim()}`,
508
+ );
509
+
510
+ const commits = commitLog.split('\n').filter(Boolean);
511
+ const categorizedCommits: Record<string, string[]> = {
512
+ '✨ Features': [],
513
+ 'πŸ› Bug Fixes': [],
514
+ 'πŸ“š Documentation': [],
515
+ 'πŸ’Ž Styles': [],
516
+ '♻️ Code Refactoring': [],
517
+ '⚑ Performance': [],
518
+ 'πŸ§ͺ Tests': [],
519
+ 'πŸ› οΈ Build': [],
520
+ 'βš™οΈ CI': [],
521
+ 'πŸ” Other Changes': [],
522
+ };
523
+
524
+ // Categorize commits
525
+ for (const commit of commits) {
526
+ const [hash, subject] = commit.split('|');
527
+ let category = 'πŸ” Other Changes';
528
+
529
+ if (subject.startsWith('feat:') || subject.startsWith('feature:')) {
530
+ category = '✨ Features';
531
+ } else if (subject.startsWith('fix:')) {
532
+ category = 'πŸ› Bug Fixes';
533
+ } else if (subject.startsWith('docs:')) {
534
+ category = 'πŸ“š Documentation';
535
+ } else if (subject.startsWith('style:')) {
536
+ category = 'πŸ’Ž Styles';
537
+ } else if (subject.startsWith('refactor:')) {
538
+ category = '♻️ Code Refactoring';
539
+ } else if (subject.startsWith('perf:')) {
540
+ category = '⚑ Performance';
541
+ } else if (subject.startsWith('test:')) {
542
+ category = 'πŸ§ͺ Tests';
543
+ } else if (subject.startsWith('build:')) {
544
+ category = 'πŸ› οΈ Build';
545
+ } else if (subject.startsWith('ci:')) {
546
+ category = 'βš™οΈ CI';
547
+ }
548
+
549
+ const message = subject.includes(':') ? subject.split(':')[1].trim() : subject.trim();
550
+ categorizedCommits[category].push(`* ${message} (${hash.substring(0, 7)})`);
551
+ }
552
+
553
+ // Build changelog content
554
+ for (const [category, commits] of Object.entries(categorizedCommits)) {
555
+ if (commits.length > 0) {
556
+ changelog += `\n## ${category}\n\n${commits.join('\n')}\n`;
557
+ }
558
+ }
559
+
560
+ // Add stats
561
+ const { stdout: stats } = await execAsync(`git diff --shortstat ${currentCommit.trim()}..${remoteCommit.trim()}`);
562
+
563
+ if (stats) {
564
+ changelog += '\n## πŸ“Š Stats\n\n';
565
+ changelog += `${stats.trim()}\n`;
566
+ }
567
+
568
+ return changelog;
569
+ } catch (error) {
570
+ console.error('Error fetching changelog:', error);
571
+ return 'Unable to fetch changelog';
572
+ }
573
+ }