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;
|