Skip to content

Commit abe0cd2

Browse files
committed
fix(plugin-js-packages): calculate total dependencies from package.json
1 parent 0a72931 commit abe0cd2

File tree

10 files changed

+96
-15
lines changed

10 files changed

+96
-15
lines changed

packages/plugin-js-packages/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ The plugin accepts the following parameters:
113113
- `packageManager`: The package manager you are using. Supported values: `npm`, `yarn-classic` (v1), `yarn-modern` (v2+), `pnpm`.
114114
- (optional) `checks`: Array of checks to be run. Supported commands: `audit`, `outdated`. Both are configured by default.
115115
- (optional) `dependencyGroups`: Array of dependency groups to be checked. `prod` and `dev` are configured by default. `optional` are opt-in.
116+
- (optional) `packageJsonPath`: File path to `package.json`. Defaults to current folder. Multiple `package.json` files are currently not supported.
116117
- (optional) `auditLevelMapping`: If you wish to set a custom level of issue severity based on audit vulnerability level, you may do so here. Any omitted values will be filled in by defaults. Audit levels are: `critical`, `high`, `moderate`, `low` and `info`. Issue severities are: `error`, `warn` and `info`. By default the mapping is as follows: `critical` and `high``error`; `moderate` and `low``warning`; `info``info`.
117118

118119
### Audits and group

packages/plugin-js-packages/src/lib/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ export const jsPackagesPluginConfigSchema = z.object({
6363
})
6464
.default(defaultAuditLevelMapping)
6565
.transform(fillAuditLevelMapping),
66+
packageJsonPath: z
67+
.string()
68+
.describe('File path to package.json. Defaults to current folder.')
69+
.default('package.json'),
6670
});
6771

6872
export type JSPackagesPluginConfig = z.input<

packages/plugin-js-packages/src/lib/config.unit.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe('jsPackagesPluginConfigSchema', () => {
1515
checks: ['audit'],
1616
packageManager: 'yarn-classic',
1717
dependencyGroups: ['prod'],
18+
packageJsonPath: './ui-app/package.json',
1819
} satisfies JSPackagesPluginConfig),
1920
).not.toThrow();
2021
});
@@ -35,6 +36,7 @@ describe('jsPackagesPluginConfigSchema', () => {
3536
checks: ['audit', 'outdated'],
3637
packageManager: 'npm',
3738
dependencyGroups: ['prod', 'dev'],
39+
packageJsonPath: 'package.json',
3840
auditLevelMapping: {
3941
critical: 'error',
4042
high: 'error',

packages/plugin-js-packages/src/lib/runner/index.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { auditResultToAuditOutput } from './audit/transform';
2121
import { AuditResult } from './audit/types';
2222
import { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants';
2323
import { outdatedResultToAuditOutput } from './outdated/transform';
24+
import { getTotalDependencies } from './utils';
2425

2526
export async function createRunnerConfig(
2627
scriptPath: string,
@@ -41,15 +42,16 @@ export async function executeRunner(): Promise<void> {
4142
packageManager,
4243
checks,
4344
auditLevelMapping,
45+
packageJsonPath,
4446
dependencyGroups: depGroups,
4547
} = await readJsonFile<FinalJSPackagesPluginConfig>(PLUGIN_CONFIG_PATH);
4648

4749
const auditResults = checks.includes('audit')
48-
? await processAudit(packageManager, auditLevelMapping, depGroups)
50+
? await processAudit(packageManager, depGroups, auditLevelMapping)
4951
: [];
5052

5153
const outdatedResults = checks.includes('outdated')
52-
? await processOutdated(packageManager, depGroups)
54+
? await processOutdated(packageManager, depGroups, packageJsonPath)
5355
: [];
5456
const checkResults = [...auditResults, ...outdatedResults];
5557

@@ -60,6 +62,7 @@ export async function executeRunner(): Promise<void> {
6062
async function processOutdated(
6163
id: PackageManagerId,
6264
depGroups: DependencyGroup[],
65+
packageJsonPath: string,
6366
) {
6467
const pm = packageManagers[id];
6568
const { stdout } = await executeProcess({
@@ -69,16 +72,23 @@ async function processOutdated(
6972
ignoreExitCode: true, // outdated returns exit code 1 when outdated dependencies are found
7073
});
7174

75+
const depTotals = await getTotalDependencies(packageJsonPath);
76+
7277
const normalizedResult = pm.outdated.unifyResult(stdout);
7378
return depGroups.map(depGroup =>
74-
outdatedResultToAuditOutput(normalizedResult, id, depGroup),
79+
outdatedResultToAuditOutput(
80+
normalizedResult,
81+
id,
82+
depGroup,
83+
depTotals[depGroup],
84+
),
7585
);
7686
}
7787

7888
async function processAudit(
7989
id: PackageManagerId,
80-
auditLevelMapping: AuditSeverity,
8190
depGroups: DependencyGroup[],
91+
auditLevelMapping: AuditSeverity,
8292
) {
8393
const pm = packageManagers[id];
8494
const supportedAuditDepGroups =

packages/plugin-js-packages/src/lib/runner/outdated/transform.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function outdatedResultToAuditOutput(
1010
result: OutdatedResult,
1111
packageManager: PackageManagerId,
1212
depGroup: DependencyGroup,
13+
totalDeps: number,
1314
): AuditOutput {
1415
const relevantDependencies: OutdatedResult = result.filter(
1516
dep => dep.type === dependencyGroupToLong[depGroup],
@@ -45,10 +46,7 @@ export function outdatedResultToAuditOutput(
4546

4647
return {
4748
slug: `${packageManager}-outdated-${depGroup}`,
48-
score: calculateOutdatedScore(
49-
outdatedStats.major,
50-
relevantDependencies.length,
51-
),
49+
score: calculateOutdatedScore(outdatedStats.major, totalDeps),
5250
value: outdatedDependencies.length,
5351
displayValue: outdatedToDisplayValue(outdatedStats),
5452
details: { issues },

packages/plugin-js-packages/src/lib/runner/outdated/transform.unit.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('outdatedResultToAuditOutput', () => {
2323
],
2424
'npm',
2525
'prod',
26+
10,
2627
),
2728
).toEqual<AuditOutput>({
2829
slug: 'npm-outdated-prod',
@@ -61,10 +62,11 @@ describe('outdatedResultToAuditOutput', () => {
6162
],
6263
'npm',
6364
'prod',
65+
5,
6466
),
6567
).toEqual<AuditOutput>({
6668
slug: 'npm-outdated-prod',
67-
score: 0.5,
69+
score: 0.8,
6870
value: 1,
6971
displayValue: '1 major outdated package version',
7072
details: {
@@ -110,10 +112,11 @@ describe('outdatedResultToAuditOutput', () => {
110112
],
111113
'npm',
112114
'prod',
115+
10,
113116
),
114117
).toEqual<AuditOutput>({
115118
slug: 'npm-outdated-prod',
116-
score: 0.75,
119+
score: 0.9,
117120
value: 4,
118121
displayValue: '4 outdated package versions (1 major, 1 minor, 2 patch)',
119122
details: {
@@ -160,6 +163,7 @@ describe('outdatedResultToAuditOutput', () => {
160163
],
161164
'npm',
162165
'optional',
166+
1,
163167
),
164168
).toEqual<AuditOutput>({
165169
slug: 'npm-outdated-optional',
@@ -183,6 +187,7 @@ describe('outdatedResultToAuditOutput', () => {
183187
],
184188
'npm',
185189
'optional',
190+
1,
186191
),
187192
).toEqual<AuditOutput>({
188193
slug: 'npm-outdated-optional',
@@ -224,6 +229,7 @@ describe('outdatedResultToAuditOutput', () => {
224229
],
225230
'npm',
226231
'dev',
232+
1,
227233
),
228234
).toEqual<AuditOutput>({
229235
slug: 'npm-outdated-dev',

packages/plugin-js-packages/src/lib/runner/outdated/types.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import type { ReleaseType } from 'semver';
22

33
export type PackageVersion = Record<ReleaseType, number>;
4-
export type DependencyGroupLong =
5-
| 'dependencies'
6-
| 'devDependencies'
7-
| 'optionalDependencies';
4+
export const dependencyGroupLong = [
5+
'dependencies',
6+
'devDependencies',
7+
'optionalDependencies',
8+
] as const;
9+
export type DependencyGroupLong = (typeof dependencyGroupLong)[number];
10+
11+
type PackageJsonDependencies = Record<string, string>;
12+
export type PackageJson = Partial<
13+
Record<DependencyGroupLong, PackageJsonDependencies>
14+
>;
815

916
// Unified Outdated result type
1017
export type OutdatedDependency = {

packages/plugin-js-packages/src/lib/runner/runner.integration.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('createRunnerConfig', () => {
1313
checks: ['audit'],
1414
auditLevelMapping: defaultAuditLevelMapping,
1515
dependencyGroups: ['prod', 'dev'],
16+
packageJsonPath: 'package.json',
1617
});
1718
expect(runnerConfig).toStrictEqual<RunnerConfig>({
1819
command: 'node',
@@ -28,6 +29,7 @@ describe('createRunnerConfig', () => {
2829
checks: ['outdated'],
2930
dependencyGroups: ['prod', 'dev'],
3031
auditLevelMapping: { ...defaultAuditLevelMapping, moderate: 'error' },
32+
packageJsonPath: 'package.json',
3133
};
3234
await createRunnerConfig('executeRunner.ts', pluginConfig);
3335
const config = await readJsonFile<FinalJSPackagesPluginConfig>(

packages/plugin-js-packages/src/lib/runner/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1+
import {
2+
objectFromEntries,
3+
objectToKeys,
4+
readJsonFile,
5+
} from '@code-pushup/utils';
6+
import { dependencyGroups } from '../config';
7+
import { dependencyGroupToLong } from '../constants';
18
import { AuditResult, Vulnerability } from './audit/types';
9+
import { PackageJson } from './outdated/types';
210

311
export function filterAuditResult(
412
result: AuditResult,
@@ -40,3 +48,13 @@ export function filterAuditResult(
4048
summary: uniqueResult.summary,
4149
};
4250
}
51+
52+
export async function getTotalDependencies(packageJsonPath: string) {
53+
const packageJson = await readJsonFile<PackageJson>(packageJsonPath);
54+
return objectFromEntries(
55+
dependencyGroups.map(depGroup => {
56+
const deps = packageJson[dependencyGroupToLong[depGroup]];
57+
return [depGroup, deps == null ? 0 : objectToKeys(deps).length];
58+
}),
59+
);
60+
}

packages/plugin-js-packages/src/lib/runner/utils.unit.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
1+
import { vol } from 'memfs';
2+
import { join } from 'node:path';
13
import { describe, expect, it } from 'vitest';
4+
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
5+
import { DependencyGroup } from '../config';
26
import { AuditResult, Vulnerability } from './audit/types';
3-
import { filterAuditResult } from './utils';
7+
import { PackageJson } from './outdated/types';
8+
import { filterAuditResult, getTotalDependencies } from './utils';
9+
10+
describe('getTotalDependencies', () => {
11+
beforeEach(() => {
12+
vol.fromJSON(
13+
{
14+
'package.json': JSON.stringify({
15+
dependencies: { '@code-pushup/eslint-config': '1.0.0' },
16+
devDependencies: {
17+
cypress: '13.10.0',
18+
vite: '5.1.4',
19+
vitest: '1.3.1',
20+
},
21+
} satisfies PackageJson),
22+
},
23+
MEMFS_VOLUME,
24+
);
25+
});
26+
27+
it('should return correct number of dependencies', async () => {
28+
await expect(
29+
getTotalDependencies(join(MEMFS_VOLUME, 'package.json')),
30+
).resolves.toStrictEqual({
31+
prod: 1,
32+
dev: 3,
33+
optional: 0,
34+
} satisfies Record<DependencyGroup, number>);
35+
});
36+
});
437

538
describe('filterAuditResult', () => {
639
describe('filtering out NPM production vulnerabilities', () => {

0 commit comments

Comments
 (0)