dep-brain 1.0.2 → 1.1.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 +9 -0
- package/README.md +34 -0
- package/action.yml +18 -0
- package/dist/cli.js +67 -2
- package/dist/core/analyzer.d.ts +2 -0
- package/dist/core/analyzer.js +38 -14
- package/dist/core/scorer.d.ts +7 -0
- package/dist/core/scorer.js +15 -6
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.1.0
|
|
6
|
+
|
|
7
|
+
- Added `--focus` modes for targeted duplicate, unused, outdated, risk, and health analysis.
|
|
8
|
+
- Added `--ci` for low-noise CI defaults focused on duplicate and runtime risk enforcement.
|
|
9
|
+
- Added `dep-brain init` to generate a starter `depbrain.config.json`.
|
|
10
|
+
- Introduced capped health score deductions so large outdated/risk counts do not automatically collapse project health to `0/100`.
|
|
11
|
+
- Added GitHub Action inputs for `focus` and `ci`.
|
|
12
|
+
- Added regression coverage for focused analysis and capped scoring.
|
|
13
|
+
|
|
5
14
|
## 1.0.2
|
|
6
15
|
|
|
7
16
|
- Treated npm `overrides` entries as intentional version pins so direct override packages are not flagged as unused.
|
package/README.md
CHANGED
|
@@ -50,6 +50,9 @@ The long-term goal is not just to list problems, but to answer:
|
|
|
50
50
|
- SARIF output via `--sarif`
|
|
51
51
|
- Ranked top issues via `--top`
|
|
52
52
|
- Baseline mode via `--baseline`
|
|
53
|
+
- Focused analysis via `--focus`
|
|
54
|
+
- Low-noise CI defaults via `--ci`
|
|
55
|
+
- Starter config generation via `dep-brain init`
|
|
53
56
|
- Reusable GitHub Action via `action.yml`
|
|
54
57
|
- Library entrypoint for programmatic use
|
|
55
58
|
|
|
@@ -70,10 +73,13 @@ npx dep-brain analyze ./path-to-project --fail-on-unused --json
|
|
|
70
73
|
npx dep-brain analyze --md > depbrain.md
|
|
71
74
|
npx dep-brain analyze --json --out depbrain.json
|
|
72
75
|
npx dep-brain analyze --sarif --out depbrain.sarif
|
|
76
|
+
npx dep-brain analyze --focus duplicates
|
|
77
|
+
npx dep-brain analyze --ci
|
|
73
78
|
npx dep-brain analyze --baseline depbrain-baseline.json
|
|
74
79
|
npx dep-brain analyze --baseline depbrain-baseline.json --min-score 90 --fail-on-risks
|
|
75
80
|
npx dep-brain report --from depbrain.json --md --out depbrain.md
|
|
76
81
|
|
|
82
|
+
dep-brain init
|
|
77
83
|
dep-brain config
|
|
78
84
|
dep-brain config --config depbrain.config.json
|
|
79
85
|
|
|
@@ -82,6 +88,34 @@ dep-brain analyze --help
|
|
|
82
88
|
dep-brain --version
|
|
83
89
|
```
|
|
84
90
|
|
|
91
|
+
## Focus Modes
|
|
92
|
+
|
|
93
|
+
Use `--focus` when you want a targeted signal instead of a full report:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
dep-brain analyze --focus duplicates
|
|
97
|
+
dep-brain analyze --focus health
|
|
98
|
+
dep-brain analyze --focus risks
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Supported values are `all`, `health`, `duplicates`, `unused`, `outdated`, and `risks`.
|
|
102
|
+
|
|
103
|
+
## CI Preset
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
dep-brain analyze --ci
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The CI preset applies low-noise defaults: a minimum score of `70`, failure on duplicates, and failure on risky dependencies.
|
|
110
|
+
|
|
111
|
+
## Config Init
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
dep-brain init
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Creates a starter `depbrain.config.json` with practical defaults for CI and common TypeScript/NestJS tooling.
|
|
118
|
+
|
|
85
119
|
## Workspaces
|
|
86
120
|
|
|
87
121
|
If the root `package.json` defines `workspaces`, `dep-brain` analyzes each workspace package and reports per-package results. Aggregated counts are still shown at the top-level summary.
|
package/action.yml
CHANGED
|
@@ -23,6 +23,14 @@ inputs:
|
|
|
23
23
|
description: Optional baseline JSON report used to ignore existing findings.
|
|
24
24
|
required: false
|
|
25
25
|
default: ""
|
|
26
|
+
focus:
|
|
27
|
+
description: Analysis focus. Use all, health, duplicates, unused, outdated, or risks.
|
|
28
|
+
required: false
|
|
29
|
+
default: "all"
|
|
30
|
+
ci:
|
|
31
|
+
description: Apply low-noise CI defaults.
|
|
32
|
+
required: false
|
|
33
|
+
default: "false"
|
|
26
34
|
min-score:
|
|
27
35
|
description: Minimum project health score required to pass.
|
|
28
36
|
required: false
|
|
@@ -55,6 +63,8 @@ runs:
|
|
|
55
63
|
INPUT_OUT: ${{ inputs.out }}
|
|
56
64
|
INPUT_CONFIG: ${{ inputs.config }}
|
|
57
65
|
INPUT_BASELINE: ${{ inputs.baseline }}
|
|
66
|
+
INPUT_FOCUS: ${{ inputs.focus }}
|
|
67
|
+
INPUT_CI: ${{ inputs.ci }}
|
|
58
68
|
INPUT_MIN_SCORE: ${{ inputs.min-score }}
|
|
59
69
|
INPUT_FAIL_ON_UNUSED: ${{ inputs.fail-on-unused }}
|
|
60
70
|
INPUT_FAIL_ON_OUTDATED: ${{ inputs.fail-on-outdated }}
|
|
@@ -89,6 +99,14 @@ runs:
|
|
|
89
99
|
args+=("--baseline" "$INPUT_BASELINE")
|
|
90
100
|
fi
|
|
91
101
|
|
|
102
|
+
if [ "$INPUT_FOCUS" != "all" ]; then
|
|
103
|
+
args+=("--focus" "$INPUT_FOCUS")
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ "$INPUT_CI" = "true" ]; then
|
|
107
|
+
args+=("--ci")
|
|
108
|
+
fi
|
|
109
|
+
|
|
92
110
|
if [ -n "$INPUT_MIN_SCORE" ]; then
|
|
93
111
|
args+=("--min-score" "$INPUT_MIN_SCORE")
|
|
94
112
|
fi
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { renderConsoleReport } from "./reporters/console.js";
|
|
|
4
4
|
import { renderJsonReport } from "./reporters/json.js";
|
|
5
5
|
import { renderMarkdownReport } from "./reporters/markdown.js";
|
|
6
6
|
import { renderSarifReport } from "./reporters/sarif.js";
|
|
7
|
+
import { defaultConfig } from "./utils/config.js";
|
|
7
8
|
import { promises as fs } from "node:fs";
|
|
8
9
|
import path from "node:path";
|
|
9
10
|
async function main() {
|
|
@@ -91,6 +92,22 @@ async function main() {
|
|
|
91
92
|
return;
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
if (command === "init") {
|
|
96
|
+
try {
|
|
97
|
+
const outputPath = optionValues.get("--out") ?? "depbrain.config.json";
|
|
98
|
+
const resolvedOut = resolveUserPath(outputPath);
|
|
99
|
+
const config = buildStarterConfig();
|
|
100
|
+
await fs.writeFile(resolvedOut, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
101
|
+
console.log(`Created ${path.relative(process.cwd(), resolvedOut) || outputPath}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("Failed to create config.");
|
|
106
|
+
console.error(error);
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
94
111
|
console.error(`Unknown command: ${sanitizeForLog(command)}`);
|
|
95
112
|
printHelp();
|
|
96
113
|
process.exitCode = 1;
|
|
@@ -108,7 +125,8 @@ async function main() {
|
|
|
108
125
|
rootDir: targetPath,
|
|
109
126
|
configPath: optionValues.get("--config"),
|
|
110
127
|
config: cliConfig,
|
|
111
|
-
baseline
|
|
128
|
+
baseline,
|
|
129
|
+
focus: parseFocus(optionValues.get("--focus"))
|
|
112
130
|
});
|
|
113
131
|
let output;
|
|
114
132
|
if (flags.has("--json")) {
|
|
@@ -169,6 +187,11 @@ function buildCliConfig(flags, optionValues) {
|
|
|
169
187
|
if (flags.has("--fail-on-unused")) {
|
|
170
188
|
policy.failOnUnused = true;
|
|
171
189
|
}
|
|
190
|
+
if (flags.has("--ci")) {
|
|
191
|
+
policy.minScore = policy.minScore ?? 70;
|
|
192
|
+
policy.failOnDuplicates = true;
|
|
193
|
+
policy.failOnRisks = true;
|
|
194
|
+
}
|
|
172
195
|
return {
|
|
173
196
|
policy
|
|
174
197
|
};
|
|
@@ -186,9 +209,10 @@ function printHelp() {
|
|
|
186
209
|
console.log("Dependency Brain");
|
|
187
210
|
console.log("");
|
|
188
211
|
console.log("Usage:");
|
|
189
|
-
console.log(" dep-brain analyze [path] [--json] [--md] [--sarif] [--top] [--out path] [--config path] [--baseline path] [--min-score n] [--fail-on-risks]");
|
|
212
|
+
console.log(" dep-brain analyze [path] [--json] [--md] [--sarif] [--top] [--focus kind] [--ci] [--out path] [--config path] [--baseline path] [--min-score n] [--fail-on-risks]");
|
|
190
213
|
console.log(" dep-brain report --from <file> [--md] [--json] [--sarif] [--top] [--out path]");
|
|
191
214
|
console.log(" dep-brain config [path] [--config path]");
|
|
215
|
+
console.log(" dep-brain init [--out depbrain.config.json]");
|
|
192
216
|
console.log(" dep-brain help");
|
|
193
217
|
console.log(" dep-brain --version");
|
|
194
218
|
console.log("");
|
|
@@ -197,6 +221,8 @@ function printHelp() {
|
|
|
197
221
|
console.log(" --md Output Markdown report");
|
|
198
222
|
console.log(" --sarif Output SARIF format for Code Scanning");
|
|
199
223
|
console.log(" --top Output the ranked top issues only");
|
|
224
|
+
console.log(" --focus <kind> Run all, health, duplicates, unused, outdated, or risks");
|
|
225
|
+
console.log(" --ci Apply low-noise CI defaults");
|
|
200
226
|
console.log(" --config <path> Path to depbrain.config.json");
|
|
201
227
|
console.log(" --baseline <path> Ignore findings already present in a baseline JSON report");
|
|
202
228
|
console.log(" --from <file> Read analysis JSON from file");
|
|
@@ -209,6 +235,45 @@ function printHelp() {
|
|
|
209
235
|
console.log(" --help Show this help output");
|
|
210
236
|
console.log(" --version Show CLI version");
|
|
211
237
|
}
|
|
238
|
+
function parseFocus(value) {
|
|
239
|
+
if (value === "duplicates" ||
|
|
240
|
+
value === "unused" ||
|
|
241
|
+
value === "outdated" ||
|
|
242
|
+
value === "risks" ||
|
|
243
|
+
value === "health" ||
|
|
244
|
+
value === "all") {
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
return "all";
|
|
248
|
+
}
|
|
249
|
+
function buildStarterConfig() {
|
|
250
|
+
return {
|
|
251
|
+
...defaultConfig,
|
|
252
|
+
ignore: {
|
|
253
|
+
...defaultConfig.ignore,
|
|
254
|
+
unused: [
|
|
255
|
+
"@nestjs/platform-express",
|
|
256
|
+
"reflect-metadata",
|
|
257
|
+
"source-map-support",
|
|
258
|
+
"ts-loader",
|
|
259
|
+
"ts-node",
|
|
260
|
+
"tsconfig-paths"
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
policy: {
|
|
264
|
+
...defaultConfig.policy,
|
|
265
|
+
minScore: 70,
|
|
266
|
+
failOnDuplicates: true,
|
|
267
|
+
failOnRisks: true
|
|
268
|
+
},
|
|
269
|
+
scoring: {
|
|
270
|
+
duplicateWeight: 8,
|
|
271
|
+
outdatedWeight: 1,
|
|
272
|
+
unusedWeight: 2,
|
|
273
|
+
riskWeight: 4
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
212
277
|
async function loadPackageVersion() {
|
|
213
278
|
try {
|
|
214
279
|
const pkgPath = new URL("../package.json", import.meta.url);
|
package/dist/core/analyzer.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ export interface AnalysisOptions {
|
|
|
4
4
|
configPath?: string;
|
|
5
5
|
config?: DepBrainConfigOverrides;
|
|
6
6
|
baseline?: DepBrainBaseline;
|
|
7
|
+
focus?: AnalysisFocus;
|
|
7
8
|
}
|
|
9
|
+
export type AnalysisFocus = "all" | "duplicates" | "unused" | "outdated" | "risks" | "health";
|
|
8
10
|
export interface DepBrainBaseline {
|
|
9
11
|
duplicates?: Array<Partial<Pick<DuplicateDependency, "name">>>;
|
|
10
12
|
unused?: Array<Partial<Pick<UnusedDependency, "name" | "section" | "package">>>;
|
package/dist/core/analyzer.js
CHANGED
|
@@ -6,27 +6,30 @@ import { runUnusedCheck } from "../checks/unused.js";
|
|
|
6
6
|
import { loadDepBrainConfig } from "../utils/config.js";
|
|
7
7
|
import { findWorkspacePackages } from "../utils/workspaces.js";
|
|
8
8
|
import { buildDependencyGraph } from "./graph-builder.js";
|
|
9
|
-
import { calculateHealthScore } from "./scorer.js";
|
|
9
|
+
import { calculateHealthScore, calculateScoreDeductions } from "./scorer.js";
|
|
10
10
|
import { buildAnalysisContext } from "./context.js";
|
|
11
11
|
export const OUTPUT_VERSION = "1.4";
|
|
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);
|
|
15
15
|
const config = mergeConfig(loadedConfig, options.config);
|
|
16
|
+
const focus = options.focus ?? "all";
|
|
16
17
|
const workspaces = await findWorkspacePackages(rootDir);
|
|
17
18
|
if (workspaces.length === 0) {
|
|
18
19
|
return analyzeSingleProject(rootDir, config, {
|
|
19
|
-
baseline: options.baseline
|
|
20
|
+
baseline: options.baseline,
|
|
21
|
+
focus
|
|
20
22
|
});
|
|
21
23
|
}
|
|
22
24
|
const rootGraph = await buildDependencyGraph(rootDir);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const duplicates = shouldRunCheck("duplicate", focus)
|
|
26
|
+
? mapDuplicateIssues(filterIssues((await runDuplicateCheck(rootGraph)).issues, "duplicates", config))
|
|
27
|
+
: [];
|
|
26
28
|
const packages = [];
|
|
27
29
|
for (const workspace of workspaces) {
|
|
28
30
|
const result = await analyzeSingleProject(workspace.rootDir, config, {
|
|
29
|
-
packageName: workspace.name
|
|
31
|
+
packageName: workspace.name,
|
|
32
|
+
focus
|
|
30
33
|
});
|
|
31
34
|
packages.push({ ...result, name: workspace.name });
|
|
32
35
|
}
|
|
@@ -163,7 +166,7 @@ function evaluatePolicy(summary, config) {
|
|
|
163
166
|
}
|
|
164
167
|
async function analyzeSingleProject(rootDir, config, options = {}) {
|
|
165
168
|
const context = await buildAnalysisContext(rootDir, config);
|
|
166
|
-
const results = await runChecks(context);
|
|
169
|
+
const results = await runChecks(context, options.focus ?? "all");
|
|
167
170
|
const issueGroups = normalizeIssues(results, config);
|
|
168
171
|
const duplicates = mapDuplicateIssues(issueGroups.duplicates);
|
|
169
172
|
const unused = mapUnusedIssues(issueGroups.unused);
|
|
@@ -255,7 +258,7 @@ function shouldIgnorePackage(name, bucket, config) {
|
|
|
255
258
|
}
|
|
256
259
|
});
|
|
257
260
|
}
|
|
258
|
-
async function runChecks(context) {
|
|
261
|
+
async function runChecks(context, focus) {
|
|
259
262
|
const checks = [
|
|
260
263
|
{
|
|
261
264
|
name: "duplicate",
|
|
@@ -276,10 +279,21 @@ async function runChecks(context) {
|
|
|
276
279
|
];
|
|
277
280
|
const results = [];
|
|
278
281
|
for (const check of checks) {
|
|
279
|
-
|
|
282
|
+
if (shouldRunCheck(check.name, focus)) {
|
|
283
|
+
results.push(await check.run());
|
|
284
|
+
}
|
|
280
285
|
}
|
|
281
286
|
return results;
|
|
282
287
|
}
|
|
288
|
+
function shouldRunCheck(checkName, focus) {
|
|
289
|
+
if (focus === "all" || focus === "health") {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return ((focus === "duplicates" && checkName === "duplicate") ||
|
|
293
|
+
(focus === "unused" && checkName === "unused") ||
|
|
294
|
+
(focus === "outdated" && checkName === "outdated") ||
|
|
295
|
+
(focus === "risks" && checkName === "risk"));
|
|
296
|
+
}
|
|
283
297
|
function normalizeIssues(results, config) {
|
|
284
298
|
const map = new Map();
|
|
285
299
|
for (const result of results) {
|
|
@@ -603,12 +617,22 @@ function normalizeWorkspaceUsage(value) {
|
|
|
603
617
|
.filter((entry) => entry !== null);
|
|
604
618
|
}
|
|
605
619
|
function buildScoreBreakdown(counts, config) {
|
|
620
|
+
const deductions = calculateScoreDeductions({
|
|
621
|
+
duplicates: counts.duplicates,
|
|
622
|
+
outdated: counts.outdated,
|
|
623
|
+
unused: counts.unused,
|
|
624
|
+
risks: counts.risks,
|
|
625
|
+
duplicateWeight: config.scoring.duplicateWeight,
|
|
626
|
+
outdatedWeight: config.scoring.outdatedWeight,
|
|
627
|
+
unusedWeight: config.scoring.unusedWeight,
|
|
628
|
+
riskWeight: config.scoring.riskWeight
|
|
629
|
+
});
|
|
606
630
|
return {
|
|
607
|
-
baseScore:
|
|
608
|
-
duplicates:
|
|
609
|
-
outdated:
|
|
610
|
-
unused:
|
|
611
|
-
risks:
|
|
631
|
+
baseScore: deductions.baseScore,
|
|
632
|
+
duplicates: deductions.duplicates,
|
|
633
|
+
outdated: deductions.outdated,
|
|
634
|
+
unused: deductions.unused,
|
|
635
|
+
risks: deductions.risks,
|
|
612
636
|
weights: {
|
|
613
637
|
duplicateWeight: config.scoring.duplicateWeight,
|
|
614
638
|
outdatedWeight: config.scoring.outdatedWeight,
|
package/dist/core/scorer.d.ts
CHANGED
|
@@ -9,3 +9,10 @@ export interface ScoreInputs {
|
|
|
9
9
|
riskWeight?: number;
|
|
10
10
|
}
|
|
11
11
|
export declare function calculateHealthScore(inputs: ScoreInputs): number;
|
|
12
|
+
export declare function calculateScoreDeductions(inputs: ScoreInputs): {
|
|
13
|
+
baseScore: number;
|
|
14
|
+
duplicates: number;
|
|
15
|
+
outdated: number;
|
|
16
|
+
unused: number;
|
|
17
|
+
risks: number;
|
|
18
|
+
};
|
package/dist/core/scorer.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
export function calculateHealthScore(inputs) {
|
|
2
|
+
const breakdown = calculateScoreDeductions(inputs);
|
|
3
|
+
return Math.max(0, breakdown.baseScore -
|
|
4
|
+
breakdown.duplicates -
|
|
5
|
+
breakdown.outdated -
|
|
6
|
+
breakdown.unused -
|
|
7
|
+
breakdown.risks);
|
|
8
|
+
}
|
|
9
|
+
export function calculateScoreDeductions(inputs) {
|
|
2
10
|
const duplicateWeight = inputs.duplicateWeight ?? 5;
|
|
3
11
|
const outdatedWeight = inputs.outdatedWeight ?? 3;
|
|
4
12
|
const unusedWeight = inputs.unusedWeight ?? 4;
|
|
5
13
|
const riskWeight = inputs.riskWeight ?? 10;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
inputs.
|
|
9
|
-
inputs.
|
|
10
|
-
inputs.
|
|
11
|
-
|
|
14
|
+
return {
|
|
15
|
+
baseScore: 100,
|
|
16
|
+
duplicates: Math.min(35, inputs.duplicates * duplicateWeight),
|
|
17
|
+
outdated: Math.min(25, inputs.outdated * outdatedWeight),
|
|
18
|
+
unused: Math.min(20, inputs.unused * unusedWeight),
|
|
19
|
+
risks: Math.min(30, inputs.risks * riskWeight)
|
|
20
|
+
};
|
|
12
21
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { analyzeProject } from "./core/analyzer.js";
|
|
2
|
-
export type { AnalysisOptions, AnalysisResult, DepBrainBaseline, DuplicateDependency, OutdatedDependency, PolicyResult, PackageAnalysisResult, Recommendation, RiskFactors, ScoreBreakdown, RiskDependency, TopIssue, TrustScore, UnusedDependency, WorkspaceDependencyUsage, WorkspaceOwnershipSummary } from "./core/analyzer.js";
|
|
2
|
+
export type { AnalysisOptions, AnalysisFocus, AnalysisResult, DepBrainBaseline, DuplicateDependency, OutdatedDependency, PolicyResult, PackageAnalysisResult, Recommendation, RiskFactors, ScoreBreakdown, RiskDependency, TopIssue, TrustScore, UnusedDependency, WorkspaceDependencyUsage, WorkspaceOwnershipSummary } from "./core/analyzer.js";
|
|
3
3
|
export { OUTPUT_VERSION } from "./core/analyzer.js";
|
|
4
4
|
export type { AnalysisContext, CheckResult, Issue } from "./core/types.js";
|
|
5
5
|
export type { DepBrainConfig, DepBrainConfigOverrides } from "./utils/config.js";
|