@assistkick/create 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/scaffolder.d.ts +12 -1
- package/dist/src/scaffolder.js +40 -3
- package/dist/src/scaffolder.js.map +1 -1
- package/package.json +1 -1
- package/templates/assistkick-product-system/package.json +1 -1
- package/templates/assistkick-product-system/packages/backend/package.json +1 -0
- package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
- package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +391 -23
- package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
- package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
- package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
- package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
- package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +149 -14
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
- package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
- package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
- package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
- package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
- package/templates/assistkick-product-system/packages/shared/package.json +2 -1
- package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
- package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
- package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
- package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
- package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
- package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
- package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
- package/templates/skills/assistkick-debugger/SKILL.md +23 -23
- package/templates/skills/assistkick-developer/SKILL.md +59 -63
- package/templates/skills/assistkick-interview/SKILL.md +26 -26
- package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
- package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Git repository routes — connect/manage git repos for projects.
|
|
3
|
-
* POST /api/projects/:id/git/connect
|
|
4
|
-
* POST /api/projects/:id/git/init
|
|
5
|
-
* POST /api/projects/:id/git/disconnect
|
|
6
|
-
* GET /api/projects/:id/git/status
|
|
7
|
-
* POST /api/projects/:id/git/test
|
|
8
|
-
* GET /api/projects/:id/git/installations
|
|
9
|
-
* GET /api/projects/:id/git/repos
|
|
10
|
-
* POST /api/projects/:id/git/ssh/generate
|
|
11
|
-
* POST /api/projects/:id/git/ssh/connect
|
|
3
|
+
* POST /api/projects/:id/git/connect — connect a git repo (remote add + fetch)
|
|
4
|
+
* POST /api/projects/:id/git/init — initialize a new local git repo
|
|
5
|
+
* POST /api/projects/:id/git/disconnect — disconnect a git repo
|
|
6
|
+
* GET /api/projects/:id/git/status — get git repo status
|
|
7
|
+
* POST /api/projects/:id/git/test — test GitHub App connection
|
|
8
|
+
* GET /api/projects/:id/git/installations — list GitHub App installations
|
|
9
|
+
* GET /api/projects/:id/git/repos — list repos for an installation
|
|
10
|
+
* POST /api/projects/:id/git/ssh/generate — generate SSH keypair for project
|
|
11
|
+
* POST /api/projects/:id/git/ssh/connect — connect repo via SSH (remote add + fetch)
|
|
12
|
+
* GET /api/projects/:id/git/branches — list local + remote branches with current
|
|
13
|
+
* POST /api/projects/:id/git/checkout — switch branch (optional commit message for dirty state)
|
|
14
|
+
* POST /api/projects/:id/git/branches — create and checkout a new branch
|
|
15
|
+
* GET /api/projects/:id/git/remotes — list configured remotes
|
|
16
|
+
* POST /api/projects/:id/git/remotes — add a new remote
|
|
17
|
+
* POST /api/projects/:id/git/fetch — fetch from remote
|
|
18
|
+
* POST /api/projects/:id/git/pull — pull current branch from remote
|
|
19
|
+
* POST /api/projects/:id/git/push — push current branch to remote
|
|
20
|
+
* GET /api/projects/:id/git/ahead-behind — get ahead/behind commit counts
|
|
21
|
+
* GET /api/projects/:id/git/initial-commit — check if only initial empty commit exists
|
|
12
22
|
*/
|
|
13
23
|
|
|
14
24
|
import { Router } from 'express';
|
|
15
25
|
import type { ProjectService } from '../services/project_service.js';
|
|
16
26
|
import type { GitHubAppService } from '../services/github_app_service.js';
|
|
17
27
|
import type { ProjectWorkspaceService } from '../services/project_workspace_service.js';
|
|
18
|
-
import
|
|
28
|
+
import { SshKeyService } from '../services/ssh_key_service.js';
|
|
19
29
|
|
|
20
30
|
interface GitRoutesDeps {
|
|
21
31
|
projectService: ProjectService;
|
|
@@ -56,24 +66,39 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
56
66
|
}
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
//
|
|
69
|
+
// Add remote and fetch (replaces old rm -rf + clone pattern)
|
|
60
70
|
await workspaceService.cloneRepo(id, cloneUrl);
|
|
61
71
|
|
|
62
72
|
// Detect default branch
|
|
63
73
|
const detectedBranch = await workspaceService.getDefaultBranch(id);
|
|
64
74
|
const effectiveBranch = baseBranch || detectedBranch;
|
|
65
75
|
|
|
76
|
+
// If local repo only has the initial empty commit, reset to remote content
|
|
77
|
+
const onlyInitial = await workspaceService.hasOnlyInitialCommit(id);
|
|
78
|
+
if (onlyInitial) {
|
|
79
|
+
try {
|
|
80
|
+
await workspaceService.resetToRemoteBranch(id, `origin/${effectiveBranch}`);
|
|
81
|
+
} catch {
|
|
82
|
+
// Remote might be empty too — that's fine
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
// Save repo metadata to project
|
|
67
|
-
|
|
87
|
+
let updated = await projectService.connectRepo(id, {
|
|
68
88
|
repoUrl: githubRepoFullName ? `https://github.com/${githubRepoFullName}.git` : repoUrl,
|
|
69
89
|
githubInstallationId,
|
|
70
90
|
githubRepoFullName,
|
|
71
91
|
baseBranch: effectiveBranch,
|
|
72
92
|
});
|
|
73
93
|
|
|
94
|
+
// Set auth method to 'github_app' when connecting via GitHub App
|
|
95
|
+
if (githubInstallationId) {
|
|
96
|
+
updated = await projectService.setGitHubAppAuth(id);
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
res.json({ project: updated });
|
|
75
100
|
} catch (err: any) {
|
|
76
|
-
log('GIT', `Connect repo failed
|
|
101
|
+
log('GIT', `Connect repo failed:`, err.stack || err.message);
|
|
77
102
|
res.status(500).json({ error: `Failed to connect repo: ${err.message}` });
|
|
78
103
|
}
|
|
79
104
|
});
|
|
@@ -101,7 +126,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
101
126
|
|
|
102
127
|
res.json({ project: updated });
|
|
103
128
|
} catch (err: any) {
|
|
104
|
-
log('GIT', `Init repo failed
|
|
129
|
+
log('GIT', `Init repo failed:`, err.stack || err.message);
|
|
105
130
|
res.status(500).json({ error: `Failed to initialize repo: ${err.message}` });
|
|
106
131
|
}
|
|
107
132
|
});
|
|
@@ -115,7 +140,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
115
140
|
const updated = await projectService.disconnectRepo(id);
|
|
116
141
|
res.json({ project: updated });
|
|
117
142
|
} catch (err: any) {
|
|
118
|
-
log('GIT', `Disconnect repo failed
|
|
143
|
+
log('GIT', `Disconnect repo failed:`, err.stack || err.message);
|
|
119
144
|
if (err.message === 'Project not found') {
|
|
120
145
|
res.status(404).json({ error: err.message });
|
|
121
146
|
return;
|
|
@@ -148,7 +173,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
148
173
|
sshConfigured: sshKeyService.isConfigured(),
|
|
149
174
|
});
|
|
150
175
|
} catch (err: any) {
|
|
151
|
-
log('GIT', `Get git status failed
|
|
176
|
+
log('GIT', `Get git status failed:`, err.stack || err.message);
|
|
152
177
|
res.status(500).json({ error: `Failed to get git status: ${err.message}` });
|
|
153
178
|
}
|
|
154
179
|
});
|
|
@@ -177,7 +202,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
177
202
|
})),
|
|
178
203
|
});
|
|
179
204
|
} catch (err: any) {
|
|
180
|
-
log('GIT', `Test GitHub App connection failed
|
|
205
|
+
log('GIT', `Test GitHub App connection failed:`, err.stack || err.message);
|
|
181
206
|
res.json({
|
|
182
207
|
configured: true,
|
|
183
208
|
error: `Connection test failed: ${err.message}`,
|
|
@@ -203,7 +228,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
203
228
|
})),
|
|
204
229
|
});
|
|
205
230
|
} catch (err: any) {
|
|
206
|
-
log('GIT', `List installations failed
|
|
231
|
+
log('GIT', `List installations failed:`, err.stack || err.message);
|
|
207
232
|
res.status(500).json({ error: `Failed to list installations: ${err.message}` });
|
|
208
233
|
}
|
|
209
234
|
});
|
|
@@ -229,7 +254,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
229
254
|
})),
|
|
230
255
|
});
|
|
231
256
|
} catch (err: any) {
|
|
232
|
-
log('GIT', `List repos failed
|
|
257
|
+
log('GIT', `List repos failed:`, err.stack || err.message);
|
|
233
258
|
res.status(500).json({ error: `Failed to list repos: ${err.message}` });
|
|
234
259
|
}
|
|
235
260
|
});
|
|
@@ -241,7 +266,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
241
266
|
|
|
242
267
|
try {
|
|
243
268
|
if (!sshKeyService.isConfigured()) {
|
|
244
|
-
res.status(400).json({ error:
|
|
269
|
+
res.status(400).json({ error: `ENCRYPTION_KEY environment variable is not configured. ${SshKeyService.KEY_HELP}` });
|
|
245
270
|
return;
|
|
246
271
|
}
|
|
247
272
|
|
|
@@ -254,6 +279,9 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
254
279
|
const keyPair = sshKeyService.generateKeyPair();
|
|
255
280
|
const encryptedPrivateKey = sshKeyService.encrypt(keyPair.privateKey);
|
|
256
281
|
|
|
282
|
+
// Install the key to ~/.ssh/ so CLI git can use the same key
|
|
283
|
+
await sshKeyService.installKeyForCli(keyPair);
|
|
284
|
+
|
|
257
285
|
const updated = await projectService.setSshKeys(id, {
|
|
258
286
|
sshPrivateKeyEncrypted: encryptedPrivateKey,
|
|
259
287
|
sshPublicKey: keyPair.publicKey,
|
|
@@ -267,7 +295,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
267
295
|
publicKey: keyPair.publicKey,
|
|
268
296
|
});
|
|
269
297
|
} catch (err: any) {
|
|
270
|
-
log('GIT', `Generate SSH key failed
|
|
298
|
+
log('GIT', `Generate SSH key failed:`, err.stack || err.message);
|
|
271
299
|
res.status(500).json({ error: `Failed to generate SSH key: ${err.message}` });
|
|
272
300
|
}
|
|
273
301
|
});
|
|
@@ -295,13 +323,23 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
295
323
|
return;
|
|
296
324
|
}
|
|
297
325
|
|
|
298
|
-
//
|
|
326
|
+
// Add remote and fetch using SSH (no rm -rf + clone)
|
|
299
327
|
await workspaceService.cloneRepoSsh(id, repoUrl, project.sshPrivateKeyEncrypted);
|
|
300
328
|
|
|
301
329
|
// Detect default branch
|
|
302
330
|
const detectedBranch = await workspaceService.getDefaultBranch(id);
|
|
303
331
|
const effectiveBranch = baseBranch || detectedBranch;
|
|
304
332
|
|
|
333
|
+
// If local repo only has the initial empty commit, reset to remote content
|
|
334
|
+
const onlyInitial = await workspaceService.hasOnlyInitialCommit(id);
|
|
335
|
+
if (onlyInitial) {
|
|
336
|
+
try {
|
|
337
|
+
await workspaceService.resetToRemoteBranch(id, `origin/${effectiveBranch}`);
|
|
338
|
+
} catch {
|
|
339
|
+
// Remote might be empty too — that's fine
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
305
343
|
// Save repo metadata
|
|
306
344
|
const updated = await projectService.connectRepo(id, {
|
|
307
345
|
repoUrl,
|
|
@@ -317,10 +355,340 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
|
|
|
317
355
|
},
|
|
318
356
|
});
|
|
319
357
|
} catch (err: any) {
|
|
320
|
-
log('GIT', `SSH connect repo failed
|
|
358
|
+
log('GIT', `SSH connect repo failed:`, err.stack || err.message);
|
|
321
359
|
res.status(500).json({ error: `Failed to connect repo via SSH: ${err.message}` });
|
|
322
360
|
}
|
|
323
361
|
});
|
|
324
362
|
|
|
363
|
+
// GET /api/projects/:id/git/remotes — list configured remotes
|
|
364
|
+
router.get('/remotes', async (req, res) => {
|
|
365
|
+
const { id } = req.params;
|
|
366
|
+
log('GIT', `GET /api/projects/${id}/git/remotes`);
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
370
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const remotes = await workspaceService.listRemotes(id);
|
|
374
|
+
res.json({ remotes });
|
|
375
|
+
} catch (err: any) {
|
|
376
|
+
log('GIT', `List remotes failed: ${err.message}`);
|
|
377
|
+
res.status(500).json({ error: `Failed to list remotes: ${err.message}` });
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// POST /api/projects/:id/git/remotes — add a new remote
|
|
382
|
+
router.post('/remotes', async (req, res) => {
|
|
383
|
+
const { id } = req.params;
|
|
384
|
+
const { name, url } = req.body;
|
|
385
|
+
log('GIT', `POST /api/projects/${id}/git/remotes name="${name}" url="${url}"`);
|
|
386
|
+
|
|
387
|
+
if (!url) {
|
|
388
|
+
res.status(400).json({ error: 'url is required' });
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const remoteName = name || 'origin';
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
396
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
await workspaceService.addRemote(id, remoteName, url);
|
|
400
|
+
|
|
401
|
+
// Save repo URL to project metadata
|
|
402
|
+
if (remoteName === 'origin') {
|
|
403
|
+
await projectService.connectRepo(id, { repoUrl: url });
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const remotes = await workspaceService.listRemotes(id);
|
|
407
|
+
res.json({ success: true, remotes });
|
|
408
|
+
} catch (err: any) {
|
|
409
|
+
log('GIT', `Add remote failed: ${err.message}`);
|
|
410
|
+
res.status(500).json({ error: `Failed to add remote: ${err.message}` });
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// POST /api/projects/:id/git/fetch — fetch from remote
|
|
415
|
+
router.post('/fetch', async (req, res) => {
|
|
416
|
+
const { id } = req.params;
|
|
417
|
+
const { remote = 'origin' } = req.body || {};
|
|
418
|
+
log('GIT', `POST /api/projects/${id}/git/fetch remote="${remote}"`);
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
422
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const project = await projectService.getById(id);
|
|
427
|
+
if (!project) {
|
|
428
|
+
res.status(404).json({ error: 'Project not found' });
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (project.gitAuthMethod === 'ssh_key' && project.sshPrivateKeyEncrypted) {
|
|
433
|
+
await workspaceService.fetchRemoteSsh(id, project.sshPrivateKeyEncrypted, remote);
|
|
434
|
+
} else if (project.gitAuthMethod === 'github_app' && project.githubInstallationId && project.githubRepoFullName) {
|
|
435
|
+
const token = await githubAppService.getInstallationToken(project.githubInstallationId);
|
|
436
|
+
const authUrl = `https://x-access-token:${token}@github.com/${project.githubRepoFullName}.git`;
|
|
437
|
+
const wsPath = workspaceService.getWorkspacePath(id);
|
|
438
|
+
await workspaceService['claudeService'].spawnCommand('git', ['remote', 'set-url', 'origin', authUrl], wsPath);
|
|
439
|
+
await workspaceService.fetchRemote(id, remote);
|
|
440
|
+
await workspaceService['claudeService'].spawnCommand(
|
|
441
|
+
'git', ['remote', 'set-url', 'origin', `https://github.com/${project.githubRepoFullName}.git`], wsPath,
|
|
442
|
+
).catch(() => {});
|
|
443
|
+
} else {
|
|
444
|
+
await workspaceService.fetchRemote(id, remote);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const aheadBehind = await workspaceService.getAheadBehind(id);
|
|
448
|
+
res.json({ success: true, ...aheadBehind });
|
|
449
|
+
} catch (err: any) {
|
|
450
|
+
log('GIT', `Fetch failed: ${err.message}`);
|
|
451
|
+
res.status(500).json({ error: `Failed to fetch: ${err.message}` });
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// POST /api/projects/:id/git/pull — pull current branch from remote
|
|
456
|
+
router.post('/pull', async (req, res) => {
|
|
457
|
+
const { id } = req.params;
|
|
458
|
+
const { force } = req.body || {};
|
|
459
|
+
log('GIT', `POST /api/projects/${id}/git/pull force=${force}`);
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
463
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const project = await projectService.getById(id);
|
|
468
|
+
if (!project) {
|
|
469
|
+
res.status(404).json({ error: 'Project not found' });
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// If force=true, do a hard reset to the remote branch (for pulling into projects with only initial commit)
|
|
474
|
+
if (force) {
|
|
475
|
+
const branch = await workspaceService.getDefaultBranch(id);
|
|
476
|
+
await workspaceService.resetToRemoteBranch(id, `origin/${branch}`);
|
|
477
|
+
// Checkout the remote branch properly
|
|
478
|
+
try {
|
|
479
|
+
await workspaceService.checkoutBranch(id, branch);
|
|
480
|
+
} catch {
|
|
481
|
+
// Already on the branch
|
|
482
|
+
}
|
|
483
|
+
const aheadBehind = await workspaceService.getAheadBehind(id);
|
|
484
|
+
res.json({ success: true, ...aheadBehind });
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (project.gitAuthMethod === 'ssh_key' && project.sshPrivateKeyEncrypted) {
|
|
489
|
+
await workspaceService.pullBranchSsh(id, project.sshPrivateKeyEncrypted);
|
|
490
|
+
} else if (project.gitAuthMethod === 'github_app' && project.githubInstallationId && project.githubRepoFullName) {
|
|
491
|
+
const token = await githubAppService.getInstallationToken(project.githubInstallationId);
|
|
492
|
+
await workspaceService.pullBranch(id, token, project.githubRepoFullName);
|
|
493
|
+
} else {
|
|
494
|
+
await workspaceService.pullBranch(id);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const aheadBehind = await workspaceService.getAheadBehind(id);
|
|
498
|
+
res.json({ success: true, ...aheadBehind });
|
|
499
|
+
} catch (err: any) {
|
|
500
|
+
log('GIT', `Pull failed: ${err.message}`);
|
|
501
|
+
res.status(500).json({ error: `Failed to pull: ${err.message}` });
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// POST /api/projects/:id/git/push — push current branch to remote
|
|
506
|
+
router.post('/push', async (req, res) => {
|
|
507
|
+
const { id } = req.params;
|
|
508
|
+
log('GIT', `POST /api/projects/${id}/git/push`);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
512
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const project = await projectService.getById(id);
|
|
517
|
+
if (!project) {
|
|
518
|
+
res.status(404).json({ error: 'Project not found' });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (project.gitAuthMethod === 'ssh_key' && project.sshPrivateKeyEncrypted) {
|
|
523
|
+
await workspaceService.pushBranchSsh(id, project.sshPrivateKeyEncrypted);
|
|
524
|
+
} else if (project.gitAuthMethod === 'github_app' && project.githubInstallationId && project.githubRepoFullName) {
|
|
525
|
+
const token = await githubAppService.getInstallationToken(project.githubInstallationId);
|
|
526
|
+
await workspaceService.pushBranch(id, token, project.githubRepoFullName);
|
|
527
|
+
} else {
|
|
528
|
+
await workspaceService.pushBranch(id);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const aheadBehind = await workspaceService.getAheadBehind(id);
|
|
532
|
+
res.json({ success: true, ...aheadBehind });
|
|
533
|
+
} catch (err: any) {
|
|
534
|
+
log('GIT', `Push failed: ${err.message}`);
|
|
535
|
+
res.status(500).json({ error: `Failed to push: ${err.message}` });
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// GET /api/projects/:id/git/ahead-behind — get ahead/behind counts
|
|
540
|
+
router.get('/ahead-behind', async (req, res) => {
|
|
541
|
+
const { id } = req.params;
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
545
|
+
res.json({ ahead: 0, behind: 0 });
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const result = await workspaceService.getAheadBehind(id);
|
|
549
|
+
res.json(result);
|
|
550
|
+
} catch (err: any) {
|
|
551
|
+
log('GIT', `Ahead/behind failed: ${err.message}`);
|
|
552
|
+
res.json({ ahead: 0, behind: 0 });
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// GET /api/projects/:id/git/initial-commit — check if workspace has only the initial empty commit
|
|
557
|
+
router.get('/initial-commit', async (req, res) => {
|
|
558
|
+
const { id } = req.params;
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
562
|
+
res.json({ onlyInitialCommit: true });
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const onlyInitialCommit = await workspaceService.hasOnlyInitialCommit(id);
|
|
566
|
+
res.json({ onlyInitialCommit });
|
|
567
|
+
} catch (err: any) {
|
|
568
|
+
log('GIT', `Initial commit check failed: ${err.message}`);
|
|
569
|
+
res.json({ onlyInitialCommit: true });
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// GET /api/projects/:id/git/branches — list local + remote branches with current
|
|
574
|
+
router.get('/branches', async (req, res) => {
|
|
575
|
+
const { id } = req.params;
|
|
576
|
+
log('GIT', `GET /api/projects/${id}/git/branches`);
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
580
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const branches = await workspaceService.listBranches(id);
|
|
585
|
+
res.json(branches);
|
|
586
|
+
} catch (err: any) {
|
|
587
|
+
log('GIT', `List branches failed: ${err.message}`);
|
|
588
|
+
res.status(500).json({ error: `Failed to list branches: ${err.message}` });
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// POST /api/projects/:id/git/checkout — switch branch
|
|
593
|
+
router.post('/checkout', async (req, res) => {
|
|
594
|
+
const { id } = req.params;
|
|
595
|
+
const { branch, commitMessage } = req.body;
|
|
596
|
+
log('GIT', `POST /api/projects/${id}/git/checkout branch="${branch}"`);
|
|
597
|
+
|
|
598
|
+
if (!branch) {
|
|
599
|
+
res.status(400).json({ error: 'branch is required' });
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
try {
|
|
604
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
605
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// If there are uncommitted changes and a commit message was provided, commit first
|
|
610
|
+
const isDirty = await workspaceService.hasUncommittedChanges(id);
|
|
611
|
+
if (isDirty) {
|
|
612
|
+
if (!commitMessage) {
|
|
613
|
+
res.status(409).json({ error: 'Uncommitted changes exist', dirty: true });
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
await workspaceService.commitAll(id, commitMessage);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
await workspaceService.checkoutBranch(id, branch);
|
|
620
|
+
const branches = await workspaceService.listBranches(id);
|
|
621
|
+
res.json({ success: true, ...branches });
|
|
622
|
+
} catch (err: any) {
|
|
623
|
+
log('GIT', `Checkout branch failed: ${err.message}`);
|
|
624
|
+
res.status(500).json({ error: `Failed to checkout branch: ${err.message}` });
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// POST /api/projects/:id/git/branches — create and checkout a new branch
|
|
629
|
+
router.post('/branches', async (req, res) => {
|
|
630
|
+
const { id } = req.params;
|
|
631
|
+
const { name } = req.body;
|
|
632
|
+
log('GIT', `POST /api/projects/${id}/git/branches name="${name}"`);
|
|
633
|
+
|
|
634
|
+
if (!name) {
|
|
635
|
+
res.status(400).json({ error: 'name is required' });
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Validate branch name (no spaces, no special chars that break git)
|
|
640
|
+
if (/[\s~^:?*\[\\]/.test(name) || name.startsWith('-') || name.endsWith('.') || name.includes('..')) {
|
|
641
|
+
res.status(400).json({ error: 'Invalid branch name' });
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
try {
|
|
646
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
647
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
await workspaceService.createBranch(id, name);
|
|
652
|
+
const branches = await workspaceService.listBranches(id);
|
|
653
|
+
res.json({ success: true, ...branches });
|
|
654
|
+
} catch (err: any) {
|
|
655
|
+
log('GIT', `Create branch failed: ${err.message}`);
|
|
656
|
+
res.status(500).json({ error: `Failed to create branch: ${err.message}` });
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// DELETE /api/projects/:id/git/branches — delete a local branch
|
|
661
|
+
router.delete('/branches', async (req, res) => {
|
|
662
|
+
const { id } = req.params;
|
|
663
|
+
const { name } = req.body;
|
|
664
|
+
log('GIT', `DELETE /api/projects/${id}/git/branches name="${name}"`);
|
|
665
|
+
|
|
666
|
+
if (!name) {
|
|
667
|
+
res.status(400).json({ error: 'name is required' });
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
try {
|
|
672
|
+
if (!workspaceService.hasWorkspace(id)) {
|
|
673
|
+
res.status(404).json({ error: 'No git workspace found for this project' });
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Prevent deleting the current branch
|
|
678
|
+
const branches = await workspaceService.listBranches(id);
|
|
679
|
+
if (branches.current === name) {
|
|
680
|
+
res.status(400).json({ error: 'Cannot delete the currently checked out branch' });
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
await workspaceService.deleteBranch(id, name);
|
|
685
|
+
const updatedBranches = await workspaceService.listBranches(id);
|
|
686
|
+
res.json({ success: true, ...updatedBranches });
|
|
687
|
+
} catch (err: any) {
|
|
688
|
+
log('GIT', `Delete branch failed: ${err.message}`);
|
|
689
|
+
res.status(500).json({ error: `Failed to delete branch: ${err.message}` });
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
325
693
|
return router;
|
|
326
694
|
};
|