@aiready/deps 0.11.17 → 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.
@@ -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
- const packageJsonPath = join(tmpDir, 'package.json');
14
+ // NPM
15
15
  writeFileSync(
16
- packageJsonPath,
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 outdated, deprecated, and skew signals', async () => {
85
+ it('detects dependencies across multiple ecosystems', async () => {
37
86
  const report = await analyzeDeps({
38
87
  rootDir: tmpDir,
39
- trainingCutoffYear: 2023,
88
+ exclude: ['vendor'],
40
89
  });
41
90
 
42
- expect(report.summary.packagesAnalyzed).toBe(6);
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
- // request, moment are known deprecated
45
- expect(report.rawData.deprecatedPackages).toBe(2);
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
- // lodash is 0.x (pre-v1) -> flagged as outdated in our mock
48
- expect(report.rawData.outdatedPackages).toBe(1);
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
- // next 15, react 19, ts 5.6 -> skew signals
51
- expect(report.rawData.trainingCutoffSkew).toBeGreaterThan(0);
106
+ const report = await analyzeDeps({ rootDir: emptyDir });
107
+ expect(report.summary.packagesAnalyzed).toBe(0);
52
108
 
53
- expect(report.issues.length).toBeGreaterThan(0);
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
@@ -6,4 +6,5 @@ ToolRegistry.register(DepsProvider);
6
6
 
7
7
  export * from './types';
8
8
  export * from './analyzer';
9
+ export * from './scoring';
9
10
  export { DepsProvider };
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
+ }