Skip to content

Commit fcd4dd0

Browse files
committed
Handle cycles in audit report
1 parent e0e448a commit fcd4dd0

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,8 +1,9 @@
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";
54
import { widgetRoot } from "../widget/paths";
5+
import { ensure, partition } from "../common";
6+
import { exec } from "node:child_process";
67

78
export type Report = {
89
auditReportVersion: 2;
@@ -59,15 +60,28 @@ export type Vulnerability = {
5960
range: string;
6061
};
6162

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

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

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

0 commit comments

Comments
 (0)