mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-04 20:05:38 +08:00
refactor: modularize project services, and wizard create/clone flow
Restructure project creation, listing, GitHub clone progress, and TaskMaster details behind a dedicated TypeScript module under server/modules/projects/, and align the client wizard with a single path-based flow. Server / routing - Remove server/routes/projects.js and mount server/modules/projects/ projects.routes.ts at /api/projects (still behind authenticateToken). - Drop duplicate handlers from server/index.js for GET /api/projects and GET /api/projects/:projectId/taskmaster; those live on the new router. - Import WORKSPACES_ROOT and validateWorkspacePath from shared utils in index.js instead of the deleted projects route module. Projects router (projects.routes.ts) - GET /: list projects with sessions (existing snapshot behavior). - POST /create-project: validate body, reject legacy workspaceType and mixed clone fields, delegate to createProject service, return distinct success copy when an archived path is reactivated. - GET /clone-progress: Server-Sent Events for clone progress/complete/error; requires authenticated user id for token resolution; wires startCloneProject. - GET /:projectId/taskmaster: delegates to getProjectTaskMaster. Services (new) - project-management.service.ts: path validation, workspace directory creation, persistence via projectsDb.createProjectPath, mapping to API project shape; surfaces AppError for validation, conflict, and not-found cases; optional dependency injection for tests. - project-clone.service.ts: validates workspace, resolves GitHub auth (stored token or inline token), runs git clone with progress callbacks, registers project via createProject on success; sanitizes errors and supports cancellation; injectable dependencies for tests. - projects-has-taskmaster.service.ts: moves TaskMaster detection and normalization out of server/projects.js; resolve-by-id and public getProjectTaskMaster with structured AppError responses. Persistence and shared types - projectsDb.createProjectPath now returns CreateProjectPathResult (created | reactivated_archived | active_conflict) using INSERT … ON CONFLICT with selective update when the row is archived; normalizes display name from path or custom name; repository row typing moves to shared ProjectRepositoryRow. - getProjectPaths() returns only non-archived rows (isArchived = 0). - shared/types.ts: ProjectRepositoryRow, CreateProjectPathResult/outcome, WorkspacePathValidationResult. - shared/utils.ts: WORKSPACES_ROOT, forbidden path lists, validateWorkspacePath, asyncHandler for Express async routes. Legacy cleanup - server/projects.js: remove detectTaskMasterFolder, normalizeTaskMasterInfo, and getProjectTaskMasterById (logic lives in the new service). - server/routes/agent.js: register external API project paths with projectsDb.createProjectPath instead of addProjectManually try/catch; treat active_conflict as an existing registration and continue. Tests - Add Node test suites for project-management, project-clone, and projects-has-taskmaster services; update projects.service test import for renamed projects-with-sessions-fetch.service.ts. Rename - projects.service.ts → projects-with-sessions-fetch.service.ts; re-export from modules/projects/index.ts. Client (project creation wizard) - Remove StepTypeSelection and workspaceType from form state and types; wizard is two steps (configure path/GitHub auth, then review). - createWorkspaceRequest → createProjectRequest; clone vs create-only inferred from githubUrl (pathUtils / isCloneWorkflow). - Adjust step indices, WizardProgress, StepConfiguration/Review, WorkspacePathField, and src/utils/api.js as needed for the new API. Docs - Minor websocket README touch-up. Net: ~1.6k insertions / ~0.9k deletions across 29 files; behavior is centralized in typed services with explicit HTTP errors and test seams.
This commit is contained in:
@@ -3,8 +3,8 @@ import type {
|
||||
BrowseFilesystemResponse,
|
||||
CloneProgressEvent,
|
||||
CreateFolderResponse,
|
||||
CreateWorkspacePayload,
|
||||
CreateWorkspaceResponse,
|
||||
CreateProjectPayload,
|
||||
CreateProjectResponse,
|
||||
CredentialsResponse,
|
||||
FolderSuggestion,
|
||||
TokenMode,
|
||||
@@ -27,6 +27,42 @@ const parseJson = async <T>(response: Response): Promise<T> => {
|
||||
return data;
|
||||
};
|
||||
|
||||
const resolveCreateProjectErrorMessage = (responseData: CreateProjectResponse): string | null => {
|
||||
if (typeof responseData.details === 'string' && responseData.details.trim().length > 0) {
|
||||
return responseData.details;
|
||||
}
|
||||
|
||||
if (typeof responseData.error === 'string' && responseData.error.trim().length > 0) {
|
||||
return responseData.error;
|
||||
}
|
||||
|
||||
if (responseData.error && typeof responseData.error === 'object') {
|
||||
const errorObject = responseData.error as { message?: unknown; details?: unknown };
|
||||
|
||||
if (typeof errorObject.details === 'string' && errorObject.details.trim().length > 0) {
|
||||
return errorObject.details;
|
||||
}
|
||||
|
||||
if (typeof errorObject.message === 'string' && errorObject.message.trim().length > 0) {
|
||||
return errorObject.message;
|
||||
}
|
||||
|
||||
if (
|
||||
errorObject.details
|
||||
&& typeof errorObject.details === 'object'
|
||||
&& typeof (errorObject.details as { projectPath?: unknown }).projectPath === 'string'
|
||||
) {
|
||||
return `Project path already exists: ${(errorObject.details as { projectPath: string }).projectPath}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof responseData.message === 'string' && responseData.message.trim().length > 0) {
|
||||
return responseData.message;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const fetchGithubTokenCredentials = async () => {
|
||||
const response = await api.get('/settings/credentials?type=github_token');
|
||||
const data = await parseJson<CredentialsResponse>(response);
|
||||
@@ -64,12 +100,12 @@ export const createFolderInFilesystem = async (folderPath: string) => {
|
||||
return data.path || folderPath;
|
||||
};
|
||||
|
||||
export const createWorkspaceRequest = async (payload: CreateWorkspacePayload) => {
|
||||
const response = await api.createWorkspace(payload);
|
||||
const data = await parseJson<CreateWorkspaceResponse>(response);
|
||||
export const createProjectRequest = async (payload: CreateProjectPayload) => {
|
||||
const response = await api.createProject(payload);
|
||||
const data = await parseJson<CreateProjectResponse>(response);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.details || data.error || 'Failed to create workspace');
|
||||
throw new Error(resolveCreateProjectErrorMessage(data) || 'Failed to create project');
|
||||
}
|
||||
|
||||
return data.project;
|
||||
|
||||
Reference in New Issue
Block a user