dep-brain 1.1.0 → 1.2.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/README.md +22 -0
- package/depbrain.config.json +15 -0
- package/depbrain.config.schema.json +31 -0
- package/depbrain.output.schema.json +5 -1
- package/dist/core/analyzer.d.ts +2 -0
- package/dist/core/analyzer.js +24 -2
- package/dist/core/plugin-manager.d.ts +20 -0
- package/dist/core/plugin-manager.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/utils/config.d.ts +19 -0
- package/dist/utils/config.js +33 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -238,6 +238,21 @@ Create a `depbrain.config.json` file in the project root:
|
|
|
238
238
|
"report": {
|
|
239
239
|
"maxSuggestions": 3
|
|
240
240
|
},
|
|
241
|
+
"plugins": {
|
|
242
|
+
"enabled": [],
|
|
243
|
+
"paths": []
|
|
244
|
+
},
|
|
245
|
+
"risk": {
|
|
246
|
+
"transitiveBloatThreshold": 50,
|
|
247
|
+
"typosquattingDistanceThreshold": 2
|
|
248
|
+
},
|
|
249
|
+
"dashboard": {
|
|
250
|
+
"outputPath": "depbrain-dashboard.html"
|
|
251
|
+
},
|
|
252
|
+
"notifications": {
|
|
253
|
+
"slackWebhookEnv": "DEPBRAIN_SLACK_WEBHOOK_URL",
|
|
254
|
+
"discordWebhookEnv": "DEPBRAIN_DISCORD_WEBHOOK_URL"
|
|
255
|
+
},
|
|
241
256
|
"scoring": {
|
|
242
257
|
"duplicateWeight": 5,
|
|
243
258
|
"outdatedWeight": 1,
|
|
@@ -264,6 +279,13 @@ Supported sections:
|
|
|
264
279
|
- `policy.failOnOutdated`
|
|
265
280
|
- `policy.failOnRisks`
|
|
266
281
|
- `report.maxSuggestions`
|
|
282
|
+
- `plugins.enabled`
|
|
283
|
+
- `plugins.paths`
|
|
284
|
+
- `risk.transitiveBloatThreshold`
|
|
285
|
+
- `risk.typosquattingDistanceThreshold`
|
|
286
|
+
- `dashboard.outputPath`
|
|
287
|
+
- `notifications.slackWebhookEnv`
|
|
288
|
+
- `notifications.discordWebhookEnv`
|
|
267
289
|
- `scoring.duplicateWeight`
|
|
268
290
|
- `scoring.outdatedWeight`
|
|
269
291
|
- `scoring.unusedWeight`
|
package/depbrain.config.json
CHANGED
|
@@ -19,6 +19,21 @@
|
|
|
19
19
|
"report": {
|
|
20
20
|
"maxSuggestions": 5
|
|
21
21
|
},
|
|
22
|
+
"plugins": {
|
|
23
|
+
"enabled": [],
|
|
24
|
+
"paths": []
|
|
25
|
+
},
|
|
26
|
+
"risk": {
|
|
27
|
+
"transitiveBloatThreshold": 50,
|
|
28
|
+
"typosquattingDistanceThreshold": 2
|
|
29
|
+
},
|
|
30
|
+
"dashboard": {
|
|
31
|
+
"outputPath": "depbrain-dashboard.html"
|
|
32
|
+
},
|
|
33
|
+
"notifications": {
|
|
34
|
+
"slackWebhookEnv": "DEPBRAIN_SLACK_WEBHOOK_URL",
|
|
35
|
+
"discordWebhookEnv": "DEPBRAIN_DISCORD_WEBHOOK_URL"
|
|
36
|
+
},
|
|
22
37
|
"scoring": {
|
|
23
38
|
"duplicateWeight": 5,
|
|
24
39
|
"outdatedWeight": 3,
|
|
@@ -36,6 +36,37 @@
|
|
|
36
36
|
"maxSuggestions": { "type": "number" }
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
|
+
"plugins": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"properties": {
|
|
43
|
+
"enabled": { "type": "array", "items": { "type": "string" } },
|
|
44
|
+
"paths": { "type": "array", "items": { "type": "string" } }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"risk": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"properties": {
|
|
51
|
+
"transitiveBloatThreshold": { "type": "number" },
|
|
52
|
+
"typosquattingDistanceThreshold": { "type": "number" }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"dashboard": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"additionalProperties": false,
|
|
58
|
+
"properties": {
|
|
59
|
+
"outputPath": { "type": "string" }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"notifications": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"properties": {
|
|
66
|
+
"slackWebhookEnv": { "type": "string" },
|
|
67
|
+
"discordWebhookEnv": { "type": "string" }
|
|
68
|
+
}
|
|
69
|
+
},
|
|
39
70
|
"scoring": {
|
|
40
71
|
"type": "object",
|
|
41
72
|
"additionalProperties": false,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"title": "Dependency Brain Analysis Output",
|
|
4
4
|
"type": "object",
|
|
5
|
-
"required": ["outputVersion", "rootDir", "score", "scoreBreakdown", "policy", "ownershipSummary", "duplicates", "unused", "outdated", "risks", "suggestions", "topIssues", "config"],
|
|
5
|
+
"required": ["outputVersion", "rootDir", "score", "scoreBreakdown", "policy", "ownershipSummary", "duplicates", "unused", "outdated", "risks", "suggestions", "topIssues", "extensions", "config"],
|
|
6
6
|
"additionalProperties": false,
|
|
7
7
|
"properties": {
|
|
8
8
|
"recommendation": {
|
|
@@ -175,6 +175,10 @@
|
|
|
175
175
|
}
|
|
176
176
|
},
|
|
177
177
|
"suggestions": { "type": "array", "items": { "type": "string" } },
|
|
178
|
+
"extensions": {
|
|
179
|
+
"type": "object",
|
|
180
|
+
"additionalProperties": true
|
|
181
|
+
},
|
|
178
182
|
"topIssues": {
|
|
179
183
|
"type": "array",
|
|
180
184
|
"items": {
|
package/dist/core/analyzer.d.ts
CHANGED
|
@@ -110,6 +110,7 @@ export interface AnalysisResult {
|
|
|
110
110
|
risks: RiskDependency[];
|
|
111
111
|
suggestions: string[];
|
|
112
112
|
topIssues: TopIssue[];
|
|
113
|
+
extensions: Record<string, unknown>;
|
|
113
114
|
config: DepBrainConfig;
|
|
114
115
|
packages?: PackageAnalysisResult[];
|
|
115
116
|
}
|
|
@@ -130,6 +131,7 @@ export interface PackageAnalysisResult {
|
|
|
130
131
|
risks: RiskDependency[];
|
|
131
132
|
suggestions: string[];
|
|
132
133
|
topIssues: TopIssue[];
|
|
134
|
+
extensions: Record<string, unknown>;
|
|
133
135
|
}
|
|
134
136
|
export declare const OUTPUT_VERSION = "1.4";
|
|
135
137
|
export interface ScoreBreakdown {
|
package/dist/core/analyzer.js
CHANGED
|
@@ -6,6 +6,7 @@ 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 { PluginManager } from "./plugin-manager.js";
|
|
9
10
|
import { calculateHealthScore, calculateScoreDeductions } from "./scorer.js";
|
|
10
11
|
import { buildAnalysisContext } from "./context.js";
|
|
11
12
|
export const OUTPUT_VERSION = "1.4";
|
|
@@ -14,12 +15,15 @@ export async function analyzeProject(options = {}) {
|
|
|
14
15
|
const loadedConfig = await loadDepBrainConfig(rootDir, options.configPath);
|
|
15
16
|
const config = mergeConfig(loadedConfig, options.config);
|
|
16
17
|
const focus = options.focus ?? "all";
|
|
18
|
+
const plugins = await PluginManager.load(rootDir, config);
|
|
19
|
+
await plugins.runPreScan({ rootDir, config });
|
|
17
20
|
const workspaces = await findWorkspacePackages(rootDir);
|
|
18
21
|
if (workspaces.length === 0) {
|
|
19
|
-
|
|
22
|
+
const result = await analyzeSingleProject(rootDir, config, {
|
|
20
23
|
baseline: options.baseline,
|
|
21
24
|
focus
|
|
22
25
|
});
|
|
26
|
+
return plugins.runPostScan(result);
|
|
23
27
|
}
|
|
24
28
|
const rootGraph = await buildDependencyGraph(rootDir);
|
|
25
29
|
const duplicates = shouldRunCheck("duplicate", focus)
|
|
@@ -79,7 +83,7 @@ export async function analyzeProject(options = {}) {
|
|
|
79
83
|
outdated: outdated.length,
|
|
80
84
|
risks: risks.length
|
|
81
85
|
}, config);
|
|
82
|
-
|
|
86
|
+
const result = {
|
|
83
87
|
outputVersion: OUTPUT_VERSION,
|
|
84
88
|
rootDir,
|
|
85
89
|
score,
|
|
@@ -102,9 +106,11 @@ export async function analyzeProject(options = {}) {
|
|
|
102
106
|
outdated,
|
|
103
107
|
risks
|
|
104
108
|
}),
|
|
109
|
+
extensions: {},
|
|
105
110
|
config,
|
|
106
111
|
packages
|
|
107
112
|
};
|
|
113
|
+
return plugins.runPostScan(result);
|
|
108
114
|
}
|
|
109
115
|
function mergeConfig(base, overrides) {
|
|
110
116
|
if (!overrides) {
|
|
@@ -131,6 +137,21 @@ function mergeConfig(base, overrides) {
|
|
|
131
137
|
report: {
|
|
132
138
|
maxSuggestions: overrides.report?.maxSuggestions ?? base.report.maxSuggestions
|
|
133
139
|
},
|
|
140
|
+
plugins: {
|
|
141
|
+
enabled: overrides.plugins?.enabled ?? base.plugins.enabled,
|
|
142
|
+
paths: overrides.plugins?.paths ?? base.plugins.paths
|
|
143
|
+
},
|
|
144
|
+
risk: {
|
|
145
|
+
transitiveBloatThreshold: overrides.risk?.transitiveBloatThreshold ?? base.risk.transitiveBloatThreshold,
|
|
146
|
+
typosquattingDistanceThreshold: overrides.risk?.typosquattingDistanceThreshold ?? base.risk.typosquattingDistanceThreshold
|
|
147
|
+
},
|
|
148
|
+
dashboard: {
|
|
149
|
+
outputPath: overrides.dashboard?.outputPath ?? base.dashboard.outputPath
|
|
150
|
+
},
|
|
151
|
+
notifications: {
|
|
152
|
+
slackWebhookEnv: overrides.notifications?.slackWebhookEnv ?? base.notifications.slackWebhookEnv,
|
|
153
|
+
discordWebhookEnv: overrides.notifications?.discordWebhookEnv ?? base.notifications.discordWebhookEnv
|
|
154
|
+
},
|
|
134
155
|
scoring: {
|
|
135
156
|
duplicateWeight: overrides.scoring?.duplicateWeight ?? base.scoring.duplicateWeight,
|
|
136
157
|
outdatedWeight: overrides.scoring?.outdatedWeight ?? base.scoring.outdatedWeight,
|
|
@@ -238,6 +259,7 @@ async function analyzeSingleProject(rootDir, config, options = {}) {
|
|
|
238
259
|
outdated: baselineFiltered.outdated,
|
|
239
260
|
risks: baselineFiltered.risks
|
|
240
261
|
}),
|
|
262
|
+
extensions: {},
|
|
241
263
|
config
|
|
242
264
|
};
|
|
243
265
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AnalysisResult } from "./analyzer.js";
|
|
2
|
+
import type { DepBrainConfig } from "../utils/config.js";
|
|
3
|
+
export interface ProjectContext {
|
|
4
|
+
rootDir: string;
|
|
5
|
+
config: DepBrainConfig;
|
|
6
|
+
}
|
|
7
|
+
export interface DepBrainPlugin {
|
|
8
|
+
name: string;
|
|
9
|
+
preScan?: (context: ProjectContext) => Promise<void> | void;
|
|
10
|
+
postScan?: (result: AnalysisResult) => Promise<AnalysisResult | void> | AnalysisResult | void;
|
|
11
|
+
reportHook?: (result: AnalysisResult) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
|
|
12
|
+
cliCommands?: (cli: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare class PluginManager {
|
|
15
|
+
private readonly plugins;
|
|
16
|
+
private constructor();
|
|
17
|
+
static load(rootDir: string, config: DepBrainConfig): Promise<PluginManager>;
|
|
18
|
+
runPreScan(context: ProjectContext): Promise<void>;
|
|
19
|
+
runPostScan(result: AnalysisResult): Promise<AnalysisResult>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
export class PluginManager {
|
|
4
|
+
plugins;
|
|
5
|
+
constructor(plugins) {
|
|
6
|
+
this.plugins = plugins;
|
|
7
|
+
}
|
|
8
|
+
static async load(rootDir, config) {
|
|
9
|
+
const specs = [
|
|
10
|
+
...config.plugins.enabled.map((name) => `dep-brain-plugin-${name}`),
|
|
11
|
+
...config.plugins.paths
|
|
12
|
+
];
|
|
13
|
+
const plugins = [];
|
|
14
|
+
for (const spec of specs) {
|
|
15
|
+
const plugin = await loadPlugin(rootDir, spec);
|
|
16
|
+
if (plugin) {
|
|
17
|
+
plugins.push(plugin);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return new PluginManager(plugins);
|
|
21
|
+
}
|
|
22
|
+
async runPreScan(context) {
|
|
23
|
+
for (const plugin of this.plugins) {
|
|
24
|
+
await plugin.preScan?.(context);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async runPostScan(result) {
|
|
28
|
+
let current = result;
|
|
29
|
+
for (const plugin of this.plugins) {
|
|
30
|
+
const next = await plugin.postScan?.(current);
|
|
31
|
+
if (next) {
|
|
32
|
+
current = next;
|
|
33
|
+
}
|
|
34
|
+
const reportSection = await plugin.reportHook?.(current);
|
|
35
|
+
if (reportSection) {
|
|
36
|
+
current.extensions[plugin.name] = {
|
|
37
|
+
...(asRecord(current.extensions[plugin.name]) ?? {}),
|
|
38
|
+
...reportSection
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return current;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function loadPlugin(rootDir, spec) {
|
|
46
|
+
try {
|
|
47
|
+
const resolved = spec.startsWith(".") || path.isAbsolute(spec)
|
|
48
|
+
? path.resolve(rootDir, spec)
|
|
49
|
+
: spec;
|
|
50
|
+
const moduleUrl = path.isAbsolute(resolved) ? pathToFileURL(resolved).href : resolved;
|
|
51
|
+
const mod = await import(moduleUrl);
|
|
52
|
+
const exported = mod.default ?? mod.plugin ?? mod;
|
|
53
|
+
const candidate = typeof exported === "function" ? new exported() : exported;
|
|
54
|
+
return isPlugin(candidate) ? candidate : null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function isPlugin(value) {
|
|
61
|
+
return Boolean(value &&
|
|
62
|
+
typeof value === "object" &&
|
|
63
|
+
typeof value.name === "string");
|
|
64
|
+
}
|
|
65
|
+
function asRecord(value) {
|
|
66
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
67
|
+
? value
|
|
68
|
+
: null;
|
|
69
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { analyzeProject } from "./core/analyzer.js";
|
|
2
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
|
+
export { PluginManager } from "./core/plugin-manager.js";
|
|
5
|
+
export type { DepBrainPlugin, ProjectContext } from "./core/plugin-manager.js";
|
|
4
6
|
export type { AnalysisContext, CheckResult, Issue } from "./core/types.js";
|
|
5
7
|
export type { DepBrainConfig, DepBrainConfigOverrides } from "./utils/config.js";
|
|
6
8
|
export type { WorkspacePackage } from "./utils/workspaces.js";
|
package/dist/index.js
CHANGED
package/dist/utils/config.d.ts
CHANGED
|
@@ -19,6 +19,21 @@ export interface DepBrainConfig {
|
|
|
19
19
|
report: {
|
|
20
20
|
maxSuggestions: number;
|
|
21
21
|
};
|
|
22
|
+
plugins: {
|
|
23
|
+
enabled: string[];
|
|
24
|
+
paths: string[];
|
|
25
|
+
};
|
|
26
|
+
risk: {
|
|
27
|
+
transitiveBloatThreshold: number;
|
|
28
|
+
typosquattingDistanceThreshold: number;
|
|
29
|
+
};
|
|
30
|
+
dashboard: {
|
|
31
|
+
outputPath: string;
|
|
32
|
+
};
|
|
33
|
+
notifications: {
|
|
34
|
+
slackWebhookEnv: string;
|
|
35
|
+
discordWebhookEnv: string;
|
|
36
|
+
};
|
|
22
37
|
scoring: {
|
|
23
38
|
duplicateWeight: number;
|
|
24
39
|
outdatedWeight: number;
|
|
@@ -33,6 +48,10 @@ export interface DepBrainConfigOverrides {
|
|
|
33
48
|
ignore?: Partial<DepBrainConfig["ignore"]>;
|
|
34
49
|
policy?: Partial<DepBrainConfig["policy"]>;
|
|
35
50
|
report?: Partial<DepBrainConfig["report"]>;
|
|
51
|
+
plugins?: Partial<DepBrainConfig["plugins"]>;
|
|
52
|
+
risk?: Partial<DepBrainConfig["risk"]>;
|
|
53
|
+
dashboard?: Partial<DepBrainConfig["dashboard"]>;
|
|
54
|
+
notifications?: Partial<DepBrainConfig["notifications"]>;
|
|
36
55
|
scoring?: Partial<DepBrainConfig["scoring"]>;
|
|
37
56
|
scan?: Partial<DepBrainConfig["scan"]>;
|
|
38
57
|
}
|
package/dist/utils/config.js
CHANGED
|
@@ -21,6 +21,21 @@ export const defaultConfig = {
|
|
|
21
21
|
report: {
|
|
22
22
|
maxSuggestions: 5
|
|
23
23
|
},
|
|
24
|
+
plugins: {
|
|
25
|
+
enabled: [],
|
|
26
|
+
paths: []
|
|
27
|
+
},
|
|
28
|
+
risk: {
|
|
29
|
+
transitiveBloatThreshold: 50,
|
|
30
|
+
typosquattingDistanceThreshold: 2
|
|
31
|
+
},
|
|
32
|
+
dashboard: {
|
|
33
|
+
outputPath: "depbrain-dashboard.html"
|
|
34
|
+
},
|
|
35
|
+
notifications: {
|
|
36
|
+
slackWebhookEnv: "DEPBRAIN_SLACK_WEBHOOK_URL",
|
|
37
|
+
discordWebhookEnv: "DEPBRAIN_DISCORD_WEBHOOK_URL"
|
|
38
|
+
},
|
|
24
39
|
scoring: {
|
|
25
40
|
duplicateWeight: 5,
|
|
26
41
|
outdatedWeight: 3,
|
|
@@ -63,6 +78,21 @@ function normalizeConfig(loaded) {
|
|
|
63
78
|
report: {
|
|
64
79
|
maxSuggestions: normalizeNumber(loaded.report?.maxSuggestions, defaultConfig.report.maxSuggestions)
|
|
65
80
|
},
|
|
81
|
+
plugins: {
|
|
82
|
+
enabled: normalizeStringArray(loaded.plugins?.enabled, defaultConfig.plugins.enabled),
|
|
83
|
+
paths: normalizeStringArray(loaded.plugins?.paths, defaultConfig.plugins.paths)
|
|
84
|
+
},
|
|
85
|
+
risk: {
|
|
86
|
+
transitiveBloatThreshold: normalizeNumber(loaded.risk?.transitiveBloatThreshold, defaultConfig.risk.transitiveBloatThreshold),
|
|
87
|
+
typosquattingDistanceThreshold: normalizeNumber(loaded.risk?.typosquattingDistanceThreshold, defaultConfig.risk.typosquattingDistanceThreshold)
|
|
88
|
+
},
|
|
89
|
+
dashboard: {
|
|
90
|
+
outputPath: normalizeString(loaded.dashboard?.outputPath, defaultConfig.dashboard.outputPath)
|
|
91
|
+
},
|
|
92
|
+
notifications: {
|
|
93
|
+
slackWebhookEnv: normalizeString(loaded.notifications?.slackWebhookEnv, defaultConfig.notifications.slackWebhookEnv),
|
|
94
|
+
discordWebhookEnv: normalizeString(loaded.notifications?.discordWebhookEnv, defaultConfig.notifications.discordWebhookEnv)
|
|
95
|
+
},
|
|
66
96
|
scoring: {
|
|
67
97
|
duplicateWeight: normalizeNumber(loaded.scoring?.duplicateWeight, defaultConfig.scoring.duplicateWeight),
|
|
68
98
|
outdatedWeight: normalizeNumber(loaded.scoring?.outdatedWeight, defaultConfig.scoring.outdatedWeight),
|
|
@@ -86,3 +116,6 @@ function normalizeBoolean(value, fallback) {
|
|
|
86
116
|
function normalizeNumber(value, fallback) {
|
|
87
117
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
88
118
|
}
|
|
119
|
+
function normalizeString(value, fallback) {
|
|
120
|
+
return typeof value === "string" && value.trim().length > 0 ? value : fallback;
|
|
121
|
+
}
|