From b5096735ef0343c08e5b44adf6da46657a2d90b8 Mon Sep 17 00:00:00 2001 From: Stijnus <72551117+Stijnus@users.noreply.github.com> Date: Mon, 3 Feb 2025 01:40:54 +0100 Subject: [PATCH] Update fix more enhanced UI and more details what is fixed, ect --- .../@settings/tabs/update/UpdateTab.tsx | 237 ++++++++++++++---- app/routes/api.update.ts | 197 ++++++++++++++- 2 files changed, 383 insertions(+), 51 deletions(-) diff --git a/app/components/@settings/tabs/update/UpdateTab.tsx b/app/components/@settings/tabs/update/UpdateTab.tsx index a279af47..53c05d0f 100644 --- a/app/components/@settings/tabs/update/UpdateTab.tsx +++ b/app/components/@settings/tabs/update/UpdateTab.tsx @@ -5,6 +5,7 @@ import { logStore } from '~/lib/stores/logs'; import { toast } from 'react-toastify'; import { Dialog, DialogRoot, DialogTitle, DialogDescription, DialogButton } from '~/components/ui/Dialog'; import { classNames } from '~/utils/classNames'; +import { Markdown } from '~/components/chat/Markdown'; interface UpdateProgress { stage: 'fetch' | 'pull' | 'install' | 'build' | 'complete'; @@ -20,6 +21,8 @@ interface UpdateProgress { currentCommit?: string; remoteCommit?: string; updateReady?: boolean; + changelog?: string; + compareUrl?: string; }; } @@ -50,15 +53,52 @@ const UpdateProgressDisplay = ({ progress }: { progress: UpdateProgress }) => ( {progress.details && (
{progress.details.changedFiles && progress.details.changedFiles.length > 0 && ( -
-
Changed Files:
- +
+
Changed Files:
+
+ {/* Group files by type */} + {['Modified', 'Added', 'Deleted'].map((type) => { + const filesOfType = progress.details?.changedFiles?.filter((file) => file.startsWith(type)) || []; + + if (filesOfType.length === 0) { + return null; + } + + return ( +
+
+ {type} ({filesOfType.length}) +
+
+ {filesOfType.map((file, index) => { + const fileName = file.split(': ')[1]; + return ( +
+
+ {fileName} +
+ ); + })} +
+
+ ); + })} +
)} {progress.details.totalSize &&
Total size: {progress.details.totalSize}
} @@ -410,19 +450,100 @@ const UpdateTab = () => { {error &&
{error}
} {/* Show update source information */} -
-

- Updates are fetched from: stackblitz-labs/bolt.diy ( - {isLatestBranch ? 'main' : 'stable'} branch) -

- {updateProgress?.details?.currentCommit && updateProgress?.details?.remoteCommit && ( -

- Current version: {updateProgress.details.currentCommit} - โ†’ - Latest version: {updateProgress.details.remoteCommit} -

- )} -
+ {updateProgress?.details?.currentCommit && updateProgress?.details?.remoteCommit && ( +
+
+
+

+ Updates are fetched from: stackblitz-labs/bolt.diy ( + {isLatestBranch ? 'main' : 'stable'} branch) +

+

+ Current version: {updateProgress.details.currentCommit} + โ†’ + Latest version: {updateProgress.details.remoteCommit} +

+
+ {updateProgress?.details?.compareUrl && ( + + + {updateProgress?.details?.additions !== undefined && updateProgress?.details?.deletions !== undefined && ( +
+
+ Changes: +{updateProgress.details.additions}{' '} + -{updateProgress.details.deletions} +
+ )} +
+ )} + + {/* Add this before the changed files section */} + {updateProgress?.details?.changelog && ( +
+
+
+

Changelog

+
+
+
+ {updateProgress.details.changelog} +
+
+
+ )} + + {/* Add this in the update status card, after the commit info */} + {updateProgress?.details?.compareUrl && ( +
+ + + )} + + {updateProgress?.details?.commitMessages && updateProgress.details.commitMessages.length > 0 && ( +
+

Changes in this Update:

+
+
+ {updateProgress.details.commitMessages.map((section, index) => ( + {section} + ))} +
+
+
+ )} {/* Update dialog */} @@ -435,40 +556,58 @@ const UpdateTab = () => { A new version is available from stackblitz-labs/bolt.diy ( {isLatestBranch ? 'main' : 'stable'} branch)

+ + {updateProgress?.details?.compareUrl && ( +
+ + + )} + {updateProgress?.details?.commitMessages && updateProgress.details.commitMessages.length > 0 && ( -
+

Commit Messages:

-
    +
    {updateProgress.details.commitMessages.map((msg, index) => ( -
  • - {msg} -
  • +
    +
    + {msg} +
    ))} -
-
- )} - {updateProgress?.details?.changedFiles && ( -
-

Changed Files:

-
    - {updateProgress.details.changedFiles.map((file, index) => ( -
  • - {file} -
  • - ))} -
+
)} + {updateProgress?.details?.totalSize && ( -

- Total size: {updateProgress.details.totalSize} -

- )} - {updateProgress?.details?.additions !== undefined && updateProgress?.details?.deletions !== undefined && ( -

- Changes: +{updateProgress.details.additions}{' '} - -{updateProgress.details.deletions} -

+
+
+
+ Total size: {updateProgress.details.totalSize} +
+ {updateProgress?.details?.additions !== undefined && + updateProgress?.details?.deletions !== undefined && ( +
+
+ Changes: +{updateProgress.details.additions}{' '} + -{updateProgress.details.deletions} +
+ )} +
)}
diff --git a/app/routes/api.update.ts b/app/routes/api.update.ts index d25a3883..9f79d4ae 100644 --- a/app/routes/api.update.ts +++ b/app/routes/api.update.ts @@ -24,6 +24,8 @@ interface UpdateProgress { currentCommit?: string; remoteCommit?: string; updateReady?: boolean; + changelog?: string; + compareUrl?: string; }; } @@ -231,9 +233,103 @@ export const action: ActionFunction = async ({ request }) => { // Get commit messages between current and remote try { const { stdout: logOutput } = await execAsync( - `git log --oneline ${currentCommit.trim()}..${remoteCommit.trim()}`, + `git log --pretty=format:"%h|%s|%aI" ${currentCommit.trim()}..${remoteCommit.trim()}`, ); - commitMessages = logOutput.split('\n').filter(Boolean); + + // Parse and group commits by type + const commits = logOutput + .split('\n') + .filter(Boolean) + .map((line) => { + const [hash, subject, timestamp] = line.split('|'); + let type = 'other'; + let message = subject; + + if (subject.startsWith('feat:') || subject.startsWith('feature:')) { + type = 'feature'; + message = subject.replace(/^feat(?:ure)?:/, '').trim(); + } else if (subject.startsWith('fix:')) { + type = 'fix'; + message = subject.replace(/^fix:/, '').trim(); + } else if (subject.startsWith('docs:')) { + type = 'docs'; + message = subject.replace(/^docs:/, '').trim(); + } else if (subject.startsWith('style:')) { + type = 'style'; + message = subject.replace(/^style:/, '').trim(); + } else if (subject.startsWith('refactor:')) { + type = 'refactor'; + message = subject.replace(/^refactor:/, '').trim(); + } else if (subject.startsWith('perf:')) { + type = 'perf'; + message = subject.replace(/^perf:/, '').trim(); + } else if (subject.startsWith('test:')) { + type = 'test'; + message = subject.replace(/^test:/, '').trim(); + } else if (subject.startsWith('build:')) { + type = 'build'; + message = subject.replace(/^build:/, '').trim(); + } else if (subject.startsWith('ci:')) { + type = 'ci'; + message = subject.replace(/^ci:/, '').trim(); + } + + return { + hash, + type, + message, + timestamp: new Date(timestamp), + }; + }); + + // Group commits by type + const groupedCommits = commits.reduce( + (acc, commit) => { + if (!acc[commit.type]) { + acc[commit.type] = []; + } + + acc[commit.type].push(commit); + + return acc; + }, + {} as Record, + ); + + // Format commit messages with emojis and timestamps + const formattedMessages = Object.entries(groupedCommits).map(([type, commits]) => { + const emoji = { + feature: 'โœจ', + fix: '๐Ÿ›', + docs: '๐Ÿ“š', + style: '๐Ÿ’Ž', + refactor: 'โ™ป๏ธ', + perf: 'โšก', + test: '๐Ÿงช', + build: '๐Ÿ› ๏ธ', + ci: 'โš™๏ธ', + other: '๐Ÿ”', + }[type]; + + const title = { + feature: 'Features', + fix: 'Bug Fixes', + docs: 'Documentation', + style: 'Styles', + refactor: 'Code Refactoring', + perf: 'Performance', + test: 'Tests', + build: 'Build', + ci: 'CI', + other: 'Other Changes', + }[type]; + + return `### ${emoji} ${title}\n\n${commits + .map((c) => `* ${c.message} (${c.hash.substring(0, 7)}) - ${c.timestamp.toLocaleString()}`) + .join('\n')}`; + }); + + commitMessages = formattedMessages; } catch { // Handle silently - empty commitMessages array will be used } @@ -260,6 +356,15 @@ export const action: ActionFunction = async ({ request }) => { return; } + // Fetch changelog + sendProgress({ + stage: 'fetch', + message: 'Fetching changelog...', + progress: 95, + }); + + const changelog = await fetchChangelog(currentCommit.trim(), remoteCommit.trim()); + // We have changes, send the details sendProgress({ stage: 'fetch', @@ -274,6 +379,8 @@ export const action: ActionFunction = async ({ request }) => { currentCommit: currentCommit.trim().substring(0, 7), remoteCommit: remoteCommit.trim().substring(0, 7), updateReady: true, + changelog, + compareUrl: `https://github.com/stackblitz-labs/bolt.diy/compare/${currentCommit.trim().substring(0, 7)}...${remoteCommit.trim().substring(0, 7)}`, }, }); @@ -292,6 +399,8 @@ export const action: ActionFunction = async ({ request }) => { currentCommit: currentCommit.trim().substring(0, 7), remoteCommit: remoteCommit.trim().substring(0, 7), updateReady: true, + changelog, + compareUrl: `https://github.com/stackblitz-labs/bolt.diy/compare/${currentCommit.trim().substring(0, 7)}...${remoteCommit.trim().substring(0, 7)}`, }, }); return; @@ -378,3 +487,87 @@ export const action: ActionFunction = async ({ request }) => { ); } }; + +// Add this function to fetch the changelog +async function fetchChangelog(currentCommit: string, remoteCommit: string): Promise { + try { + // First try to get the changelog.md content + const { stdout: changelogContent } = await execAsync('git show upstream/main:changelog.md'); + + // If we have a changelog, return it + if (changelogContent) { + return changelogContent; + } + + // If no changelog.md, generate one in a similar format + let changelog = '# Changes in this Update\n\n'; + + // Get commit messages grouped by type + const { stdout: commitLog } = await execAsync( + `git log --pretty=format:"%h|%s|%b" ${currentCommit.trim()}..${remoteCommit.trim()}`, + ); + + const commits = commitLog.split('\n').filter(Boolean); + const categorizedCommits: Record = { + 'โœจ Features': [], + '๐Ÿ› Bug Fixes': [], + '๐Ÿ“š Documentation': [], + '๐Ÿ’Ž Styles': [], + 'โ™ป๏ธ Code Refactoring': [], + 'โšก Performance': [], + '๐Ÿงช Tests': [], + '๐Ÿ› ๏ธ Build': [], + 'โš™๏ธ CI': [], + '๐Ÿ” Other Changes': [], + }; + + // Categorize commits + for (const commit of commits) { + const [hash, subject] = commit.split('|'); + let category = '๐Ÿ” Other Changes'; + + if (subject.startsWith('feat:') || subject.startsWith('feature:')) { + category = 'โœจ Features'; + } else if (subject.startsWith('fix:')) { + category = '๐Ÿ› Bug Fixes'; + } else if (subject.startsWith('docs:')) { + category = '๐Ÿ“š Documentation'; + } else if (subject.startsWith('style:')) { + category = '๐Ÿ’Ž Styles'; + } else if (subject.startsWith('refactor:')) { + category = 'โ™ป๏ธ Code Refactoring'; + } else if (subject.startsWith('perf:')) { + category = 'โšก Performance'; + } else if (subject.startsWith('test:')) { + category = '๐Ÿงช Tests'; + } else if (subject.startsWith('build:')) { + category = '๐Ÿ› ๏ธ Build'; + } else if (subject.startsWith('ci:')) { + category = 'โš™๏ธ CI'; + } + + const message = subject.includes(':') ? subject.split(':')[1].trim() : subject.trim(); + categorizedCommits[category].push(`* ${message} (${hash.substring(0, 7)})`); + } + + // Build changelog content + for (const [category, commits] of Object.entries(categorizedCommits)) { + if (commits.length > 0) { + changelog += `\n## ${category}\n\n${commits.join('\n')}\n`; + } + } + + // Add stats + const { stdout: stats } = await execAsync(`git diff --shortstat ${currentCommit.trim()}..${remoteCommit.trim()}`); + + if (stats) { + changelog += '\n## ๐Ÿ“Š Stats\n\n'; + changelog += `${stats.trim()}\n`; + } + + return changelog; + } catch (error) { + console.error('Error fetching changelog:', error); + return 'Unable to fetch changelog'; + } +}