Skip to content

Commit 5ff7dae

Browse files
committed
Handle cycles in audit report
1 parent a5ceef2 commit 5ff7dae

3 files changed

Lines changed: 33 additions & 12 deletions

File tree

packages/pluggable-widgets-tools/src/commands/audit.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export async function auditPluggableWidgetsTools(fix: boolean = false) {
2020
}
2121

2222
// Collect updateable, vulnerable packages installed by pwt
23-
const vulnerabilities = NpmAudit.collectVulnerabilities(report, report.vulnerabilities[pluggableWidgetsTools]);
23+
const vulnerabilities = NpmAudit.collectVulnerabilities(report, pluggableWidgetsTools);
2424
const vulnerableDependencies = vulnerabilities
2525
.map(v => v.name)
2626
.reduce((unique, p) => (unique.includes(p) ? unique : [...unique, p]), [] as NpmAudit.PackageName[]);
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
export function ensure<T>(arg?: T): T {
2-
if (arg == null) {
3-
throw new Error("Did not expect an argument to be undefined");
1+
export function ensure<T>(arg?: T, label: string = "argument"): T {
2+
if (arg === null || arg === undefined) {
3+
throw new Error(`Did not expect ${label} to be ${arg}`);
44
}
55
return arg;
66
}
7+
8+
export function partition<T, A extends T, B extends Exclude<T, A>>(
9+
input: Array<T>,
10+
predicate: (x: T) => x is A
11+
): [A[], B[]] {
12+
return [input.filter(predicate), input.filter((x): x is B => !predicate(x))] as const;
13+
}

packages/pluggable-widgets-tools/src/utils/npmAudit.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import assert from "node:assert";
2-
import { exec } from "node:child_process";
32
import { existsSync } from "node:fs";
43
import { join } from "node:path";
4+
import { ensure, partition } from "../common";
5+
import { exec } from "node:child_process";
56

67
export type Report = {
78
auditReportVersion: 2;
@@ -58,15 +59,28 @@ export type Vulnerability = {
5859
range: string;
5960
};
6061

61-
export function collectVulnerabilities(report: Report, dependency: Dependency): Vulnerability[] {
62-
const vulnerabilities = dependency.via.filter(v => typeof v !== "string");
63-
if (vulnerabilities.length > 0) {
64-
return vulnerabilities;
62+
export function collectVulnerabilities(report: Report, rootDependency: PackageName): Vulnerability[] {
63+
const dependencies: PackageName[] = [rootDependency];
64+
const dependenciesSeen: PackageName[] = [];
65+
const allVulnerabilities: Vulnerability[] = [];
66+
67+
while (dependencies.length > 0) {
68+
const dependencyName = ensure(dependencies.shift());
69+
dependenciesSeen.push(dependencyName);
70+
71+
const [transients, vulnerabilities] = partition(
72+
report.vulnerabilities[dependencyName].via,
73+
v => typeof v === "string"
74+
);
75+
76+
if (vulnerabilities.length > 0) {
77+
allVulnerabilities.push(...vulnerabilities);
78+
continue;
79+
}
80+
dependencies.push(...transients.filter(d => !dependenciesSeen.includes(d)));
6581
}
6682

67-
return dependency.via
68-
.filter(v => typeof v === "string")
69-
.flatMap(v => collectVulnerabilities(report, report.vulnerabilities[v]));
83+
return allVulnerabilities;
7084
}
7185

7286
export async function run(): Promise<Report> {

0 commit comments

Comments
 (0)