Github enhancement
Browse files
.cursorrules
CHANGED
|
@@ -8,8 +8,6 @@ bolt.diy (previously oTToDev) is an open-source AI-powered full-stack web develo
|
|
| 8 |
- Focus on best practices and clean code
|
| 9 |
- Provide clear explanations for code changes
|
| 10 |
- Maintain consistent code style with the existing codebase
|
| 11 |
-
- Always write comments that are relevant to the code they describe
|
| 12 |
-
- Always write a changelog what you did and save it in a file called changelog.md in the root of the project
|
| 13 |
|
| 14 |
# Techstack
|
| 15 |
|
|
@@ -134,3 +132,25 @@ bolt.diy (previously oTToDev) is an open-source AI-powered full-stack web develo
|
|
| 134 |
4. Validate your solution against existing patterns
|
| 135 |
5. Test thoroughly before considering work complete
|
| 136 |
6. Review impact on related components
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
- Focus on best practices and clean code
|
| 9 |
- Provide clear explanations for code changes
|
| 10 |
- Maintain consistent code style with the existing codebase
|
|
|
|
|
|
|
| 11 |
|
| 12 |
# Techstack
|
| 13 |
|
|
|
|
| 132 |
4. Validate your solution against existing patterns
|
| 133 |
5. Test thoroughly before considering work complete
|
| 134 |
6. Review impact on related components
|
| 135 |
+
|
| 136 |
+
# UI GUIDELINES
|
| 137 |
+
|
| 138 |
+
- Use consistent colors and typography
|
| 139 |
+
- Ensure UI is responsive and accessible
|
| 140 |
+
- Provide clear feedback for user actions
|
| 141 |
+
- Use meaningful icons and labels
|
| 142 |
+
- Keep UI clean and organized
|
| 143 |
+
- Use consistent spacing and alignment
|
| 144 |
+
- Use consistent naming conventions for components and variables
|
| 145 |
+
- Use consistent file and folder structure
|
| 146 |
+
- Use consistent naming conventions for components and variables
|
| 147 |
+
- Use consistent file and folder structure
|
| 148 |
+
|
| 149 |
+
# Style Guide
|
| 150 |
+
|
| 151 |
+
- Use consistent naming conventions for components and variables
|
| 152 |
+
- Use consistent file and folder structure
|
| 153 |
+
- Respect the Light/Dark mode
|
| 154 |
+
- Don't use white background for dark mode
|
| 155 |
+
- Don't use white text on white background for dark mode
|
| 156 |
+
- Match the style of the existing codebase
|
app/components/settings/connections/components/PushToGitHubDialog.tsx
CHANGED
|
@@ -6,11 +6,16 @@ import { getLocalStorage } from '~/utils/localStorage';
|
|
| 6 |
import { classNames } from '~/utils/classNames';
|
| 7 |
import type { GitHubUserResponse } from '~/types/GitHub';
|
| 8 |
import { logStore } from '~/lib/stores/logs';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
interface PushToGitHubDialogProps {
|
| 11 |
isOpen: boolean;
|
| 12 |
onClose: () => void;
|
| 13 |
-
onPush: (repoName: string, username?: string, token?: string) => Promise<
|
| 14 |
}
|
| 15 |
|
| 16 |
interface GitHubRepo {
|
|
@@ -35,6 +40,7 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
|
| 35 |
const [isFetchingRepos, setIsFetchingRepos] = useState(false);
|
| 36 |
const [showSuccessDialog, setShowSuccessDialog] = useState(false);
|
| 37 |
const [createdRepoUrl, setCreatedRepoUrl] = useState('');
|
|
|
|
| 38 |
|
| 39 |
// Load GitHub connection on mount
|
| 40 |
useEffect(() => {
|
|
@@ -126,10 +132,44 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
|
| 126 |
setIsLoading(true);
|
| 127 |
|
| 128 |
try {
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
-
const repoUrl =
|
| 132 |
setCreatedRepoUrl(repoUrl);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
setShowSuccessDialog(true);
|
| 134 |
} catch (error) {
|
| 135 |
console.error('Error pushing to GitHub:', error);
|
|
@@ -159,26 +199,23 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
|
| 159 |
animate={{ opacity: 1, scale: 1 }}
|
| 160 |
exit={{ opacity: 0, scale: 0.95 }}
|
| 161 |
transition={{ duration: 0.2 }}
|
| 162 |
-
className="w-[90vw] md:w-[
|
| 163 |
>
|
| 164 |
-
<Dialog.Content className="bg-white dark:bg-[#
|
| 165 |
-
<div className="
|
| 166 |
-
<
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
</h3>
|
| 178 |
-
<p className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark">
|
| 179 |
-
Your code has been pushed to GitHub and is ready to use.
|
| 180 |
-
</p>
|
| 181 |
</div>
|
|
|
|
| 182 |
<div className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg p-3 text-left">
|
| 183 |
<p className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark mb-2">
|
| 184 |
Repository URL
|
|
@@ -200,26 +237,58 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
|
| 200 |
</motion.button>
|
| 201 |
</div>
|
| 202 |
</div>
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
<motion.a
|
| 213 |
href={createdRepoUrl}
|
| 214 |
target="_blank"
|
| 215 |
rel="noopener noreferrer"
|
| 216 |
-
className="
|
| 217 |
whileHover={{ scale: 1.02 }}
|
| 218 |
whileTap={{ scale: 0.98 }}
|
| 219 |
>
|
| 220 |
<div className="i-ph:github-logo w-4 h-4" />
|
| 221 |
-
|
| 222 |
</motion.a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
</div>
|
| 224 |
</div>
|
| 225 |
</Dialog.Content>
|
|
|
|
| 6 |
import { classNames } from '~/utils/classNames';
|
| 7 |
import type { GitHubUserResponse } from '~/types/GitHub';
|
| 8 |
import { logStore } from '~/lib/stores/logs';
|
| 9 |
+
import { workbenchStore } from '~/lib/stores/workbench';
|
| 10 |
+
import { extractRelativePath } from '~/utils/diff';
|
| 11 |
+
import { formatSize } from '~/utils/formatSize';
|
| 12 |
+
import type { FileMap, File } from '~/lib/stores/files';
|
| 13 |
+
import { Octokit } from '@octokit/rest';
|
| 14 |
|
| 15 |
interface PushToGitHubDialogProps {
|
| 16 |
isOpen: boolean;
|
| 17 |
onClose: () => void;
|
| 18 |
+
onPush: (repoName: string, username?: string, token?: string, isPrivate?: boolean) => Promise<string>;
|
| 19 |
}
|
| 20 |
|
| 21 |
interface GitHubRepo {
|
|
|
|
| 40 |
const [isFetchingRepos, setIsFetchingRepos] = useState(false);
|
| 41 |
const [showSuccessDialog, setShowSuccessDialog] = useState(false);
|
| 42 |
const [createdRepoUrl, setCreatedRepoUrl] = useState('');
|
| 43 |
+
const [pushedFiles, setPushedFiles] = useState<{ path: string; size: number }[]>([]);
|
| 44 |
|
| 45 |
// Load GitHub connection on mount
|
| 46 |
useEffect(() => {
|
|
|
|
| 132 |
setIsLoading(true);
|
| 133 |
|
| 134 |
try {
|
| 135 |
+
// Check if repository exists first
|
| 136 |
+
const octokit = new Octokit({ auth: connection.token });
|
| 137 |
+
|
| 138 |
+
try {
|
| 139 |
+
await octokit.repos.get({
|
| 140 |
+
owner: connection.user.login,
|
| 141 |
+
repo: repoName,
|
| 142 |
+
});
|
| 143 |
+
|
| 144 |
+
// If we get here, the repo exists
|
| 145 |
+
const confirmOverwrite = window.confirm(
|
| 146 |
+
`Repository "${repoName}" already exists. Do you want to update it? This will add or modify files in the repository.`,
|
| 147 |
+
);
|
| 148 |
+
|
| 149 |
+
if (!confirmOverwrite) {
|
| 150 |
+
setIsLoading(false);
|
| 151 |
+
return;
|
| 152 |
+
}
|
| 153 |
+
} catch (error) {
|
| 154 |
+
// 404 means repo doesn't exist, which is what we want for new repos
|
| 155 |
+
if (error instanceof Error && 'status' in error && error.status !== 404) {
|
| 156 |
+
throw error;
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
|
| 160 |
+
const repoUrl = await onPush(repoName, connection.user.login, connection.token, isPrivate);
|
| 161 |
setCreatedRepoUrl(repoUrl);
|
| 162 |
+
|
| 163 |
+
// Get list of pushed files
|
| 164 |
+
const files = workbenchStore.files.get();
|
| 165 |
+
const filesList = Object.entries(files as FileMap)
|
| 166 |
+
.filter(([, dirent]) => dirent?.type === 'file' && !dirent.isBinary)
|
| 167 |
+
.map(([path, dirent]) => ({
|
| 168 |
+
path: extractRelativePath(path),
|
| 169 |
+
size: new TextEncoder().encode((dirent as File).content || '').length,
|
| 170 |
+
}));
|
| 171 |
+
|
| 172 |
+
setPushedFiles(filesList);
|
| 173 |
setShowSuccessDialog(true);
|
| 174 |
} catch (error) {
|
| 175 |
console.error('Error pushing to GitHub:', error);
|
|
|
|
| 199 |
animate={{ opacity: 1, scale: 1 }}
|
| 200 |
exit={{ opacity: 0, scale: 0.95 }}
|
| 201 |
transition={{ duration: 0.2 }}
|
| 202 |
+
className="w-[90vw] md:w-[600px] max-h-[85vh] overflow-y-auto"
|
| 203 |
>
|
| 204 |
+
<Dialog.Content className="bg-white dark:bg-[#1E1E1E] rounded-lg border border-[#E5E5E5] dark:border-[#333333] shadow-xl">
|
| 205 |
+
<div className="p-6 space-y-4">
|
| 206 |
+
<div className="flex items-center justify-between">
|
| 207 |
+
<div className="flex items-center gap-2 text-green-500">
|
| 208 |
+
<div className="i-ph:check-circle w-5 h-5" />
|
| 209 |
+
<h3 className="text-lg font-medium">Successfully pushed to GitHub</h3>
|
| 210 |
+
</div>
|
| 211 |
+
<Dialog.Close
|
| 212 |
+
onClick={handleClose}
|
| 213 |
+
className="p-2 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400"
|
| 214 |
+
>
|
| 215 |
+
<div className="i-ph:x w-5 h-5" />
|
| 216 |
+
</Dialog.Close>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
</div>
|
| 218 |
+
|
| 219 |
<div className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg p-3 text-left">
|
| 220 |
<p className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark mb-2">
|
| 221 |
Repository URL
|
|
|
|
| 237 |
</motion.button>
|
| 238 |
</div>
|
| 239 |
</div>
|
| 240 |
+
|
| 241 |
+
<div className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg p-3">
|
| 242 |
+
<p className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark mb-2">
|
| 243 |
+
Pushed Files ({pushedFiles.length})
|
| 244 |
+
</p>
|
| 245 |
+
<div className="max-h-[200px] overflow-y-auto custom-scrollbar">
|
| 246 |
+
{pushedFiles.map((file) => (
|
| 247 |
+
<div
|
| 248 |
+
key={file.path}
|
| 249 |
+
className="flex items-center justify-between py-1 text-sm text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark"
|
| 250 |
+
>
|
| 251 |
+
<span className="font-mono truncate flex-1">{file.path}</span>
|
| 252 |
+
<span className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark ml-2">
|
| 253 |
+
{formatSize(file.size)}
|
| 254 |
+
</span>
|
| 255 |
+
</div>
|
| 256 |
+
))}
|
| 257 |
+
</div>
|
| 258 |
+
</div>
|
| 259 |
+
|
| 260 |
+
<div className="flex justify-end gap-2 pt-2">
|
| 261 |
<motion.a
|
| 262 |
href={createdRepoUrl}
|
| 263 |
target="_blank"
|
| 264 |
rel="noopener noreferrer"
|
| 265 |
+
className="px-4 py-2 rounded-lg bg-purple-500 text-white hover:bg-purple-600 text-sm inline-flex items-center gap-2"
|
| 266 |
whileHover={{ scale: 1.02 }}
|
| 267 |
whileTap={{ scale: 0.98 }}
|
| 268 |
>
|
| 269 |
<div className="i-ph:github-logo w-4 h-4" />
|
| 270 |
+
View Repository
|
| 271 |
</motion.a>
|
| 272 |
+
<motion.button
|
| 273 |
+
onClick={() => {
|
| 274 |
+
navigator.clipboard.writeText(createdRepoUrl);
|
| 275 |
+
toast.success('URL copied to clipboard');
|
| 276 |
+
}}
|
| 277 |
+
className="px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#1A1A1A] text-gray-600 dark:text-gray-400 hover:bg-[#E5E5E5] dark:hover:bg-[#252525] text-sm inline-flex items-center gap-2"
|
| 278 |
+
whileHover={{ scale: 1.02 }}
|
| 279 |
+
whileTap={{ scale: 0.98 }}
|
| 280 |
+
>
|
| 281 |
+
<div className="i-ph:copy w-4 h-4" />
|
| 282 |
+
Copy URL
|
| 283 |
+
</motion.button>
|
| 284 |
+
<motion.button
|
| 285 |
+
onClick={handleClose}
|
| 286 |
+
className="px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#1A1A1A] text-gray-600 dark:text-gray-400 hover:bg-[#E5E5E5] dark:hover:bg-[#252525] text-sm"
|
| 287 |
+
whileHover={{ scale: 1.02 }}
|
| 288 |
+
whileTap={{ scale: 0.98 }}
|
| 289 |
+
>
|
| 290 |
+
Close
|
| 291 |
+
</motion.button>
|
| 292 |
</div>
|
| 293 |
</div>
|
| 294 |
</Dialog.Content>
|
app/components/workbench/Workbench.client.tsx
CHANGED
|
@@ -215,11 +215,10 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
|
|
| 215 |
<PushToGitHubDialog
|
| 216 |
isOpen={isPushDialogOpen}
|
| 217 |
onClose={() => setIsPushDialogOpen(false)}
|
| 218 |
-
onPush={async (repoName, username, token) => {
|
| 219 |
try {
|
| 220 |
-
await workbenchStore.pushToGitHub(repoName, undefined, username, token);
|
| 221 |
-
|
| 222 |
-
// Success dialog will be shown by PushToGitHubDialog component
|
| 223 |
} catch (error) {
|
| 224 |
console.error('Error pushing to GitHub:', error);
|
| 225 |
toast.error('Failed to push to GitHub');
|
|
|
|
| 215 |
<PushToGitHubDialog
|
| 216 |
isOpen={isPushDialogOpen}
|
| 217 |
onClose={() => setIsPushDialogOpen(false)}
|
| 218 |
+
onPush={async (repoName, username, token, isPrivate) => {
|
| 219 |
try {
|
| 220 |
+
const repoUrl = await workbenchStore.pushToGitHub(repoName, undefined, username, token, isPrivate);
|
| 221 |
+
return repoUrl;
|
|
|
|
| 222 |
} catch (error) {
|
| 223 |
console.error('Error pushing to GitHub:', error);
|
| 224 |
toast.error('Failed to push to GitHub');
|
app/lib/stores/workbench.ts
CHANGED
|
@@ -434,7 +434,13 @@ export class WorkbenchStore {
|
|
| 434 |
return syncedFiles;
|
| 435 |
}
|
| 436 |
|
| 437 |
-
async pushToGitHub(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
try {
|
| 439 |
// Use cookies if username and token are not provided
|
| 440 |
const githubToken = ghToken || Cookies.get('githubToken');
|
|
@@ -458,7 +464,7 @@ export class WorkbenchStore {
|
|
| 458 |
// Repository doesn't exist, so create a new one
|
| 459 |
const { data: newRepo } = await octokit.repos.createForAuthenticatedUser({
|
| 460 |
name: repoName,
|
| 461 |
-
private:
|
| 462 |
auto_init: true,
|
| 463 |
});
|
| 464 |
repo = newRepo;
|
|
|
|
| 434 |
return syncedFiles;
|
| 435 |
}
|
| 436 |
|
| 437 |
+
async pushToGitHub(
|
| 438 |
+
repoName: string,
|
| 439 |
+
commitMessage?: string,
|
| 440 |
+
githubUsername?: string,
|
| 441 |
+
ghToken?: string,
|
| 442 |
+
isPrivate: boolean = false,
|
| 443 |
+
) {
|
| 444 |
try {
|
| 445 |
// Use cookies if username and token are not provided
|
| 446 |
const githubToken = ghToken || Cookies.get('githubToken');
|
|
|
|
| 464 |
// Repository doesn't exist, so create a new one
|
| 465 |
const { data: newRepo } = await octokit.repos.createForAuthenticatedUser({
|
| 466 |
name: repoName,
|
| 467 |
+
private: isPrivate,
|
| 468 |
auto_init: true,
|
| 469 |
});
|
| 470 |
repo = newRepo;
|