• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apowers313 / aiforge / 21570337701

01 Feb 2026 09:11PM UTC coverage: 81.026% (-2.9%) from 83.954%
21570337701

push

github

apowers313
test: increase coverage to 80%+

2049 of 2382 branches covered (86.02%)

Branch coverage included in aggregate %.

1849 of 2529 new or added lines in 25 files covered. (73.11%)

681 existing lines in 21 files now uncovered.

9861 of 12317 relevant lines covered (80.06%)

26.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.64
/src/server/services/project/ProjectMetadataService.ts
1
/**
2
 * ProjectMetadataService - Detects project metadata like git remotes, package.json, etc.
3
 * Phase 7: Refactored to use GitService for centralized git operations
4
 */
5
import { readFile, access } from 'node:fs/promises';
1✔
6
import { join } from 'node:path';
1✔
7
import type { ProjectMetadata } from '@shared/types/index.js';
8
import { GitService } from '../git/GitService.js';
1✔
9

10
export class ProjectMetadataService {
1✔
11
  /**
12
   * Get metadata for a project directory
13
   */
14
  async getMetadata(projectPath: string): Promise<ProjectMetadata> {
1✔
15
    const [gitInfo, packageJson, hasWorkflows] = await Promise.all([
15✔
16
      this.getGitInfo(projectPath),
15✔
17
      this.readPackageJson(projectPath),
15✔
18
      this.hasGithubWorkflows(projectPath),
15✔
19
    ]);
15✔
20

21
    return {
15✔
22
      gitRemoteUrl: gitInfo.remoteUrl,
15✔
23
      gitRemoteType: gitInfo.remoteType,
15✔
24
      hasPackageJson: packageJson.exists,
15✔
25
      packageName: packageJson.name,
15✔
26
      hasGithubWorkflows: hasWorkflows,
15✔
27
    };
15✔
28
  }
15✔
29

30
  /**
31
   * Get git remote information
32
   * Phase 7: Refactored to use GitService for centralized git operations
33
   */
34
  private async getGitInfo(projectPath: string): Promise<{
1✔
35
    remoteUrl: string | null;
36
    remoteType: ProjectMetadata['gitRemoteType'];
37
  }> {
15✔
38
    try {
15✔
39
      const gitService = new GitService(projectPath);
15✔
40
      const remotes = await gitService.getRemotes();
15✔
41
      const origin = remotes.find((r) => r.name === 'origin');
15✔
42

43
      if (!origin?.refs.fetch) {
15✔
44
        return { remoteUrl: null, remoteType: null };
10✔
45
      }
10✔
46

47
      const remoteUrl = origin.refs.fetch;
5✔
48
      const remoteType = this.detectRemoteType(remoteUrl);
5✔
49

50
      return { remoteUrl, remoteType };
5✔
51
    } catch {
12!
UNCOV
52
      return { remoteUrl: null, remoteType: null };
×
UNCOV
53
    }
×
54
  }
15✔
55

56
  /**
57
   * Detect the type of git remote from URL
58
   */
59
  private detectRemoteType(url: string): ProjectMetadata['gitRemoteType'] {
1✔
60
    if (url.includes('github.com')) {
5✔
61
      return 'github';
2✔
62
    }
2✔
63
    if (url.includes('gitlab.com') || url.includes('gitlab')) {
5✔
64
      return 'gitlab';
1✔
65
    }
1✔
66
    if (url.includes('bitbucket.org') || url.includes('bitbucket')) {
5✔
67
      return 'bitbucket';
1✔
68
    }
1✔
69
    return 'other';
1✔
70
  }
5✔
71

72
  /**
73
   * Read package.json if it exists
74
   */
75
  private async readPackageJson(projectPath: string): Promise<{
1✔
76
    exists: boolean;
77
    name: string | null;
78
  }> {
15✔
79
    try {
15✔
80
      const packageJsonPath = join(projectPath, 'package.json');
15✔
81
      const content = await readFile(packageJsonPath, 'utf-8');
15✔
82
      const parsed = JSON.parse(content) as { name?: string };
3✔
83
      return {
3✔
84
        exists: true,
3✔
85
        name: parsed.name ?? null,
15!
86
      };
15✔
87
    } catch {
15✔
88
      return { exists: false, name: null };
13✔
89
    }
13✔
90
  }
15✔
91

92
  /**
93
   * Check if .github/workflows directory exists and has files
94
   */
95
  private async hasGithubWorkflows(projectPath: string): Promise<boolean> {
1✔
96
    try {
15✔
97
      const workflowsPath = join(projectPath, '.github', 'workflows');
15✔
98
      await access(workflowsPath);
15✔
99
      return true;
2✔
100
    } catch {
15✔
101
      return false;
13✔
102
    }
13✔
103
  }
15✔
104

105
  /**
106
   * Parse a GitHub URL to a web-accessible URL
107
   * Converts SSH and HTTPS git URLs to web URLs
108
   */
109
  parseGitHubUrl(url: string): string | null {
1✔
110
    if (!url.includes('github.com')) {
4✔
111
      return null;
1✔
112
    }
1✔
113

114
    // Handle SSH format: git@github.com:user/repo.git
115
    if (url.startsWith('git@github.com:')) {
4✔
116
      const path = url.replace('git@github.com:', '').replace(/\.git$/, '');
1✔
117
      return `https://github.com/${path}`;
1✔
118
    }
1✔
119

120
    // Handle HTTPS format: https://github.com/user/repo.git
121
    if (url.startsWith('https://github.com/')) {
2✔
122
      return url.replace(/\.git$/, '');
2✔
123
    }
2!
124

UNCOV
125
    return null;
×
126
  }
4✔
127
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc