Skip to content

Commit 8273b48

Browse files
committed
chore: implement apostrophising, JSON lines, improve readability
1 parent 9c1f1c6 commit 8273b48

File tree

16 files changed

+147
-119
lines changed

16 files changed

+147
-119
lines changed

packages/plugin-js-packages/src/lib/runner/audit/constants.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,20 @@ export const auditScoreModifiers: Record<PackageAuditLevel, number> = {
1717
};
1818
/* eslint-enable no-magic-numbers */
1919

20-
/* eslint-disable @typescript-eslint/consistent-type-assertions */
2120
export const normalizeAuditMapper: Record<
2221
PackageManager,
23-
(_: string) => AuditResult
22+
(output: string) => AuditResult
2423
> = {
2524
npm: npmToAuditResult,
2625
'yarn-classic': yarnv1ToAuditResult,
2726
// eslint-disable-next-line @typescript-eslint/naming-convention
28-
'yarn-modern': _ => ({} as AuditResult),
29-
pnpm: _ => ({} as AuditResult),
27+
'yarn-modern': () => {
28+
throw new Error('Yarn v2+ audit is not supported yet.');
29+
},
30+
pnpm: () => {
31+
throw new Error('PNPM audit is not supported yet.');
32+
},
3033
};
31-
/* eslint-enable @typescript-eslint/consistent-type-assertions */
3234

3335
const npmDependencyOptions: Record<DependencyGroup, string[]> = {
3436
prod: ['--omit=dev', '--omit=optional'],
@@ -41,6 +43,7 @@ export const auditArgs = (
4143
): Record<PackageManager, string[]> => ({
4244
npm: [...npmDependencyOptions[groupDep], '--json', '--audit-level=none'],
4345
'yarn-classic': ['--json', `--groups ${dependencyGroupToLong[groupDep]}`],
46+
// TODO: Add once the package managers are supported.
4447
'yarn-modern': [],
4548
pnpm: [],
4649
});

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

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import type { AuditOutput, Issue, IssueSeverity } from '@code-pushup/models';
2-
import { objectToEntries } from '@code-pushup/utils';
1+
import type { AuditOutput, Issue } from '@code-pushup/models';
2+
import { apostrophize, objectToEntries } from '@code-pushup/utils';
33
import {
4+
AuditSeverity,
45
DependencyGroup,
5-
PackageAuditLevel,
66
PackageManager,
77
packageAuditLevels,
88
} from '../../config';
99
import { auditScoreModifiers } from './constants';
10-
import { AuditResult } from './types';
10+
import { AuditResult, AuditSummary, Vulnerability } from './types';
1111

1212
export function auditResultToAuditOutput(
1313
result: AuditResult,
1414
packageManager: PackageManager,
1515
dependenciesType: DependencyGroup,
16-
auditLevelMapping: Record<PackageAuditLevel, IssueSeverity>,
16+
auditLevelMapping: AuditSeverity,
1717
): AuditOutput {
1818
const issues = vulnerabilitiesToIssues(
1919
result.vulnerabilities,
@@ -24,14 +24,12 @@ export function auditResultToAuditOutput(
2424
slug: `${packageManager}-audit-${dependenciesType}`,
2525
score: calculateAuditScore(result.summary),
2626
value: result.summary.total,
27-
displayValue: vulnerabilitiesToDisplayValue(result.summary),
27+
displayValue: summaryToDisplayValue(result.summary),
2828
...(issues.length > 0 && { details: { issues } }),
2929
};
3030
}
3131

32-
export function calculateAuditScore(
33-
stats: Record<PackageAuditLevel | 'total', number>,
34-
) {
32+
export function calculateAuditScore(stats: AuditSummary) {
3533
if (stats.total === 0) {
3634
return 1;
3735
}
@@ -49,43 +47,41 @@ export function calculateAuditScore(
4947
);
5048
}
5149

52-
export function vulnerabilitiesToDisplayValue(
53-
vulnerabilities: Record<PackageAuditLevel | 'total', number>,
54-
): string {
55-
if (vulnerabilities.total === 0) {
50+
export function summaryToDisplayValue(summary: AuditSummary): string {
51+
if (summary.total === 0) {
5652
return '0 vulnerabilities';
5753
}
5854

5955
const vulnerabilityStats = packageAuditLevels
60-
.map(level =>
61-
vulnerabilities[level] > 0 ? `${vulnerabilities[level]} ${level}` : '',
62-
)
56+
.map(level => (summary[level] > 0 ? `${summary[level]} ${level}` : ''))
6357
.filter(text => text !== '')
6458
.join(', ');
65-
return `${vulnerabilities.total} ${
66-
vulnerabilities.total === 1 ? 'vulnerability' : 'vulnerabilities'
59+
return `${summary.total} ${
60+
summary.total === 1 ? 'vulnerability' : 'vulnerabilities'
6761
} (${vulnerabilityStats})`;
6862
}
6963

7064
export function vulnerabilitiesToIssues(
71-
vulnerabilities: AuditResult['vulnerabilities'],
72-
auditLevelMapping: Record<PackageAuditLevel, IssueSeverity>,
65+
vulnerabilities: Vulnerability[],
66+
auditLevelMapping: AuditSeverity,
7367
): Issue[] {
7468
if (vulnerabilities.length === 0) {
7569
return [];
7670
}
7771

78-
return Object.values(vulnerabilities).map<Issue>(detail => {
72+
return Object.values(vulnerabilities).map((detail): Issue => {
7973
const versionRange =
8074
detail.versionRange === '*'
8175
? '**all** versions'
8276
: `versions **${detail.versionRange}**`;
83-
const depHierarchy =
77+
const directDependency =
8478
typeof detail.directDependency === 'string'
85-
? `\`${detail.directDependency}\`'${
86-
detail.directDependency.endsWith('s') ? '' : 's'
87-
} dependency \`${detail.name}\``
88-
: `\`${detail.name}\` dependency`;
79+
? `\`${detail.directDependency}\``
80+
: '';
81+
const depHierarchy =
82+
directDependency === ''
83+
? `\`${detail.name}\` dependency`
84+
: `${apostrophize(directDependency)} dependency \`${detail.name}\``;
8985

9086
const vulnerabilitySummary = `has a **${detail.severity}** vulnerability in ${versionRange}.`;
9187
const fixInfo = detail.fixInformation ? ` ${detail.fixInformation}` : '';

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { defaultAuditLevelMapping } from '../../constants';
44
import {
55
auditResultToAuditOutput,
66
calculateAuditScore,
7-
vulnerabilitiesToDisplayValue,
7+
summaryToDisplayValue,
88
vulnerabilitiesToIssues,
99
} from './transform';
10-
import { AuditResult } from './types';
10+
import { Vulnerability } from './types';
1111

1212
describe('auditResultToAuditOutput', () => {
1313
it('should return audit output with no vulnerabilities', () => {
@@ -194,7 +194,7 @@ describe('calculateAuditScore', () => {
194194
describe('vulnerabilitiesToDisplayValue', () => {
195195
it('should return passed for no vulnerabilities', () => {
196196
expect(
197-
vulnerabilitiesToDisplayValue({
197+
summaryToDisplayValue({
198198
critical: 0,
199199
high: 0,
200200
moderate: 0,
@@ -207,7 +207,7 @@ describe('vulnerabilitiesToDisplayValue', () => {
207207

208208
it('should return a summary of vulnerabilities', () => {
209209
expect(
210-
vulnerabilitiesToDisplayValue({
210+
summaryToDisplayValue({
211211
critical: 1,
212212
high: 0,
213213
moderate: 2,
@@ -253,7 +253,7 @@ describe('vulnerabilitiesToIssues', () => {
253253
title: 'tough-cookie Prototype Pollution vulnerability',
254254
url: 'https://github.com/advisories/GHSA-72xf-g2v4-qvf3',
255255
},
256-
] as AuditResult['vulnerabilities'],
256+
] as Vulnerability[],
257257
defaultAuditLevelMapping,
258258
),
259259
).toEqual<Issue[]>([
@@ -273,7 +273,7 @@ describe('vulnerabilitiesToIssues', () => {
273273
name: '@cypress/request',
274274
directDependency: 'cypress',
275275
},
276-
] as AuditResult['vulnerabilities'],
276+
] as Vulnerability[],
277277
defaultAuditLevelMapping,
278278
),
279279
).toEqual<Issue[]>([
@@ -294,7 +294,7 @@ describe('vulnerabilitiesToIssues', () => {
294294
severity: 'high',
295295
directDependency: true,
296296
},
297-
] as AuditResult['vulnerabilities'],
297+
] as Vulnerability[],
298298
{ ...defaultAuditLevelMapping, high: 'info' },
299299
),
300300
).toEqual<Issue[]>([
@@ -314,7 +314,7 @@ describe('vulnerabilitiesToIssues', () => {
314314
versionRange: '*',
315315
directDependency: true,
316316
},
317-
] as AuditResult['vulnerabilities'],
317+
] as Vulnerability[],
318318
defaultAuditLevelMapping,
319319
),
320320
).toEqual<Issue[]>([

packages/plugin-js-packages/src/lib/runner/audit/unify-type.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { objectToEntries, toUnixNewlines } from '@code-pushup/utils';
1+
import { fromJsonLines, objectToEntries } from '@code-pushup/utils';
22
import { filterAuditResult } from '../utils';
33
import {
44
AuditResult,
@@ -86,15 +86,13 @@ export function npmToAdvisory(
8686
while (newReferences.length > 0 && !advisoryInfoFound) {
8787
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
8888
const ref = newReferences.pop()!;
89+
prevNodes.add(ref);
8990
const result = npmToAdvisory(ref, vulnerabilities, prevNodes);
9091

91-
if (result == null) {
92-
prevNodes.add(ref);
93-
continue;
92+
if (result != null) {
93+
advisoryInfo = { title: result.title, url: result.url };
94+
advisoryInfoFound = true;
9495
}
95-
96-
advisoryInfo = { title: result.title, url: result.url };
97-
advisoryInfoFound = true;
9896
}
9997
/* eslint-enable functional/immutable-data, functional/no-loop-statements */
10098

@@ -105,9 +103,7 @@ export function npmToAdvisory(
105103
}
106104

107105
export function yarnv1ToAuditResult(output: string): AuditResult {
108-
const yarnv1Json = jsonLinesToJson(output);
109-
const yarnv1Result = JSON.parse(yarnv1Json) as Yarnv1AuditResultJson;
110-
106+
const yarnv1Result = fromJsonLines<Yarnv1AuditResultJson>(output);
111107
const [yarnv1Advisory, yarnv1Summary] = validateYarnv1Result(yarnv1Result);
112108

113109
const vulnerabilities = yarnv1Advisory.map(
@@ -143,11 +139,6 @@ export function yarnv1ToAuditResult(output: string): AuditResult {
143139
return filterAuditResult({ vulnerabilities, summary }, 'id');
144140
}
145141

146-
function jsonLinesToJson(text: string) {
147-
const unifiedNewLines = toUnixNewlines(text).trim();
148-
return `[${unifiedNewLines.split('\n').join(',')}]`;
149-
}
150-
151142
function validateYarnv1Result(
152143
result: Yarnv1AuditResultJson,
153144
): [Yarnv1AuditAdvisory[], Yarnv1AuditSummary] {

packages/plugin-js-packages/src/lib/runner/audit/unify-type.unit.test.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, expect, it } from 'vitest';
2+
import { toJsonLines } from '@code-pushup/utils';
23
import {
34
AuditResult,
45
NpmAdvisory,
@@ -249,11 +250,9 @@ describe('yarnv1ToAuditResult', () => {
249250
},
250251
} satisfies Yarnv1AuditSummary;
251252

252-
const jsonLines = [advisory, summary]
253-
.map(item => JSON.stringify(item))
254-
.join('\n');
255-
256-
expect(yarnv1ToAuditResult(jsonLines)).toEqual<AuditResult>({
253+
expect(
254+
yarnv1ToAuditResult(toJsonLines([advisory, summary])),
255+
).toEqual<AuditResult>({
257256
vulnerabilities: [
258257
{
259258
name: 'semver',
@@ -278,13 +277,12 @@ describe('yarnv1ToAuditResult', () => {
278277
});
279278

280279
it('should throw for no audit summary', () => {
281-
expect(() =>
282-
yarnv1ToAuditResult(
283-
JSON.stringify({
284-
data: {},
285-
type: 'auditAdvisory',
286-
} as Yarnv1AuditAdvisory),
287-
),
288-
).toThrow('no summary found');
280+
const advisory = {
281+
data: {},
282+
type: 'auditAdvisory',
283+
};
284+
expect(() => yarnv1ToAuditResult(toJsonLines([advisory]))).toThrow(
285+
'no summary found',
286+
);
289287
});
290288
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const outdatedSeverity: Record<VersionType, IssueSeverity> = {
1111

1212
export const outdatedArgs: Record<PackageManager, string[]> = {
1313
npm: ['--json', '--long'],
14-
'yarn-classic': ['--json', '|', 'jq', '-s'],
14+
'yarn-classic': ['--json'],
1515
'yarn-modern': [],
1616
pnpm: [],
1717
};

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@ export function outdatedToDisplayValue(stats: Record<VersionType, number>) {
7979

8080
export function outdatedToIssues(dependencies: OutdatedResult): Issue[] {
8181
return dependencies.map<Issue>(dep => {
82-
const { name, current, latest, url, project } = dep;
82+
const { name, current, latest, url } = dep;
8383
const outdatedLevel = getOutdatedLevel(current, latest);
8484
const packageReference =
8585
url == null ? `\`${name}\`` : `[\`${name}\`](${url})`;
8686

8787
return {
88-
message: `${project}'s dependency ${packageReference} requires a **${outdatedLevel}** update from **${current}** to **${latest}**.`,
88+
message: `Package ${packageReference} requires a **${outdatedLevel}** update from **${current}** to **${latest}**.`,
8989
severity: outdatedSeverity[outdatedLevel],
9090
};
9191
});

0 commit comments

Comments
 (0)