dep-brain 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/README.md +3 -0
- package/depbrain.output.schema.json +18 -1
- package/dist/checks/risk.d.ts +5 -1
- package/dist/checks/risk.js +93 -37
- package/dist/core/analyzer.d.ts +14 -1
- package/dist/core/analyzer.js +46 -4
- package/dist/reporters/console.js +3 -3
- package/dist/reporters/markdown.js +3 -3
- package/dist/utils/npm-api.d.ts +3 -0
- package/dist/utils/npm-api.js +22 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,3 +9,5 @@ All notable changes to this project will be documented in this file.
|
|
|
9
9
|
- Improved duplicate detection and unused dependency heuristics.
|
|
10
10
|
- Actionable recommendations for unused, duplicate, outdated, and risk findings.
|
|
11
11
|
- Ranked top-issues summary output with `--top`.
|
|
12
|
+
- Supply-chain trust scoring for risk findings.
|
|
13
|
+
- Structured risk factors in JSON output.
|
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
- Detect likely unused dependencies from source imports and scripts
|
|
22
22
|
- Detect outdated packages
|
|
23
23
|
- Highlight dependency risk signals
|
|
24
|
+
- Score package trust using supply-chain metadata
|
|
24
25
|
- Generate a simple project health score
|
|
25
26
|
- Output reports in human-readable or JSON format
|
|
26
27
|
|
|
@@ -257,6 +258,8 @@ src/
|
|
|
257
258
|
|
|
258
259
|
The project should optimize for trust, clarity, and actionability over flashy UI, generic graphs, or simply adding more checks.
|
|
259
260
|
|
|
261
|
+
Risk findings now include a `trustScore` plus structured `riskFactors` such as publish recency, maintainer count, and repository presence.
|
|
262
|
+
|
|
260
263
|
## Repository Notes
|
|
261
264
|
|
|
262
265
|
- Project brief: [docs/project-brief.md](./docs/project-brief.md)
|
|
@@ -17,6 +17,20 @@
|
|
|
17
17
|
"reasons": { "type": "array", "items": { "type": "string" } }
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
+
"riskFactors": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"required": ["daysSincePublish", "downloads", "maintainersCount", "versionCount", "recentReleaseCount", "hasRepository", "dependencyType"],
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"properties": {
|
|
25
|
+
"daysSincePublish": { "type": ["number", "null"] },
|
|
26
|
+
"downloads": { "type": ["number", "null"] },
|
|
27
|
+
"maintainersCount": { "type": ["number", "null"] },
|
|
28
|
+
"versionCount": { "type": ["number", "null"] },
|
|
29
|
+
"recentReleaseCount": { "type": ["number", "null"] },
|
|
30
|
+
"hasRepository": { "type": "boolean" },
|
|
31
|
+
"dependencyType": { "type": "string", "enum": ["dependencies", "devDependencies", "unknown"] }
|
|
32
|
+
}
|
|
33
|
+
},
|
|
20
34
|
"outputVersion": { "type": "string" },
|
|
21
35
|
"rootDir": { "type": "string" },
|
|
22
36
|
"score": { "type": "number" },
|
|
@@ -120,7 +134,7 @@
|
|
|
120
134
|
"type": "array",
|
|
121
135
|
"items": {
|
|
122
136
|
"type": "object",
|
|
123
|
-
"required": ["name", "reasons", "confidence", "reasonCodes", "explanation", "recommendation"],
|
|
137
|
+
"required": ["name", "reasons", "confidence", "reasonCodes", "explanation", "trustScore", "riskFactors", "recommendation"],
|
|
124
138
|
"additionalProperties": false,
|
|
125
139
|
"properties": {
|
|
126
140
|
"name": { "type": "string" },
|
|
@@ -129,6 +143,8 @@
|
|
|
129
143
|
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
130
144
|
"reasonCodes": { "type": "array", "items": { "type": "string" } },
|
|
131
145
|
"explanation": { "type": "array", "items": { "type": "string" } },
|
|
146
|
+
"trustScore": { "type": "string", "enum": ["high", "medium", "low"] },
|
|
147
|
+
"riskFactors": { "$ref": "#/properties/riskFactors" },
|
|
132
148
|
"recommendation": { "$ref": "#/properties/recommendation" }
|
|
133
149
|
}
|
|
134
150
|
}
|
|
@@ -147,6 +163,7 @@
|
|
|
147
163
|
"priority": { "type": "string", "enum": ["high", "medium", "low"] },
|
|
148
164
|
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
149
165
|
"summary": { "type": "string" },
|
|
166
|
+
"trustScore": { "type": "string", "enum": ["high", "medium", "low"] },
|
|
150
167
|
"recommendation": { "$ref": "#/properties/recommendation" }
|
|
151
168
|
}
|
|
152
169
|
}
|
package/dist/checks/risk.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { DependencyGraph } from "../core/graph-builder.js";
|
|
2
2
|
import type { RiskDependency } from "../core/analyzer.js";
|
|
3
3
|
import type { CheckResult } from "../core/types.js";
|
|
4
|
-
|
|
4
|
+
import { type PackageMetadata } from "../utils/npm-api.js";
|
|
5
|
+
export interface RiskCheckOptions {
|
|
6
|
+
resolvePackageMetadata?: (name: string) => Promise<PackageMetadata | null>;
|
|
7
|
+
}
|
|
8
|
+
export declare function findRiskDependencies(graph: DependencyGraph, options?: RiskCheckOptions): Promise<RiskDependency[]>;
|
|
5
9
|
export declare function runRiskCheck(graph: DependencyGraph): Promise<CheckResult>;
|
package/dist/checks/risk.js
CHANGED
|
@@ -1,50 +1,41 @@
|
|
|
1
1
|
import { getPackageMetadata } from "../utils/npm-api.js";
|
|
2
2
|
const TWO_YEARS_IN_DAYS = 365 * 2;
|
|
3
|
-
|
|
3
|
+
const ONE_YEAR_IN_DAYS = 365;
|
|
4
|
+
export async function findRiskDependencies(graph, options = {}) {
|
|
5
|
+
const resolvePackageMetadata = options.resolvePackageMetadata ?? getPackageMetadata;
|
|
4
6
|
const names = Object.keys({
|
|
5
7
|
...graph.dependencies,
|
|
6
8
|
...graph.devDependencies
|
|
7
9
|
});
|
|
8
10
|
const results = await Promise.all(names.map(async (name) => {
|
|
9
|
-
const metadata = await
|
|
10
|
-
const reasons = [];
|
|
11
|
+
const metadata = await resolvePackageMetadata(name);
|
|
11
12
|
if (!metadata) {
|
|
12
13
|
return null;
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
reasons.push("Missing repository metadata");
|
|
22
|
-
}
|
|
23
|
-
if (reasons.length === 0) {
|
|
15
|
+
const dependencyType = graph.dependencies[name]
|
|
16
|
+
? "dependencies"
|
|
17
|
+
: graph.devDependencies[name]
|
|
18
|
+
? "devDependencies"
|
|
19
|
+
: "unknown";
|
|
20
|
+
const assessment = assessRisk(metadata, dependencyType);
|
|
21
|
+
if (assessment.reasons.length === 0) {
|
|
24
22
|
return null;
|
|
25
23
|
}
|
|
26
24
|
return {
|
|
27
25
|
name,
|
|
28
|
-
reasons,
|
|
29
|
-
confidence:
|
|
30
|
-
reasonCodes:
|
|
31
|
-
explanation: reasons,
|
|
32
|
-
|
|
26
|
+
reasons: assessment.reasons,
|
|
27
|
+
confidence: assessment.confidence,
|
|
28
|
+
reasonCodes: assessment.reasonCodes,
|
|
29
|
+
explanation: assessment.reasons,
|
|
30
|
+
trustScore: assessment.trustScore,
|
|
31
|
+
riskFactors: assessment.riskFactors,
|
|
32
|
+
recommendation: buildRiskRecommendation(assessment.reasons, assessment.confidence, assessment.trustScore)
|
|
33
33
|
};
|
|
34
34
|
}));
|
|
35
35
|
return results
|
|
36
36
|
.filter((item) => item !== null)
|
|
37
37
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
38
38
|
}
|
|
39
|
-
function buildRiskRecommendation(reasons, confidence) {
|
|
40
|
-
return {
|
|
41
|
-
action: "review",
|
|
42
|
-
priority: confidence >= 0.79 ? "high" : "medium",
|
|
43
|
-
safety: "caution",
|
|
44
|
-
summary: "Review package trust signals and decide whether to keep, replace, or monitor it.",
|
|
45
|
-
reasons
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
39
|
export async function runRiskCheck(graph) {
|
|
49
40
|
const risks = await findRiskDependencies(graph);
|
|
50
41
|
return {
|
|
@@ -53,24 +44,89 @@ export async function runRiskCheck(graph) {
|
|
|
53
44
|
issues: risks.map((item) => ({
|
|
54
45
|
id: `risk:${item.name}`,
|
|
55
46
|
message: `${item.name}: ${item.reasons.join("; ")}`,
|
|
56
|
-
severity: "
|
|
47
|
+
severity: item.trustScore === "low"
|
|
48
|
+
? "critical"
|
|
49
|
+
: item.trustScore === "medium"
|
|
50
|
+
? "warning"
|
|
51
|
+
: "info",
|
|
57
52
|
confidence: item.confidence,
|
|
58
53
|
reasonCodes: item.reasonCodes,
|
|
59
54
|
explanation: item.explanation,
|
|
60
55
|
meta: {
|
|
61
56
|
name: item.name,
|
|
62
|
-
reasons: item.reasons
|
|
57
|
+
reasons: item.reasons,
|
|
58
|
+
trustScore: item.trustScore,
|
|
59
|
+
riskFactors: item.riskFactors
|
|
63
60
|
}
|
|
64
61
|
}))
|
|
65
62
|
};
|
|
66
63
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
64
|
+
function assessRisk(metadata, dependencyType) {
|
|
65
|
+
const reasons = [];
|
|
66
|
+
const reasonCodes = [];
|
|
67
|
+
let weight = 0;
|
|
68
|
+
if (metadata.daysSincePublish !== null && metadata.daysSincePublish > TWO_YEARS_IN_DAYS) {
|
|
69
|
+
reasons.push("No release in over 2 years");
|
|
70
|
+
reasonCodes.push("stale_release");
|
|
71
|
+
weight += 3;
|
|
72
|
+
}
|
|
73
|
+
else if (metadata.daysSincePublish !== null &&
|
|
74
|
+
metadata.daysSincePublish > ONE_YEAR_IN_DAYS) {
|
|
75
|
+
reasons.push("No release in over 12 months");
|
|
76
|
+
reasonCodes.push("aging_release");
|
|
77
|
+
weight += 2;
|
|
78
|
+
}
|
|
79
|
+
if (metadata.downloads !== null && metadata.downloads < 1000) {
|
|
80
|
+
reasons.push("Low weekly download volume");
|
|
81
|
+
reasonCodes.push("low_download_volume");
|
|
82
|
+
weight += 2;
|
|
83
|
+
}
|
|
84
|
+
if (!metadata.repository) {
|
|
85
|
+
reasons.push("Missing repository metadata");
|
|
86
|
+
reasonCodes.push("missing_repository_metadata");
|
|
87
|
+
weight += 2;
|
|
88
|
+
}
|
|
89
|
+
if (metadata.maintainersCount !== null && metadata.maintainersCount <= 1) {
|
|
90
|
+
reasons.push("Single maintainer package");
|
|
91
|
+
reasonCodes.push("single_maintainer");
|
|
92
|
+
weight += 2;
|
|
93
|
+
}
|
|
94
|
+
if (metadata.recentReleaseCount !== null && metadata.recentReleaseCount === 0) {
|
|
95
|
+
reasons.push("No releases published in the last 30 days");
|
|
96
|
+
reasonCodes.push("no_recent_release");
|
|
97
|
+
weight += 1;
|
|
98
|
+
}
|
|
99
|
+
if (metadata.versionCount !== null && metadata.versionCount <= 3) {
|
|
100
|
+
reasons.push("Very limited published version history");
|
|
101
|
+
reasonCodes.push("limited_version_history");
|
|
102
|
+
weight += 1;
|
|
103
|
+
}
|
|
104
|
+
const confidence = reasons.length === 0 ? 0.5 : Math.min(0.99, 0.52 + weight * 0.07);
|
|
105
|
+
const trustScore = weight >= 6 ? "low" : weight >= 3 ? "medium" : "high";
|
|
106
|
+
return {
|
|
107
|
+
confidence,
|
|
108
|
+
trustScore,
|
|
109
|
+
reasons,
|
|
110
|
+
reasonCodes,
|
|
111
|
+
riskFactors: {
|
|
112
|
+
daysSincePublish: metadata.daysSincePublish,
|
|
113
|
+
downloads: metadata.downloads,
|
|
114
|
+
maintainersCount: metadata.maintainersCount,
|
|
115
|
+
versionCount: metadata.versionCount,
|
|
116
|
+
recentReleaseCount: metadata.recentReleaseCount,
|
|
117
|
+
hasRepository: Boolean(metadata.repository),
|
|
118
|
+
dependencyType
|
|
119
|
+
}
|
|
120
|
+
};
|
|
69
121
|
}
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
122
|
+
function buildRiskRecommendation(reasons, confidence, trustScore) {
|
|
123
|
+
return {
|
|
124
|
+
action: "review",
|
|
125
|
+
priority: trustScore === "low" || confidence >= 0.8 ? "high" : "medium",
|
|
126
|
+
safety: "caution",
|
|
127
|
+
summary: trustScore === "low"
|
|
128
|
+
? "Low trust package; review whether to replace, pin, or monitor it closely."
|
|
129
|
+
: "Review package trust signals and decide whether to keep, replace, or monitor it.",
|
|
130
|
+
reasons
|
|
131
|
+
};
|
|
76
132
|
}
|
package/dist/core/analyzer.d.ts
CHANGED
|
@@ -15,6 +15,16 @@ export interface Recommendation {
|
|
|
15
15
|
summary: string;
|
|
16
16
|
reasons: string[];
|
|
17
17
|
}
|
|
18
|
+
export interface RiskFactors {
|
|
19
|
+
daysSincePublish: number | null;
|
|
20
|
+
downloads: number | null;
|
|
21
|
+
maintainersCount: number | null;
|
|
22
|
+
versionCount: number | null;
|
|
23
|
+
recentReleaseCount: number | null;
|
|
24
|
+
hasRepository: boolean;
|
|
25
|
+
dependencyType: "dependencies" | "devDependencies" | "unknown";
|
|
26
|
+
}
|
|
27
|
+
export type TrustScore = "high" | "medium" | "low";
|
|
18
28
|
export interface DuplicateDependency {
|
|
19
29
|
name: string;
|
|
20
30
|
versions: string[];
|
|
@@ -51,6 +61,8 @@ export interface RiskDependency {
|
|
|
51
61
|
confidence: number;
|
|
52
62
|
reasonCodes: string[];
|
|
53
63
|
explanation: string[];
|
|
64
|
+
trustScore: TrustScore;
|
|
65
|
+
riskFactors: RiskFactors;
|
|
54
66
|
recommendation: Recommendation;
|
|
55
67
|
}
|
|
56
68
|
export interface TopIssue {
|
|
@@ -60,6 +72,7 @@ export interface TopIssue {
|
|
|
60
72
|
priority: "high" | "medium" | "low";
|
|
61
73
|
confidence: number;
|
|
62
74
|
summary: string;
|
|
75
|
+
trustScore?: TrustScore;
|
|
63
76
|
recommendation: Recommendation;
|
|
64
77
|
}
|
|
65
78
|
export interface AnalysisResult {
|
|
@@ -94,7 +107,7 @@ export interface PackageAnalysisResult {
|
|
|
94
107
|
suggestions: string[];
|
|
95
108
|
topIssues: TopIssue[];
|
|
96
109
|
}
|
|
97
|
-
export declare const OUTPUT_VERSION = "1.
|
|
110
|
+
export declare const OUTPUT_VERSION = "1.3";
|
|
98
111
|
export interface ScoreBreakdown {
|
|
99
112
|
baseScore: number;
|
|
100
113
|
duplicates: number;
|
package/dist/core/analyzer.js
CHANGED
|
@@ -8,7 +8,7 @@ import { findWorkspacePackages } from "../utils/workspaces.js";
|
|
|
8
8
|
import { buildDependencyGraph } from "./graph-builder.js";
|
|
9
9
|
import { calculateHealthScore } from "./scorer.js";
|
|
10
10
|
import { buildAnalysisContext } from "./context.js";
|
|
11
|
-
export const OUTPUT_VERSION = "1.
|
|
11
|
+
export const OUTPUT_VERSION = "1.3";
|
|
12
12
|
export async function analyzeProject(options = {}) {
|
|
13
13
|
const rootDir = path.resolve(options.rootDir ?? process.cwd());
|
|
14
14
|
const loadedConfig = await loadDepBrainConfig(rootDir, options.configPath);
|
|
@@ -309,6 +309,8 @@ function mapRiskIssues(issues) {
|
|
|
309
309
|
confidence: normalizeConfidence(issue.confidence),
|
|
310
310
|
reasonCodes: normalizeStringArray(issue.reasonCodes),
|
|
311
311
|
explanation: normalizeStringArray(issue.explanation),
|
|
312
|
+
trustScore: normalizeTrustScore(issue.meta?.trustScore),
|
|
313
|
+
riskFactors: normalizeRiskFactors(issue.meta?.riskFactors),
|
|
312
314
|
recommendation: buildRiskRecommendation(issue)
|
|
313
315
|
}));
|
|
314
316
|
}
|
|
@@ -371,11 +373,14 @@ function buildOutdatedRecommendation(issue) {
|
|
|
371
373
|
function buildRiskRecommendation(issue) {
|
|
372
374
|
const reasons = normalizeStringArray(issue.explanation);
|
|
373
375
|
const confidence = normalizeConfidence(issue.confidence);
|
|
376
|
+
const trustScore = normalizeTrustScore(issue.meta?.trustScore);
|
|
374
377
|
return {
|
|
375
378
|
action: "review",
|
|
376
|
-
priority: confidence >= 0.79 ? "high" : "medium",
|
|
379
|
+
priority: trustScore === "low" || confidence >= 0.79 ? "high" : "medium",
|
|
377
380
|
safety: "caution",
|
|
378
|
-
summary:
|
|
381
|
+
summary: trustScore === "low"
|
|
382
|
+
? "Low trust package; review whether to replace, pin, or monitor it closely."
|
|
383
|
+
: "Review package trust signals and decide whether to keep, replace, or monitor it.",
|
|
379
384
|
reasons
|
|
380
385
|
};
|
|
381
386
|
}
|
|
@@ -414,17 +419,24 @@ function buildTopIssues(input) {
|
|
|
414
419
|
priority: item.recommendation.priority,
|
|
415
420
|
confidence: item.confidence,
|
|
416
421
|
summary: item.recommendation.summary,
|
|
422
|
+
trustScore: item.trustScore,
|
|
417
423
|
recommendation: item.recommendation
|
|
418
424
|
}))
|
|
419
425
|
];
|
|
420
426
|
return issues
|
|
421
|
-
.sort((left, right) => comparePriority(right.priority, left.priority) ||
|
|
427
|
+
.sort((left, right) => comparePriority(right.priority, left.priority) ||
|
|
428
|
+
compareTrustScore(right.trustScore, left.trustScore) ||
|
|
429
|
+
right.confidence - left.confidence)
|
|
422
430
|
.slice(0, 5);
|
|
423
431
|
}
|
|
424
432
|
function comparePriority(left, right) {
|
|
425
433
|
const rank = { high: 3, medium: 2, low: 1 };
|
|
426
434
|
return rank[left] - rank[right];
|
|
427
435
|
}
|
|
436
|
+
function compareTrustScore(left, right) {
|
|
437
|
+
const rank = { low: 3, medium: 2, high: 1, undefined: 0 };
|
|
438
|
+
return rank[left ?? "undefined"] - rank[right ?? "undefined"];
|
|
439
|
+
}
|
|
428
440
|
function normalizeConfidence(value) {
|
|
429
441
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
430
442
|
return 0.5;
|
|
@@ -437,6 +449,36 @@ function normalizeStringArray(value) {
|
|
|
437
449
|
}
|
|
438
450
|
return value.filter((entry) => typeof entry === "string");
|
|
439
451
|
}
|
|
452
|
+
function normalizeTrustScore(value) {
|
|
453
|
+
return value === "high" || value === "medium" || value === "low"
|
|
454
|
+
? value
|
|
455
|
+
: "medium";
|
|
456
|
+
}
|
|
457
|
+
function normalizeRiskFactors(value) {
|
|
458
|
+
if (!value || typeof value !== "object") {
|
|
459
|
+
return {
|
|
460
|
+
daysSincePublish: null,
|
|
461
|
+
downloads: null,
|
|
462
|
+
maintainersCount: null,
|
|
463
|
+
versionCount: null,
|
|
464
|
+
recentReleaseCount: null,
|
|
465
|
+
hasRepository: false,
|
|
466
|
+
dependencyType: "unknown"
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const factors = value;
|
|
470
|
+
return {
|
|
471
|
+
daysSincePublish: typeof factors.daysSincePublish === "number" ? factors.daysSincePublish : null,
|
|
472
|
+
downloads: typeof factors.downloads === "number" ? factors.downloads : null,
|
|
473
|
+
maintainersCount: typeof factors.maintainersCount === "number" ? factors.maintainersCount : null,
|
|
474
|
+
versionCount: typeof factors.versionCount === "number" ? factors.versionCount : null,
|
|
475
|
+
recentReleaseCount: typeof factors.recentReleaseCount === "number" ? factors.recentReleaseCount : null,
|
|
476
|
+
hasRepository: factors.hasRepository === true,
|
|
477
|
+
dependencyType: factors.dependencyType === "dependencies" || factors.dependencyType === "devDependencies"
|
|
478
|
+
? factors.dependencyType
|
|
479
|
+
: "unknown"
|
|
480
|
+
};
|
|
481
|
+
}
|
|
440
482
|
function buildScoreBreakdown(counts, config) {
|
|
441
483
|
return {
|
|
442
484
|
baseScore: 100,
|
|
@@ -3,7 +3,7 @@ export function renderConsoleReport(result) {
|
|
|
3
3
|
if (result.topIssues.length > 0) {
|
|
4
4
|
lines.push("Top Issues:");
|
|
5
5
|
for (const item of result.topIssues) {
|
|
6
|
-
lines.push(`- [${item.priority.toUpperCase()}] ${item.kind} ${item.name}${item.package ? ` [${item.package}]` : ""} | confidence ${Math.round(item.confidence * 100)}% | ${item.summary}`);
|
|
6
|
+
lines.push(`- [${item.priority.toUpperCase()}] ${item.kind} ${item.name}${item.package ? ` [${item.package}]` : ""}${item.trustScore ? ` | trust ${item.trustScore.toUpperCase()}` : ""} | confidence ${Math.round(item.confidence * 100)}% | ${item.summary}`);
|
|
7
7
|
}
|
|
8
8
|
lines.push("");
|
|
9
9
|
}
|
|
@@ -31,8 +31,8 @@ export function renderConsoleReport(result) {
|
|
|
31
31
|
? `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}] [${item.package}]`
|
|
32
32
|
: `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}]`, item.confidence, item.explanation, item.recommendation)));
|
|
33
33
|
appendSection(lines, "Risky dependencies", result.risks.map((item) => formatEntry(item.package
|
|
34
|
-
? `${item.name}: ${item.reasons.join("; ")} [${item.package}]`
|
|
35
|
-
: `${item.name}: ${item.reasons.join("; ")}`, item.confidence, item.explanation, item.recommendation)));
|
|
34
|
+
? `${item.name}: ${item.reasons.join("; ")} [${item.package}] [trust ${item.trustScore.toUpperCase()}]`
|
|
35
|
+
: `${item.name}: ${item.reasons.join("; ")} [trust ${item.trustScore.toUpperCase()}]`, item.confidence, item.explanation, item.recommendation)));
|
|
36
36
|
appendSection(lines, "Policy reasons", result.policy.reasons);
|
|
37
37
|
if (result.suggestions.length > 0) {
|
|
38
38
|
lines.push("");
|
|
@@ -5,7 +5,7 @@ export function renderMarkdownReport(result) {
|
|
|
5
5
|
if (result.topIssues.length > 0) {
|
|
6
6
|
lines.push("## Top Issues");
|
|
7
7
|
for (const item of result.topIssues) {
|
|
8
|
-
lines.push(`- **${item.priority.toUpperCase()}** ${item.kind} \`${item.name}\`${item.package ? ` [${item.package}]` : ""} | confidence ${Math.round(item.confidence * 100)}% | ${item.summary}`);
|
|
8
|
+
lines.push(`- **${item.priority.toUpperCase()}** ${item.kind} \`${item.name}\`${item.package ? ` [${item.package}]` : ""}${item.trustScore ? ` | trust ${item.trustScore.toUpperCase()}` : ""} | confidence ${Math.round(item.confidence * 100)}% | ${item.summary}`);
|
|
9
9
|
}
|
|
10
10
|
lines.push("");
|
|
11
11
|
}
|
|
@@ -35,8 +35,8 @@ export function renderMarkdownReport(result) {
|
|
|
35
35
|
? `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}] [${item.package}]`
|
|
36
36
|
: `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}]`, item.confidence, item.explanation, item.recommendation)));
|
|
37
37
|
appendSection(lines, "Risky dependencies", result.risks.map((item) => formatEntry(item.package
|
|
38
|
-
? `${item.name}: ${item.reasons.join("; ")} [${item.package}]`
|
|
39
|
-
: `${item.name}: ${item.reasons.join("; ")}`, item.confidence, item.explanation, item.recommendation)));
|
|
38
|
+
? `${item.name}: ${item.reasons.join("; ")} [${item.package}] [trust ${item.trustScore.toUpperCase()}]`
|
|
39
|
+
: `${item.name}: ${item.reasons.join("; ")} [trust ${item.trustScore.toUpperCase()}]`, item.confidence, item.explanation, item.recommendation)));
|
|
40
40
|
appendSection(lines, "Policy reasons", result.policy.reasons);
|
|
41
41
|
if (result.suggestions.length > 0) {
|
|
42
42
|
lines.push("## Suggestions");
|
package/dist/utils/npm-api.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export interface PackageMetadata {
|
|
|
3
3
|
repository: string | null;
|
|
4
4
|
downloads: number | null;
|
|
5
5
|
daysSincePublish: number | null;
|
|
6
|
+
maintainersCount: number | null;
|
|
7
|
+
versionCount: number | null;
|
|
8
|
+
recentReleaseCount: number | null;
|
|
6
9
|
}
|
|
7
10
|
export declare function getLatestVersion(name: string): Promise<string | null>;
|
|
8
11
|
export declare function getPackageMetadata(name: string): Promise<PackageMetadata | null>;
|
package/dist/utils/npm-api.js
CHANGED
|
@@ -39,14 +39,35 @@ async function fetchPackageMetadata(name) {
|
|
|
39
39
|
const repository = typeof packageJson.repository === "string"
|
|
40
40
|
? packageJson.repository
|
|
41
41
|
: packageJson.repository?.url ?? null;
|
|
42
|
+
const maintainersCount = Array.isArray(packageJson.maintainers)
|
|
43
|
+
? packageJson.maintainers.length
|
|
44
|
+
: null;
|
|
45
|
+
const versionCount = packageJson.versions
|
|
46
|
+
? Object.keys(packageJson.versions).length
|
|
47
|
+
: null;
|
|
48
|
+
const recentReleaseCount = countRecentReleases(packageJson.time ?? {});
|
|
42
49
|
return {
|
|
43
50
|
latestVersion,
|
|
44
51
|
repository,
|
|
45
52
|
downloads: downloadsJson.downloads ?? null,
|
|
46
|
-
daysSincePublish
|
|
53
|
+
daysSincePublish,
|
|
54
|
+
maintainersCount,
|
|
55
|
+
versionCount,
|
|
56
|
+
recentReleaseCount
|
|
47
57
|
};
|
|
48
58
|
}
|
|
49
59
|
catch {
|
|
50
60
|
return null;
|
|
51
61
|
}
|
|
52
62
|
}
|
|
63
|
+
function countRecentReleases(time) {
|
|
64
|
+
const values = Object.entries(time)
|
|
65
|
+
.filter(([key]) => key !== "created" && key !== "modified")
|
|
66
|
+
.map(([, value]) => new Date(value).getTime())
|
|
67
|
+
.filter((value) => Number.isFinite(value));
|
|
68
|
+
if (values.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
72
|
+
return values.filter((value) => value >= thirtyDaysAgo).length;
|
|
73
|
+
}
|