Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ You can run it locally on any library published to npm by providing its package
LIBRARY: cdk-ecr-deployment
VERSION: 4.0.3

OVERALL SCORE: 76/100
OVERALL SCORE: 80/100

---

SUBSCORES
MAINTENANCE : 67/100
MAINTENANCE : 75/100
QUALITY : 83/100
POPULARITY : 85/100
```
Expand All @@ -51,27 +51,28 @@ SUBSCORES
Add `--details` for a detailed breakdown:

```
> cdk-construct-analyzer cdk-ecr-deployment --details
> cdk-construct-analyzer cdk-ecr-deployment --details

LIBRARY: cdk-ecr-deployment
VERSION: 4.0.3

OVERALL SCORE: 76/100
OVERALL SCORE: 80/100

---

SUBSCORES
MAINTENANCE : 67/100
MAINTENANCE : 75/100
QUALITY : 83/100
POPULARITY : 85/100

---

=== MAINTENANCE === SCORE WEIGHT
— Time To First Response ............................ ★★☆☆☆ 15
— Time To First Response ............................ ★★☆☆☆ 10
— Provenance Verification ........................... ★★★★★ 10
— Release Frequency ................................. ★★★★☆ 10
— Number Of Contributors - Maintenance .............. ★★★★☆ 5
— Number Of Feats And Fixes ......................... ★★★★★ 5
— Open Issues Ratio ................................. ★★★★★ 5

=== QUALITY === SCORE WEIGHT
Expand All @@ -83,7 +84,7 @@ SUBSCORES
— Multi Language Support ............................ ★★★★★ 5

=== POPULARITY === SCORE WEIGHT
— Weekly Downloads .................................. ★★★★★ 15
— Weekly Downloads .................................. ★★★★★ 10
— Github Stars ...................................... ★★★★☆ 10
— Number Of Contributors - Popularity ............... ★★★★☆ 5
```
Expand Down Expand Up @@ -149,6 +150,7 @@ Helps determine if the project is active and healthy, or abandoned. Signals incl
* Open issues / total issues: A lower ratio of open issues indicates backlog health and follow through normalized by repository popularity. Note: 0 total issues scores worst (100% ratio) as it suggests no community engagement.
* Release Frequency: Regular releases signal iteration, patching, and progress.
* Number of Contributors: More contributors reduce risk of abandonment and reflect shared maintenance.
* Number of Features and Fixes: Counts occurrences of "feat" and "fix" in release notes from the past year, indicating active development.

##### Quality

Expand All @@ -159,6 +161,7 @@ Signals that are visible in the repo/package that showcases quality:
* Author Track Record: Measures how many packages the author has published, more published packages often indicate greater experience.
* Changelog includes feats/fixes: Checks if there are feats/fixes published in the release notes.
* Stable versioning (>=1.x.x, not deprecated): Indicates API maturity and stability.ation makes the project easier to adopt and use (README, API References, Usage Examples).
* Multi-language Support: Supporting more CDK languages shows extra effort and intent to reach a broader developer base

##### Popularity

Expand Down
8 changes: 7 additions & 1 deletion src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const CONFIG: Config = {
signals: [
{
name: 'timeToFirstResponse',
defaultWeight: 15,
defaultWeight: 10,
description: 'Time to first response on issues',
benchmarks: (weeks: number) => categorizeLowerIsBetter([1, 4, 12, 52], weeks),
},
Expand All @@ -36,6 +36,12 @@ export const CONFIG: Config = {
description: 'Number of Contributors in the past year',
benchmarks: (contributors: number) => categorizeHigherIsBetter([8, 2, 1, 1], contributors),
},
{
name: 'numberOfFeatsAndFixes',
defaultWeight: 5,
description: 'Number of features and bug fixes contributed to the project in the past year',
benchmarks: (contributors: number) => categorizeHigherIsBetter([28, 10, 2, 1], contributors),
},
{
name: 'openIssuesRatio',
defaultWeight: 5,
Expand Down
6 changes: 4 additions & 2 deletions src/lib/data/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import {
processContributorsData,
analyzeDocumentationCompleteness,
analyzeTestsPresence,
analyzeReleaseNotesContent,
countFeatsAndFixes,
calculateTimeToFirstResponse,
calculateOpenIssuesRatio,
calculateReleaseFrequency,
analyzeReleaseNotesContent,
calculateOpenIssuesRatio,
analyzeJsiiLanguageSupport,
} from '../utils';

Expand Down Expand Up @@ -101,6 +102,7 @@ function processPackageData(rawData: RawPackageData): PackageData {
provenanceVerification: rawData.npm.hasProvenance,
numberOfContributors_Popularity: processContributorsData(repository.commits),
releaseFrequency: calculateReleaseFrequency(repository.releases),
numberOfFeatsAndFixes: countFeatsAndFixes(repository),
multiLanguageSupport: jsiiLanguageCount,
openIssuesRatio: calculateOpenIssuesRatio(repository.openIssuesCount, repository.totalIssuesCount),
};
Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type PackageData = {
readonly timeToFirstResponse?: number;
readonly provenanceVerification?: boolean;
readonly releaseFrequency?: number;
readonly numberOfFeatsAndFixes?: number;
readonly multiLanguageSupport?: number;
readonly openIssuesRatio?: number;
} & Record<string, any>;
Expand Down
48 changes: 46 additions & 2 deletions src/lib/utils/releaseNotes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { GitHubRepository } from '../types';
import { GitHubRepository, ReleaseNotesData } from '../types';

/**
* Analyzes GitHub releases to determine if they include features and fixes
*/
export function analyzeReleaseNotesContent(repository: GitHubRepository): { hasFeats: boolean; hasFixes: boolean } {
export function analyzeReleaseNotesContent(repository: GitHubRepository): ReleaseNotesData {
if (!repository.releases || repository.releases.length === 0) {
return { hasFeats: false, hasFixes: false };
}
Expand Down Expand Up @@ -40,6 +40,50 @@ export function analyzeReleaseNotesContent(repository: GitHubRepository): { hasF
return { hasFeats, hasFixes };
}

/**
* Count the number of features and bug fixes from release notes in the past year
*/
export function countFeatsAndFixes(repository: GitHubRepository): number {
if (!repository.releases || repository.releases.length === 0) {
return 0;
}

const oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);

const releasesInPastYear = repository.releases.filter(release => {
if (!release.publishedAt) {
return false;
}

const publishedDate = new Date(release.publishedAt);
return publishedDate >= oneYearAgo;
});

let count = 0;

for (const release of releasesInPastYear) {
if (release.description) {
count += countFeatsAndFixesInDescription(release.description);
}
}

return count;
}

/**
* Count features and fixes in a release description
* Looks for "feat" or "fix" keywords
*/
function countFeatsAndFixesInDescription(description: string): number {
const lowerDescription = description.toLowerCase();

const featCount = (lowerDescription.match(/feat/g) ?? []).length;
const fixCount = (lowerDescription.match(/fix/g) ?? []).length;

return featCount + fixCount;
}

/**
* Checks if release description contains feature-related content
*/
Expand Down
1 change: 1 addition & 0 deletions test/lib/data/collect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ describe('collectPackageData', () => {
weeklyDownloads: 10000,
githubStars: 500,
numberOfContributors_Popularity: 2,
numberOfFeatsAndFixes: 2,
stableVersioning: {
isStableMajorVersion: true,
hasMinorReleases: false,
Expand Down
59 changes: 57 additions & 2 deletions test/lib/utils/releaseNotes.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { GitHubRepository } from '../../../src/lib/types';
import { analyzeReleaseNotesContent } from '../../../src/lib/utils/releaseNotes';
import { analyzeReleaseNotesContent, countFeatsAndFixes } from '../../../src/lib/utils/releaseNotes';

describe('analyzeReleaseNotesContent', () => {
test('should return false for both when no releases exist', () => {
Expand Down Expand Up @@ -163,4 +163,59 @@ describe('analyzeReleaseNotesContent', () => {
expect(result.hasFeats).toBe(true);
expect(result.hasFixes).toBe(true);
});
});
});


describe('countFeatsAndFixes', () => {
test('should return 0 when no releases are provided', () => {
const repository: GitHubRepository = {
stargazerCount: 0,
releases: [],
};

expect(countFeatsAndFixes(repository)).toBe(0);
});

test('should return 0 when releases is undefined', () => {
const repository: GitHubRepository = {
stargazerCount: 0,
};

expect(countFeatsAndFixes(repository)).toBe(0);
});

test('should count features and fixes in release descriptions', () => {
const repository: GitHubRepository = {
stargazerCount: 0,
releases: [
{
publishedAt: new Date().toISOString(),
tagName: 'v1.0.0',
description: 'feat fix',
},
],
};

expect(countFeatsAndFixes(repository)).toBe(2);
});

test('should count across multiple releases', () => {
const repository: GitHubRepository = {
stargazerCount: 0,
releases: [
{
publishedAt: new Date().toISOString(),
tagName: 'v1.0.0',
description: 'feat fix',
},
{
publishedAt: new Date().toISOString(),
tagName: 'v1.1.0',
description: 'feat feat',
},
],
};

expect(countFeatsAndFixes(repository)).toBe(4);
});
});
Loading