@aiready/deps 0.11.18 → 0.11.21
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/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-test.log +8 -6
- package/coverage/analyzer.ts.html +841 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +127 -0
- package/coverage/coverage-final.json +4 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +146 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/provider.ts.html +307 -0
- package/coverage/scoring.ts.html +277 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/index.d.mts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +56 -4
- package/dist/index.mjs +52 -1
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +79 -23
- package/src/__tests__/provider.test.ts +45 -0
- package/src/__tests__/scoring.test.ts +47 -0
- package/src/index.ts +1 -0
- package/src/scoring.ts +64 -0
|
@@ -8,48 +8,104 @@ describe('Deps Health Analyzer', () => {
|
|
|
8
8
|
let tmpDir: string;
|
|
9
9
|
|
|
10
10
|
beforeAll(() => {
|
|
11
|
-
tmpDir = join(tmpdir(), `deps-test-${Date.now()}`);
|
|
11
|
+
tmpDir = join(tmpdir(), `deps-test-complex-${Date.now()}`);
|
|
12
12
|
mkdirSync(tmpDir, { recursive: true });
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// NPM
|
|
15
15
|
writeFileSync(
|
|
16
|
-
|
|
16
|
+
join(tmpDir, 'package.json'),
|
|
17
17
|
JSON.stringify({
|
|
18
|
-
dependencies: {
|
|
19
|
-
request: '^2.88.2',
|
|
20
|
-
moment: '~2.29.4',
|
|
21
|
-
lodash: '^0.4.0',
|
|
22
|
-
react: '^19.0.0',
|
|
23
|
-
next: '15.0.0-rc',
|
|
24
|
-
},
|
|
25
|
-
devDependencies: {
|
|
26
|
-
typescript: '5.6.3',
|
|
27
|
-
},
|
|
18
|
+
dependencies: { lodash: '^0.4.0', request: '^2.88.2' },
|
|
28
19
|
})
|
|
29
20
|
);
|
|
21
|
+
|
|
22
|
+
// Python requirements.txt
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(tmpDir, 'requirements.txt'),
|
|
25
|
+
'requests==2.31.0\nurllib3>=1.26.15\n# Comment\nflask'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Maven pom.xml
|
|
29
|
+
writeFileSync(
|
|
30
|
+
join(tmpDir, 'pom.xml'),
|
|
31
|
+
`
|
|
32
|
+
<project>
|
|
33
|
+
<dependencies>
|
|
34
|
+
<dependency>
|
|
35
|
+
<artifactId>log4j</artifactId>
|
|
36
|
+
</dependency>
|
|
37
|
+
<dependency>
|
|
38
|
+
<artifactId>junit</artifactId>
|
|
39
|
+
</dependency>
|
|
40
|
+
</dependencies>
|
|
41
|
+
</project>
|
|
42
|
+
`
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Go go.mod
|
|
46
|
+
writeFileSync(
|
|
47
|
+
join(tmpDir, 'go.mod'),
|
|
48
|
+
`
|
|
49
|
+
module example.com/m
|
|
50
|
+
go 1.21
|
|
51
|
+
require github.com/gorilla/mux v1.8.1
|
|
52
|
+
require (
|
|
53
|
+
github.com/spf13/cobra v1.8.0
|
|
54
|
+
github.com/stretchr/testify v1.8.4
|
|
55
|
+
)
|
|
56
|
+
`
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// .NET csproj
|
|
60
|
+
writeFileSync(
|
|
61
|
+
join(tmpDir, 'app.csproj'),
|
|
62
|
+
`
|
|
63
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
64
|
+
<ItemGroup>
|
|
65
|
+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
66
|
+
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
|
67
|
+
</ItemGroup>
|
|
68
|
+
</Project>
|
|
69
|
+
`
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Subdirectory with exclusions
|
|
73
|
+
const subDir = join(tmpDir, 'vendor');
|
|
74
|
+
mkdirSync(subDir);
|
|
75
|
+
writeFileSync(
|
|
76
|
+
join(subDir, 'package.json'),
|
|
77
|
+
JSON.stringify({ dependencies: { 'should-be-ignored': '1.0.0' } })
|
|
78
|
+
);
|
|
30
79
|
});
|
|
31
80
|
|
|
32
81
|
afterAll(() => {
|
|
33
82
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
34
83
|
});
|
|
35
84
|
|
|
36
|
-
it('detects
|
|
85
|
+
it('detects dependencies across multiple ecosystems', async () => {
|
|
37
86
|
const report = await analyzeDeps({
|
|
38
87
|
rootDir: tmpDir,
|
|
39
|
-
|
|
88
|
+
exclude: ['vendor'],
|
|
40
89
|
});
|
|
41
90
|
|
|
42
|
-
|
|
91
|
+
// 2 (npm) + 3 (python) + 2 (maven) + 3 (go) + 2 (dotnet) = 12
|
|
92
|
+
expect(report.summary.packagesAnalyzed).toBe(12);
|
|
93
|
+
expect(report.summary.filesAnalyzed).toBe(5); // Ignored vendor/package.json
|
|
43
94
|
|
|
44
|
-
//
|
|
45
|
-
|
|
95
|
+
// Check for deprecated ones we added
|
|
96
|
+
// request (npm), log4j (maven), gorilla/mux (go), urllib3 (python)
|
|
97
|
+
expect(report.rawData.deprecatedPackages).toBeGreaterThanOrEqual(4);
|
|
98
|
+
});
|
|
46
99
|
|
|
47
|
-
|
|
48
|
-
|
|
100
|
+
it('handles empty or malformed manifests gracefully', async () => {
|
|
101
|
+
const emptyDir = join(tmpdir(), `deps-test-empty-${Date.now()}`);
|
|
102
|
+
mkdirSync(emptyDir);
|
|
103
|
+
writeFileSync(join(emptyDir, 'package.json'), 'invalid json');
|
|
104
|
+
writeFileSync(join(emptyDir, 'requirements.txt'), '');
|
|
49
105
|
|
|
50
|
-
|
|
51
|
-
expect(report.
|
|
106
|
+
const report = await analyzeDeps({ rootDir: emptyDir });
|
|
107
|
+
expect(report.summary.packagesAnalyzed).toBe(0);
|
|
52
108
|
|
|
53
|
-
|
|
109
|
+
rmSync(emptyDir, { recursive: true, force: true });
|
|
54
110
|
});
|
|
55
111
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { DepsProvider } from '../provider';
|
|
3
|
+
import * as analyzer from '../analyzer';
|
|
4
|
+
|
|
5
|
+
vi.mock('../analyzer', () => ({
|
|
6
|
+
analyzeDeps: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe('Dependency Health Provider', () => {
|
|
10
|
+
it('should analyze and return SpokeOutput', async () => {
|
|
11
|
+
vi.mocked(analyzer.analyzeDeps).mockResolvedValue({
|
|
12
|
+
summary: {
|
|
13
|
+
filesAnalyzed: 1,
|
|
14
|
+
packagesAnalyzed: 10,
|
|
15
|
+
score: 95,
|
|
16
|
+
rating: 'excellent',
|
|
17
|
+
},
|
|
18
|
+
issues: [],
|
|
19
|
+
rawData: {
|
|
20
|
+
totalPackages: 10,
|
|
21
|
+
outdatedPackages: 0,
|
|
22
|
+
deprecatedPackages: 0,
|
|
23
|
+
trainingCutoffSkew: 0,
|
|
24
|
+
},
|
|
25
|
+
recommendations: [],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const output = await DepsProvider.analyze({ rootDir: '.' });
|
|
29
|
+
|
|
30
|
+
expect(output.summary.filesAnalyzed).toBe(1);
|
|
31
|
+
expect(output.metadata.toolName).toBe('dependency-health');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should score an output', () => {
|
|
35
|
+
const mockOutput = {
|
|
36
|
+
summary: { score: 85, recommendations: ['Update things'] } as any,
|
|
37
|
+
metadata: { rawData: {} },
|
|
38
|
+
results: [],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const scoring = DepsProvider.score(mockOutput as any, { rootDir: '.' });
|
|
42
|
+
expect(scoring.score).toBe(85);
|
|
43
|
+
expect(scoring.recommendations[0].action).toBe('Update things');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { calculateDepsScore } from '../scoring';
|
|
3
|
+
import { DepsReport } from '../types';
|
|
4
|
+
import { ToolName } from '@aiready/core';
|
|
5
|
+
|
|
6
|
+
describe('Dependency Health Scoring', () => {
|
|
7
|
+
const mockReport: DepsReport = {
|
|
8
|
+
summary: {
|
|
9
|
+
filesAnalyzed: 2,
|
|
10
|
+
packagesAnalyzed: 20,
|
|
11
|
+
score: 85,
|
|
12
|
+
rating: 'excellent',
|
|
13
|
+
},
|
|
14
|
+
issues: [],
|
|
15
|
+
rawData: {
|
|
16
|
+
totalPackages: 100,
|
|
17
|
+
outdatedPackages: 10,
|
|
18
|
+
deprecatedPackages: 2,
|
|
19
|
+
trainingCutoffSkew: 0.1,
|
|
20
|
+
},
|
|
21
|
+
recommendations: ['Replace 2 deprecated packages.'],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
it('should map report to ToolScoringOutput correctly', () => {
|
|
25
|
+
const scoring = calculateDepsScore(mockReport);
|
|
26
|
+
|
|
27
|
+
expect(scoring.toolName).toBe(ToolName.DependencyHealth);
|
|
28
|
+
expect(scoring.score).toBe(85);
|
|
29
|
+
expect(scoring.factors.length).toBeGreaterThan(0);
|
|
30
|
+
expect(scoring.recommendations[0].action).toBe(
|
|
31
|
+
'Replace 2 deprecated packages.'
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should set high priority for low scores', () => {
|
|
36
|
+
const lowScoreReport: DepsReport = {
|
|
37
|
+
...mockReport,
|
|
38
|
+
summary: {
|
|
39
|
+
...mockReport.summary,
|
|
40
|
+
score: 30,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const scoring = calculateDepsScore(lowScoreReport);
|
|
45
|
+
expect(scoring.recommendations[0].priority).toBe('high');
|
|
46
|
+
});
|
|
47
|
+
});
|
package/src/index.ts
CHANGED
package/src/scoring.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { calculateDependencyHealth, ToolName } from '@aiready/core';
|
|
2
|
+
import type { ToolScoringOutput } from '@aiready/core';
|
|
3
|
+
import type { DepsReport } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert dependency health report into a ToolScoringOutput.
|
|
7
|
+
*/
|
|
8
|
+
export function calculateDepsScore(report: DepsReport): ToolScoringOutput {
|
|
9
|
+
const { rawData, summary } = report;
|
|
10
|
+
|
|
11
|
+
// Recalculate using core math to get risk contribution breakdown
|
|
12
|
+
const riskResult = calculateDependencyHealth({
|
|
13
|
+
totalPackages: rawData.totalPackages,
|
|
14
|
+
outdatedPackages: rawData.outdatedPackages,
|
|
15
|
+
deprecatedPackages: rawData.deprecatedPackages,
|
|
16
|
+
trainingCutoffSkew: rawData.trainingCutoffSkew,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const factors: ToolScoringOutput['factors'] = [
|
|
20
|
+
{
|
|
21
|
+
name: 'Outdated Packages',
|
|
22
|
+
impact: -Math.min(
|
|
23
|
+
30,
|
|
24
|
+
(rawData.outdatedPackages / Math.max(1, rawData.totalPackages)) *
|
|
25
|
+
100 *
|
|
26
|
+
0.3
|
|
27
|
+
),
|
|
28
|
+
description: `${rawData.outdatedPackages} outdated packages`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'Deprecated Packages',
|
|
32
|
+
impact: -Math.min(
|
|
33
|
+
40,
|
|
34
|
+
(rawData.deprecatedPackages / Math.max(1, rawData.totalPackages)) *
|
|
35
|
+
100 *
|
|
36
|
+
0.4
|
|
37
|
+
),
|
|
38
|
+
description: `${rawData.deprecatedPackages} deprecated packages`,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Training Cutoff Skew',
|
|
42
|
+
impact: -Math.min(30, rawData.trainingCutoffSkew * 100 * 0.3),
|
|
43
|
+
description: `Training cutoff skew of ${rawData.trainingCutoffSkew.toFixed(1)} years`,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const recommendations: ToolScoringOutput['recommendations'] =
|
|
48
|
+
riskResult.recommendations.map((rec) => ({
|
|
49
|
+
action: rec,
|
|
50
|
+
estimatedImpact: 6,
|
|
51
|
+
priority: summary.score < 50 ? 'high' : 'medium',
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
toolName: ToolName.DependencyHealth,
|
|
56
|
+
score: summary.score,
|
|
57
|
+
rawMetrics: {
|
|
58
|
+
...rawData,
|
|
59
|
+
rating: summary.rating,
|
|
60
|
+
},
|
|
61
|
+
factors,
|
|
62
|
+
recommendations,
|
|
63
|
+
};
|
|
64
|
+
}
|