git-repo-analyzer-test 1.0.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/.github/copilot-instructions.md +108 -0
- package/.idea/aianalyzer.iml +9 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/API_REFERENCE.md +244 -0
- package/ENHANCEMENTS.md +282 -0
- package/README.md +179 -0
- package/USAGE.md +189 -0
- package/analysis.txt +0 -0
- package/bin/cli.js +135 -0
- package/docs/SONARCLOUD_ANALYSIS_COVERED.md +144 -0
- package/docs/SonarCloud_Presentation_Points.md +81 -0
- package/docs/UI_IMPROVEMENTS.md +117 -0
- package/package-lock_cmd.json +542 -0
- package/package.json +44 -0
- package/package_command.json +16 -0
- package/public/analysis-options.json +31 -0
- package/public/images/README.txt +2 -0
- package/public/images/rws-logo.png +0 -0
- package/public/index.html +2433 -0
- package/repositories.example.txt +17 -0
- package/sample-repos.txt +20 -0
- package/src/analyzers/accessibility.js +47 -0
- package/src/analyzers/cicd-enhanced.js +113 -0
- package/src/analyzers/codeReview-enhanced.js +599 -0
- package/src/analyzers/codeReview-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/codeReview.js +171 -0
- package/src/analyzers/codeReview.js:Zone.Identifier +3 -0
- package/src/analyzers/documentation-enhanced.js +137 -0
- package/src/analyzers/performance-enhanced.js +747 -0
- package/src/analyzers/performance-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/performance.js +211 -0
- package/src/analyzers/performance.js:Zone.Identifier +3 -0
- package/src/analyzers/performance_cmd.js +216 -0
- package/src/analyzers/quality-enhanced.js +386 -0
- package/src/analyzers/quality-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/quality.js +92 -0
- package/src/analyzers/quality.js:Zone.Identifier +3 -0
- package/src/analyzers/security-enhanced.js +512 -0
- package/src/analyzers/security-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/snyk-ai.js:Zone.Identifier +3 -0
- package/src/analyzers/sonarcloud.js +928 -0
- package/src/analyzers/vulnerability.js +185 -0
- package/src/analyzers/vulnerability.js:Zone.Identifier +3 -0
- package/src/cli.js:Zone.Identifier +3 -0
- package/src/config.js +43 -0
- package/src/core/analyzerEngine.js +68 -0
- package/src/core/reportGenerator.js +21 -0
- package/src/gemini.js +321 -0
- package/src/github/client.js +124 -0
- package/src/github/client.js:Zone.Identifier +3 -0
- package/src/index.js +93 -0
- package/src/index_cmd.js +130 -0
- package/src/openai.js +297 -0
- package/src/report/generator.js +459 -0
- package/src/report/generator_cmd.js +459 -0
- package/src/report/pdf-generator.js +387 -0
- package/src/report/pdf-generator.js:Zone.Identifier +3 -0
- package/src/server.js +431 -0
- package/src/server.js:Zone.Identifier +3 -0
- package/src/server_cmd.js +434 -0
- package/src/sonarcloud/client.js +365 -0
- package/src/sonarcloud/scanner.js +171 -0
- package/src.zip +0 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Code Quality Analyzer
|
|
3
|
+
* Provides detailed insights on code quality metrics against standards
|
|
4
|
+
*/
|
|
5
|
+
export class CodeQualityAnalyzer {
|
|
6
|
+
constructor(githubClient) {
|
|
7
|
+
this.client = githubClient;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Comprehensive code quality analysis
|
|
12
|
+
*/
|
|
13
|
+
async analyzeCodeQuality(owner, repo) {
|
|
14
|
+
try {
|
|
15
|
+
const [repoData, languages, commits] = await Promise.all([
|
|
16
|
+
this.client.getRepository(owner, repo),
|
|
17
|
+
this.client.getLanguages(owner, repo),
|
|
18
|
+
this.client.getCommits(owner, repo, { perPage: 100 }),
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const issues = this.identifyCodeQualityIssues(repoData, languages);
|
|
22
|
+
const score = this.calculateQualityScore(repoData, issues);
|
|
23
|
+
const rating = this.getRating(score);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
score,
|
|
27
|
+
rating,
|
|
28
|
+
metrics: {
|
|
29
|
+
stars: repoData.stargazers_count || 0,
|
|
30
|
+
forks: repoData.forks_count || 0,
|
|
31
|
+
watchers: repoData.watchers_count || 0,
|
|
32
|
+
openIssues: repoData.open_issues_count || 0,
|
|
33
|
+
hasWiki: repoData.has_wiki,
|
|
34
|
+
hasPages: repoData.has_pages,
|
|
35
|
+
languages: Object.keys(languages).length,
|
|
36
|
+
primaryLanguage: repoData.language,
|
|
37
|
+
lastUpdate: repoData.updated_at,
|
|
38
|
+
daysInactive: this.getDaysSinceUpdate(repoData.updated_at),
|
|
39
|
+
description: repoData.description,
|
|
40
|
+
topics: repoData.topics || [],
|
|
41
|
+
license: repoData.license?.name || 'None',
|
|
42
|
+
isArchived: repoData.archived,
|
|
43
|
+
size: repoData.size,
|
|
44
|
+
},
|
|
45
|
+
issues,
|
|
46
|
+
recommendations: this.generateQualityRecommendations(issues),
|
|
47
|
+
details: {
|
|
48
|
+
codeStandards: this.analyzeCodeStandards(repoData, languages),
|
|
49
|
+
libraryAudit: this.auditLibraries(repoData),
|
|
50
|
+
documentationQuality: this.assessDocumentation(repoData),
|
|
51
|
+
maintenanceStatus: this.assessMaintenanceStatus(repoData, commits),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new Error(`Code quality analysis failed: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Identify specific code quality issues
|
|
61
|
+
*/
|
|
62
|
+
identifyCodeQualityIssues(repoData, languages) {
|
|
63
|
+
const issues = [];
|
|
64
|
+
|
|
65
|
+
// Issue 1: Outdated or archived repository
|
|
66
|
+
if (repoData.archived) {
|
|
67
|
+
issues.push({
|
|
68
|
+
type: 'ARCHIVED_REPOSITORY',
|
|
69
|
+
severity: 'HIGH',
|
|
70
|
+
message: 'Repository is archived and no longer maintained',
|
|
71
|
+
location: 'Repository Status',
|
|
72
|
+
impact: 'No updates or security patches expected',
|
|
73
|
+
recommendation: 'Consider using an actively maintained fork or alternative',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Issue 2: No license
|
|
78
|
+
if (!repoData.license) {
|
|
79
|
+
issues.push({
|
|
80
|
+
type: 'NO_LICENSE',
|
|
81
|
+
severity: 'MEDIUM',
|
|
82
|
+
message: 'Repository has no license defined',
|
|
83
|
+
location: 'LICENSE file',
|
|
84
|
+
impact: 'Legal and usage clarity issues for contributors',
|
|
85
|
+
recommendation: 'Add a LICENSE file (MIT, Apache 2.0, GPL, etc.)',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Issue 3: Stale repository (>1 year inactive)
|
|
90
|
+
const daysSinceUpdate = this.getDaysSinceUpdate(repoData.updated_at);
|
|
91
|
+
if (daysSinceUpdate > 365) {
|
|
92
|
+
issues.push({
|
|
93
|
+
type: 'STALE_CODEBASE',
|
|
94
|
+
severity: 'MEDIUM',
|
|
95
|
+
message: `Repository not updated for ${daysSinceUpdate} days`,
|
|
96
|
+
location: 'Last commit/update',
|
|
97
|
+
impact: 'Potential security vulnerabilities, outdated dependencies',
|
|
98
|
+
recommendation: 'Regular maintenance and dependency updates needed',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Issue 4: High number of open issues
|
|
103
|
+
if (repoData.open_issues_count > 100) {
|
|
104
|
+
issues.push({
|
|
105
|
+
type: 'UNRESOLVED_ISSUES',
|
|
106
|
+
severity: 'MEDIUM',
|
|
107
|
+
message: `${repoData.open_issues_count} open issues`,
|
|
108
|
+
location: 'Issues tracker',
|
|
109
|
+
impact: 'Code quality concerns, bug backlog',
|
|
110
|
+
recommendation: 'Implement issue triage and prioritization strategy',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Issue 5: Lack of documentation
|
|
115
|
+
if (!repoData.has_wiki && !repoData.has_pages && !repoData.description) {
|
|
116
|
+
issues.push({
|
|
117
|
+
type: 'POOR_DOCUMENTATION',
|
|
118
|
+
severity: 'MEDIUM',
|
|
119
|
+
message: 'Insufficient documentation for code clarity',
|
|
120
|
+
location: 'README, Wiki, GitHub Pages',
|
|
121
|
+
impact: 'Hard to understand code purpose and usage',
|
|
122
|
+
recommendation: 'Add comprehensive README.md, API documentation, examples',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Issue 6: Too many languages (indicates scattered codebase)
|
|
127
|
+
const languageCount = Object.keys(languages).length;
|
|
128
|
+
if (languageCount > 8) {
|
|
129
|
+
issues.push({
|
|
130
|
+
type: 'EXCESSIVE_LANGUAGES',
|
|
131
|
+
severity: 'LOW',
|
|
132
|
+
message: `Using ${languageCount} different programming languages`,
|
|
133
|
+
location: 'Various file types across repo',
|
|
134
|
+
impact: 'Maintenance complexity, inconsistent code standards',
|
|
135
|
+
recommendation: 'Consider consolidating to core languages only',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Issue 7: No primary language detected
|
|
140
|
+
if (!repoData.language) {
|
|
141
|
+
issues.push({
|
|
142
|
+
type: 'NO_PRIMARY_LANGUAGE',
|
|
143
|
+
severity: 'LOW',
|
|
144
|
+
message: 'No primary programming language identified',
|
|
145
|
+
location: 'Repository structure',
|
|
146
|
+
impact: 'Unclear project type and technical stack',
|
|
147
|
+
recommendation: 'Ensure majority of code is in a single language',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Issue 8: Large repository (code smell)
|
|
152
|
+
if (repoData.size > 500000) {
|
|
153
|
+
issues.push({
|
|
154
|
+
type: 'LARGE_REPOSITORY',
|
|
155
|
+
severity: 'LOW',
|
|
156
|
+
message: `Repository size is ${Math.round(repoData.size / 1024)} MB`,
|
|
157
|
+
location: 'Overall repository',
|
|
158
|
+
impact: 'Slow clone times, harder to navigate',
|
|
159
|
+
recommendation: 'Consider splitting into multiple repositories or removing artifacts',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return issues;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Analyze code standards compliance
|
|
168
|
+
*/
|
|
169
|
+
analyzeCodeStandards(repoData, languages) {
|
|
170
|
+
const languageList = Object.keys(languages);
|
|
171
|
+
const standards = {
|
|
172
|
+
hasLinter: this.hasLinterConfiguration(repoData),
|
|
173
|
+
hasFormatter: this.hasFormatterConfiguration(repoData),
|
|
174
|
+
hasTests: this.hasTestSetup(repoData),
|
|
175
|
+
hasBuildProcess: this.hasBuildConfiguration(repoData),
|
|
176
|
+
hasCICD: this.hasCICDConfiguration(repoData),
|
|
177
|
+
primaryLanguages: languageList.slice(0, 3),
|
|
178
|
+
languageDetails: this.getLanguageDetails(languageList),
|
|
179
|
+
};
|
|
180
|
+
return standards;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Audit dependencies and libraries
|
|
185
|
+
*/
|
|
186
|
+
auditLibraries(repoData) {
|
|
187
|
+
const hasPackageJson = repoData.topics?.includes('npm') ||
|
|
188
|
+
repoData.language === 'JavaScript' ||
|
|
189
|
+
repoData.language === 'TypeScript';
|
|
190
|
+
const hasPyproject = repoData.topics?.includes('python') ||
|
|
191
|
+
repoData.language === 'Python';
|
|
192
|
+
const hasGemfile = repoData.language === 'Ruby';
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
dependencyManagement: {
|
|
196
|
+
npm: hasPackageJson,
|
|
197
|
+
python: hasPyproject,
|
|
198
|
+
ruby: hasGemfile,
|
|
199
|
+
other: !hasPackageJson && !hasPyproject && !hasGemfile,
|
|
200
|
+
},
|
|
201
|
+
recommendations: [
|
|
202
|
+
'Regular dependency updates recommended',
|
|
203
|
+
'Use dependency scanning tools (npm audit, pip-audit)',
|
|
204
|
+
'Keep frameworks and libraries up to date',
|
|
205
|
+
'Monitor security advisories for used libraries',
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Assess documentation quality
|
|
212
|
+
*/
|
|
213
|
+
assessDocumentation(repoData) {
|
|
214
|
+
const score = {
|
|
215
|
+
readme: repoData.description ? 2 : 0,
|
|
216
|
+
wiki: repoData.has_wiki ? 2 : 0,
|
|
217
|
+
pages: repoData.has_pages ? 2 : 0,
|
|
218
|
+
license: repoData.license ? 2 : 0,
|
|
219
|
+
changelog: 0, // Would need file scanning
|
|
220
|
+
contributing: 0, // Would need file scanning
|
|
221
|
+
};
|
|
222
|
+
const total = Object.values(score).reduce((a, b) => a + b, 0);
|
|
223
|
+
return {
|
|
224
|
+
score: total,
|
|
225
|
+
maxScore: 12,
|
|
226
|
+
percentage: Math.round((total / 12) * 100),
|
|
227
|
+
details: score,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Assess repository maintenance status
|
|
233
|
+
*/
|
|
234
|
+
assessMaintenanceStatus(repoData, commits) {
|
|
235
|
+
const daysSinceUpdate = this.getDaysSinceUpdate(repoData.updated_at);
|
|
236
|
+
let status = 'ACTIVE';
|
|
237
|
+
let points = 10;
|
|
238
|
+
|
|
239
|
+
if (daysSinceUpdate > 365) {
|
|
240
|
+
status = 'INACTIVE';
|
|
241
|
+
points = 2;
|
|
242
|
+
} else if (daysSinceUpdate > 180) {
|
|
243
|
+
status = 'DORMANT';
|
|
244
|
+
points = 4;
|
|
245
|
+
} else if (daysSinceUpdate > 30) {
|
|
246
|
+
status = 'MODERATE';
|
|
247
|
+
points = 7;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
status,
|
|
252
|
+
points,
|
|
253
|
+
daysSinceUpdate,
|
|
254
|
+
commitFrequency: commits.length > 0 ? 'Active' : 'None',
|
|
255
|
+
recommendations:
|
|
256
|
+
status === 'ACTIVE'
|
|
257
|
+
? ['Maintain current update frequency']
|
|
258
|
+
: ['Resume active maintenance', 'Update dependencies', 'Address open issues'],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Helper: Get days since last update
|
|
264
|
+
*/
|
|
265
|
+
getDaysSinceUpdate(lastUpdate) {
|
|
266
|
+
const lastUpdateDate = new Date(lastUpdate);
|
|
267
|
+
const daysSince = Math.floor((new Date() - lastUpdateDate) / (1000 * 60 * 60 * 24));
|
|
268
|
+
return daysSince;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Helper: Check for linter configuration
|
|
273
|
+
*/
|
|
274
|
+
hasLinterConfiguration(repoData) {
|
|
275
|
+
return repoData.topics?.some((t) =>
|
|
276
|
+
['eslint', 'pylint', 'rubocop', 'prettier'].includes(t)
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Helper: Check for formatter configuration
|
|
282
|
+
*/
|
|
283
|
+
hasFormatterConfiguration(repoData) {
|
|
284
|
+
return repoData.topics?.some((t) =>
|
|
285
|
+
['prettier', 'black', 'rustfmt'].includes(t)
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Helper: Check for test setup
|
|
291
|
+
*/
|
|
292
|
+
hasTestSetup(repoData) {
|
|
293
|
+
return repoData.topics?.some((t) =>
|
|
294
|
+
['testing', 'jest', 'pytest', 'rspec', 'vitest'].includes(t)
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Helper: Check for build configuration
|
|
300
|
+
*/
|
|
301
|
+
hasBuildConfiguration(repoData) {
|
|
302
|
+
return repoData.topics?.some((t) =>
|
|
303
|
+
['webpack', 'gradle', 'maven', 'cargo', 'rust'].includes(t)
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Helper: Check for CI/CD configuration
|
|
309
|
+
*/
|
|
310
|
+
hasCICDConfiguration(repoData) {
|
|
311
|
+
return repoData.topics?.some((t) =>
|
|
312
|
+
['github-actions', 'ci', 'cd', 'travis', 'gitlab-ci'].includes(t)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Helper: Get language details
|
|
318
|
+
*/
|
|
319
|
+
getLanguageDetails(languages) {
|
|
320
|
+
const languageMap = {
|
|
321
|
+
JavaScript: 'Dynamic, web-focused',
|
|
322
|
+
TypeScript: 'Typed JavaScript, enterprise',
|
|
323
|
+
Python: 'Scripting, data science, automation',
|
|
324
|
+
Java: 'Enterprise, large-scale systems',
|
|
325
|
+
Go: 'Concurrent, cloud-native',
|
|
326
|
+
Rust: 'Systems, performance-critical',
|
|
327
|
+
'C++': 'Performance, systems programming',
|
|
328
|
+
'C#': 'Enterprise, .NET ecosystem',
|
|
329
|
+
Ruby: 'Rapid development, web frameworks',
|
|
330
|
+
PHP: 'Web backend, rapid deployment',
|
|
331
|
+
};
|
|
332
|
+
return languages.map((lang) => ({
|
|
333
|
+
language: lang,
|
|
334
|
+
description: languageMap[lang] || 'General purpose',
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Calculate quality score
|
|
340
|
+
*/
|
|
341
|
+
calculateQualityScore(repoData, issues) {
|
|
342
|
+
let score = 100;
|
|
343
|
+
|
|
344
|
+
// Deduct points for issues
|
|
345
|
+
issues.forEach((issue) => {
|
|
346
|
+
if (issue.severity === 'HIGH') score -= 20;
|
|
347
|
+
else if (issue.severity === 'MEDIUM') score -= 10;
|
|
348
|
+
else if (issue.severity === 'LOW') score -= 3;
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Add bonus points for good practices
|
|
352
|
+
if (repoData.has_wiki) score += 2;
|
|
353
|
+
if (repoData.has_pages) score += 2;
|
|
354
|
+
if (repoData.license) score += 3;
|
|
355
|
+
if (repoData.stargazers_count > 1000) score += 5;
|
|
356
|
+
|
|
357
|
+
return Math.max(0, Math.min(100, score));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Generate recommendations
|
|
362
|
+
*/
|
|
363
|
+
generateQualityRecommendations(issues) {
|
|
364
|
+
return issues.map((issue) => ({
|
|
365
|
+
priority: issue.severity,
|
|
366
|
+
issue: issue.message,
|
|
367
|
+
location: issue.location,
|
|
368
|
+
action: issue.recommendation,
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get rating from score
|
|
374
|
+
*/
|
|
375
|
+
getRating(score) {
|
|
376
|
+
if (score >= 90) return 'A+';
|
|
377
|
+
if (score >= 80) return 'A';
|
|
378
|
+
if (score >= 70) return 'B+';
|
|
379
|
+
if (score >= 60) return 'B';
|
|
380
|
+
if (score >= 50) return 'C+';
|
|
381
|
+
if (score >= 40) return 'C';
|
|
382
|
+
return 'F';
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export default CodeQualityAnalyzer;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { GitHubClient } from '../github/client.js';
|
|
2
|
+
|
|
3
|
+
export class QualityAnalyzer {
|
|
4
|
+
constructor(githubClient) {
|
|
5
|
+
this.client = githubClient;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Analyze code quality metrics
|
|
10
|
+
*/
|
|
11
|
+
async analyzeCodeQuality(owner, repo) {
|
|
12
|
+
if (!owner || !repo) {
|
|
13
|
+
throw new Error('Owner and repo are required');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
console.info(`Analyzing repo: ${owner}/${repo}`);
|
|
18
|
+
|
|
19
|
+
const [repoData, languages] = await Promise.all([
|
|
20
|
+
this.client.getRepository(owner, repo),
|
|
21
|
+
this.client.getLanguages(owner, repo),
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const daysInactive = repoData.pushed_at
|
|
25
|
+
? Math.floor((Date.now() - new Date(repoData.pushed_at)) / (1000 * 60 * 60 * 24))
|
|
26
|
+
: null;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
score: this.calculateQualityScore(repoData),
|
|
30
|
+
metrics: {
|
|
31
|
+
stars: repoData.stargazers_count || 0,
|
|
32
|
+
forks: repoData.forks_count || 0,
|
|
33
|
+
watchers: repoData.watchers_count || 0,
|
|
34
|
+
openIssues: repoData.open_issues_count || 0,
|
|
35
|
+
hasWiki: repoData.has_wiki || false,
|
|
36
|
+
hasPages: repoData.has_pages || false,
|
|
37
|
+
languageCount: languages ? Object.keys(languages).length : 0,
|
|
38
|
+
primaryLanguage: repoData.language,
|
|
39
|
+
lastUpdate: repoData.updated_at,
|
|
40
|
+
daysInactive,
|
|
41
|
+
description: repoData.description,
|
|
42
|
+
topics: repoData.topics || [],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('QualityAnalyzer Error:', error);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Calculate overall quality score (0-100)
|
|
53
|
+
*/
|
|
54
|
+
calculateQualityScore(repoData) {
|
|
55
|
+
let score = 50; // Base score
|
|
56
|
+
|
|
57
|
+
// Stars contribution (0-15)
|
|
58
|
+
score += Math.min(repoData.stargazers_count / 1000, 15);
|
|
59
|
+
|
|
60
|
+
// Forks contribution (0-10)
|
|
61
|
+
score += Math.min(repoData.forks_count / 500, 10);
|
|
62
|
+
|
|
63
|
+
// Issues contribution (0-10)
|
|
64
|
+
const issueScore = Math.max(10 - repoData.open_issues_count / 100, 0);
|
|
65
|
+
score += issueScore;
|
|
66
|
+
|
|
67
|
+
// Documentation (0-10)
|
|
68
|
+
if (repoData.has_wiki || repoData.description) {
|
|
69
|
+
score += 5;
|
|
70
|
+
}
|
|
71
|
+
if (repoData.has_pages) {
|
|
72
|
+
score += 5;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Activity (0-10)
|
|
76
|
+
const lastUpdate = new Date(repoData.updated_at);
|
|
77
|
+
const daysSinceUpdate = Math.floor(
|
|
78
|
+
(new Date() - lastUpdate) / (1000 * 60 * 60 * 24)
|
|
79
|
+
);
|
|
80
|
+
if (daysSinceUpdate < 30) {
|
|
81
|
+
score += 10;
|
|
82
|
+
} else if (daysSinceUpdate < 90) {
|
|
83
|
+
score += 7;
|
|
84
|
+
} else if (daysSinceUpdate < 365) {
|
|
85
|
+
score += 3;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return Math.min(Math.round(score), 100);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export default QualityAnalyzer;
|