projectsPath = $this->resolveProjectsPath(); } private function resolveProjectsPath(): string { $path = $this->configService->get('workspace.projects_path'); if (empty($path)) { throw new RuntimeException('configs 表未设置 workspace.projects_path。'); } return rtrim($path, '/'); } /** * 获取项目根目录路径 */ public function getProjectsPath(): string { return $this->projectsPath; } /** * 获取所有项目 */ public function getAllProjects(): Collection { return Project::query()->orderBy('name')->get(); } /** * 根据 slug 获取项目 */ public function getBySlug(string $slug): ?Project { return Project::findBySlug($slug); } /** * 创建项目 */ public function create(array $data): Project { return Project::query()->create($data); } /** * 更新项目 */ public function update(Project $project, array $data): Project { $project->update($data); return $project->refresh(); } /** * 删除项目 */ public function delete(Project $project): bool { return $project->delete(); } /** * 获取项目状态 */ public function getProjectStatus(Project $project): array { $fullPath = $project->getFullPath($this->projectsPath); $pathValid = $project->isPathValid($this->projectsPath); $isGitRepo = $pathValid && $project->isGitRepository($this->projectsPath); $status = [ 'path_valid' => $pathValid, 'full_path' => $fullPath, 'is_git_repo' => $isGitRepo, 'current_branch' => null, 'has_uncommitted_changes' => null, ]; if ($isGitRepo) { try { $status['current_branch'] = $this->getCurrentBranch($fullPath); $status['has_uncommitted_changes'] = $this->hasUncommittedChanges($fullPath); } catch (\Exception $e) { // 忽略 Git 命令错误 } } return $status; } /** * 发现新项目(扫描 projects_path 目录) */ public function discoverProjects(): array { if (!File::exists($this->projectsPath)) { return []; } $existingSlugs = Project::query()->pluck('slug')->toArray(); $discovered = []; $directories = File::directories($this->projectsPath); foreach ($directories as $directory) { $dirName = basename($directory); // 跳过已存在的项目 if (in_array($dirName, $existingSlugs, true)) { continue; } // 跳过隐藏目录 if (str_starts_with($dirName, '.')) { continue; } $isGitRepo = is_dir($directory . DIRECTORY_SEPARATOR . '.git'); $discovered[] = [ 'slug' => $dirName, 'name' => ucfirst(str_replace(['-', '_'], ' ', $dirName)), 'directory' => $dirName, 'path' => $directory, 'is_git_repo' => $isGitRepo, ]; } return $discovered; } /** * 批量添加发现的项目 */ public function addDiscoveredProjects(array $slugs): array { $discovered = $this->discoverProjects(); $added = []; foreach ($discovered as $project) { if (in_array($project['slug'], $slugs, true)) { $created = Project::query()->create([ 'slug' => $project['slug'], 'name' => $project['name'], 'directory' => $project['directory'], ]); $added[] = $created; } } return $added; } /** * 同步项目的 Git 信息 */ public function syncGitInfo(Project $project): Project { $fullPath = $project->getFullPath($this->projectsPath); if (!$project->isGitRepository($this->projectsPath)) { return $project; } try { // 获取当前版本(从 version.txt) $version = $this->getMasterVersion($fullPath); if ($version !== null) { $project->git_current_version = $version; } // 获取当前分支 $branch = $this->getCurrentBranch($fullPath); if ($branch !== null) { $project->git_release_branch = $branch; } $project->git_version_cached_at = now(); $project->save(); } catch (\Exception $e) { // 忽略错误 } return $project->refresh(); } /** * 获取 master 分支的版本号 */ private function getMasterVersion(string $path): ?string { try { $this->runGit($path, ['git', 'fetch', 'origin', 'master']); $version = $this->runGit($path, ['git', 'show', 'origin/master:version.txt']); return trim($version) ?: null; } catch (ProcessFailedException $e) { return null; } } /** * 获取当前分支 */ private function getCurrentBranch(string $path): ?string { try { $branch = $this->runGit($path, ['git', 'rev-parse', '--abbrev-ref', 'HEAD']); return trim($branch) ?: null; } catch (ProcessFailedException $e) { return null; } } /** * 检查是否有未提交的更改 */ private function hasUncommittedChanges(string $path): bool { try { $output = $this->runGit($path, ['git', 'status', '--porcelain']); return !empty(trim($output)); } catch (ProcessFailedException $e) { return false; } } /** * 运行 Git 命令 */ private function runGit(string $cwd, array $command): string { $process = new Process($command, $cwd); $process->setTimeout(60); $process->mustRun(); return $process->getOutput(); } /** * 获取所有启用 Git 监控的项目 */ public function getGitMonitorEnabledProjects(): Collection { return Project::getGitMonitorEnabled(); } /** * 根据 app 名称查找项目 */ public function findByAppName(string $appName): ?Project { return Project::findByAppName($appName); } /** * 一键同步所有项目的 Git 信息 */ public function syncAllGitInfo(): array { $projects = $this->getAllProjects(); $synced = []; $failed = []; foreach ($projects as $project) { try { if ($project->isGitRepository($this->projectsPath)) { $this->syncGitInfo($project); $synced[] = $project->slug; } } catch (\Exception $e) { $failed[] = [ 'slug' => $project->slug, 'error' => $e->getMessage(), ]; } } return [ 'synced' => $synced, 'failed' => $failed, ]; } }