| 368 | } |
| 369 | |
| 370 | export class ProjectService { |
| 371 | private readonly fileCompletionsCache = new Map<string, FileCompletionsCacheEntry>(); |
| 372 | private directoryPicker?: (initialPath?: string | null) => Promise<string | null>; |
| 373 | private readonly sshPromptService: SshPromptService | undefined; |
| 374 | private workspaceService?: WorkspaceRemover; |
| 375 | |
| 376 | constructor( |
| 377 | private readonly config: Config, |
| 378 | sshPromptService?: SshPromptService |
| 379 | ) { |
| 380 | this.sshPromptService = sshPromptService; |
| 381 | } |
| 382 | |
| 383 | setWorkspaceService(workspaceService: WorkspaceRemover): void { |
| 384 | this.workspaceService = workspaceService; |
| 385 | } |
| 386 | |
| 387 | setDirectoryPicker(picker: (initialPath?: string | null) => Promise<string | null>) { |
| 388 | this.directoryPicker = picker; |
| 389 | } |
| 390 | |
| 391 | async pickDirectory(initialPath?: string | null): Promise<string | null> { |
| 392 | if (!this.directoryPicker) return null; |
| 393 | return this.directoryPicker(initialPath ?? null); |
| 394 | } |
| 395 | |
| 396 | async create( |
| 397 | projectPath: string |
| 398 | ): Promise<Result<{ projectConfig: ProjectConfig; normalizedPath: string }>> { |
| 399 | try { |
| 400 | // Validate input |
| 401 | if (!projectPath || projectPath.trim().length === 0) { |
| 402 | return Err("Project path cannot be empty"); |
| 403 | } |
| 404 | |
| 405 | // Resolve the path: |
| 406 | // - Bare names like "my-project" → ~/.mux/projects/my-project |
| 407 | // - Paths with ~ → expand to home directory |
| 408 | // - Absolute/relative paths → resolve normally |
| 409 | const isBareProjectName = |
| 410 | projectPath.length > 0 && |
| 411 | !projectPath.includes("/") && |
| 412 | !projectPath.includes("\\") && |
| 413 | !projectPath.startsWith("~"); |
| 414 | |
| 415 | const config = this.config.loadConfigOrDefault(); |
| 416 | let normalizedPath: string; |
| 417 | if (isBareProjectName) { |
| 418 | // Bare project name - put in default projects directory |
| 419 | const parentDir = resolveProjectParentDir(undefined, config.defaultProjectDir); |
| 420 | normalizedPath = path.join(parentDir, projectPath); |
| 421 | } else if ( |
| 422 | projectPath === "~" || |
| 423 | projectPath.startsWith("~/") || |
| 424 | projectPath.startsWith("~\\") |
| 425 | ) { |
| 426 | // Tilde expansion - uses expandTilde to respect MUX_ROOT for ~/.mux paths |
| 427 | normalizedPath = path.resolve(expandTilde(projectPath)); |
nothing calls this directly
no outgoing calls
no test coverage detected