Skip to content

Commit

Permalink
fix(plugin-js-packages): calculate total dependencies from package.json
Browse files Browse the repository at this point in the history
  • Loading branch information
Tlacenka committed Jun 3, 2024
1 parent 0a72931 commit abe0cd2
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 15 deletions.
1 change: 1 addition & 0 deletions packages/plugin-js-packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ The plugin accepts the following parameters:
- `packageManager`: The package manager you are using. Supported values: `npm`, `yarn-classic` (v1), `yarn-modern` (v2+), `pnpm`.
- (optional) `checks`: Array of checks to be run. Supported commands: `audit`, `outdated`. Both are configured by default.
- (optional) `dependencyGroups`: Array of dependency groups to be checked. `prod` and `dev` are configured by default. `optional` are opt-in.
- (optional) `packageJsonPath`: File path to `package.json`. Defaults to current folder. Multiple `package.json` files are currently not supported.
- (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`.

### Audits and group
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-js-packages/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export const jsPackagesPluginConfigSchema = z.object({
})
.default(defaultAuditLevelMapping)
.transform(fillAuditLevelMapping),
packageJsonPath: z
.string()
.describe('File path to package.json. Defaults to current folder.')
.default('package.json'),
});

export type JSPackagesPluginConfig = z.input<
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-js-packages/src/lib/config.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('jsPackagesPluginConfigSchema', () => {
checks: ['audit'],
packageManager: 'yarn-classic',
dependencyGroups: ['prod'],
packageJsonPath: './ui-app/package.json',
} satisfies JSPackagesPluginConfig),
).not.toThrow();
});
Expand All @@ -35,6 +36,7 @@ describe('jsPackagesPluginConfigSchema', () => {
checks: ['audit', 'outdated'],
packageManager: 'npm',
dependencyGroups: ['prod', 'dev'],
packageJsonPath: 'package.json',
auditLevelMapping: {
critical: 'error',
high: 'error',
Expand Down
18 changes: 14 additions & 4 deletions packages/plugin-js-packages/src/lib/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { auditResultToAuditOutput } from './audit/transform';
import { AuditResult } from './audit/types';
import { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants';
import { outdatedResultToAuditOutput } from './outdated/transform';
import { getTotalDependencies } from './utils';

export async function createRunnerConfig(
scriptPath: string,
Expand All @@ -41,15 +42,16 @@ export async function executeRunner(): Promise<void> {
packageManager,
checks,
auditLevelMapping,
packageJsonPath,
dependencyGroups: depGroups,
} = await readJsonFile<FinalJSPackagesPluginConfig>(PLUGIN_CONFIG_PATH);

const auditResults = checks.includes('audit')
? await processAudit(packageManager, auditLevelMapping, depGroups)
? await processAudit(packageManager, depGroups, auditLevelMapping)
: [];

const outdatedResults = checks.includes('outdated')
? await processOutdated(packageManager, depGroups)
? await processOutdated(packageManager, depGroups, packageJsonPath)
: [];
const checkResults = [...auditResults, ...outdatedResults];

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

const depTotals = await getTotalDependencies(packageJsonPath);

const normalizedResult = pm.outdated.unifyResult(stdout);
return depGroups.map(depGroup =>
outdatedResultToAuditOutput(normalizedResult, id, depGroup),
outdatedResultToAuditOutput(
normalizedResult,
id,
depGroup,
depTotals[depGroup],
),
);
}

async function processAudit(
id: PackageManagerId,
auditLevelMapping: AuditSeverity,
depGroups: DependencyGroup[],
auditLevelMapping: AuditSeverity,
) {
const pm = packageManagers[id];
const supportedAuditDepGroups =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function outdatedResultToAuditOutput(
result: OutdatedResult,
packageManager: PackageManagerId,
depGroup: DependencyGroup,
totalDeps: number,
): AuditOutput {
const relevantDependencies: OutdatedResult = result.filter(
dep => dep.type === dependencyGroupToLong[depGroup],
Expand Down Expand Up @@ -45,10 +46,7 @@ export function outdatedResultToAuditOutput(

return {
slug: `${packageManager}-outdated-${depGroup}`,
score: calculateOutdatedScore(
outdatedStats.major,
relevantDependencies.length,
),
score: calculateOutdatedScore(outdatedStats.major, totalDeps),
value: outdatedDependencies.length,
displayValue: outdatedToDisplayValue(outdatedStats),
details: { issues },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('outdatedResultToAuditOutput', () => {
],
'npm',
'prod',
10,
),
).toEqual<AuditOutput>({
slug: 'npm-outdated-prod',
Expand Down Expand Up @@ -61,10 +62,11 @@ describe('outdatedResultToAuditOutput', () => {
],
'npm',
'prod',
5,
),
).toEqual<AuditOutput>({
slug: 'npm-outdated-prod',
score: 0.5,
score: 0.8,
value: 1,
displayValue: '1 major outdated package version',
details: {
Expand Down Expand Up @@ -110,10 +112,11 @@ describe('outdatedResultToAuditOutput', () => {
],
'npm',
'prod',
10,
),
).toEqual<AuditOutput>({
slug: 'npm-outdated-prod',
score: 0.75,
score: 0.9,
value: 4,
displayValue: '4 outdated package versions (1 major, 1 minor, 2 patch)',
details: {
Expand Down Expand Up @@ -160,6 +163,7 @@ describe('outdatedResultToAuditOutput', () => {
],
'npm',
'optional',
1,
),
).toEqual<AuditOutput>({
slug: 'npm-outdated-optional',
Expand All @@ -183,6 +187,7 @@ describe('outdatedResultToAuditOutput', () => {
],
'npm',
'optional',
1,
),
).toEqual<AuditOutput>({
slug: 'npm-outdated-optional',
Expand Down Expand Up @@ -224,6 +229,7 @@ describe('outdatedResultToAuditOutput', () => {
],
'npm',
'dev',
1,
),
).toEqual<AuditOutput>({
slug: 'npm-outdated-dev',
Expand Down
15 changes: 11 additions & 4 deletions packages/plugin-js-packages/src/lib/runner/outdated/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import type { ReleaseType } from 'semver';

export type PackageVersion = Record<ReleaseType, number>;
export type DependencyGroupLong =
| 'dependencies'
| 'devDependencies'
| 'optionalDependencies';
export const dependencyGroupLong = [
'dependencies',
'devDependencies',
'optionalDependencies',
] as const;
export type DependencyGroupLong = (typeof dependencyGroupLong)[number];

type PackageJsonDependencies = Record<string, string>;
export type PackageJson = Partial<
Record<DependencyGroupLong, PackageJsonDependencies>
>;

// Unified Outdated result type
export type OutdatedDependency = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('createRunnerConfig', () => {
checks: ['audit'],
auditLevelMapping: defaultAuditLevelMapping,
dependencyGroups: ['prod', 'dev'],
packageJsonPath: 'package.json',
});
expect(runnerConfig).toStrictEqual<RunnerConfig>({
command: 'node',
Expand All @@ -28,6 +29,7 @@ describe('createRunnerConfig', () => {
checks: ['outdated'],
dependencyGroups: ['prod', 'dev'],
auditLevelMapping: { ...defaultAuditLevelMapping, moderate: 'error' },
packageJsonPath: 'package.json',
};
await createRunnerConfig('executeRunner.ts', pluginConfig);
const config = await readJsonFile<FinalJSPackagesPluginConfig>(
Expand Down
18 changes: 18 additions & 0 deletions packages/plugin-js-packages/src/lib/runner/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import {
objectFromEntries,
objectToKeys,
readJsonFile,
} from '@code-pushup/utils';
import { dependencyGroups } from '../config';
import { dependencyGroupToLong } from '../constants';
import { AuditResult, Vulnerability } from './audit/types';
import { PackageJson } from './outdated/types';

export function filterAuditResult(
result: AuditResult,
Expand Down Expand Up @@ -40,3 +48,13 @@ export function filterAuditResult(
summary: uniqueResult.summary,
};
}

export async function getTotalDependencies(packageJsonPath: string) {
const packageJson = await readJsonFile<PackageJson>(packageJsonPath);
return objectFromEntries(
dependencyGroups.map(depGroup => {
const deps = packageJson[dependencyGroupToLong[depGroup]];
return [depGroup, deps == null ? 0 : objectToKeys(deps).length];
}),
);
}
35 changes: 34 additions & 1 deletion packages/plugin-js-packages/src/lib/runner/utils.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
import { vol } from 'memfs';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
import { DependencyGroup } from '../config';
import { AuditResult, Vulnerability } from './audit/types';
import { filterAuditResult } from './utils';
import { PackageJson } from './outdated/types';
import { filterAuditResult, getTotalDependencies } from './utils';

describe('getTotalDependencies', () => {
beforeEach(() => {
vol.fromJSON(
{
'package.json': JSON.stringify({
dependencies: { '@code-pushup/eslint-config': '1.0.0' },
devDependencies: {
cypress: '13.10.0',
vite: '5.1.4',
vitest: '1.3.1',
},
} satisfies PackageJson),
},
MEMFS_VOLUME,
);
});

it('should return correct number of dependencies', async () => {
await expect(
getTotalDependencies(join(MEMFS_VOLUME, 'package.json')),
).resolves.toStrictEqual({
prod: 1,
dev: 3,
optional: 0,
} satisfies Record<DependencyGroup, number>);
});
});

describe('filterAuditResult', () => {
describe('filtering out NPM production vulnerabilities', () => {
Expand Down

0 comments on commit abe0cd2

Please sign in to comment.