dep-brain 0.3.0 → 0.4.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 +10 -1
- package/depbrain.config.json +6 -1
- package/depbrain.config.schema.json +10 -1
- package/dist/checks/unused.d.ts +3 -1
- package/dist/checks/unused.js +2 -2
- package/dist/core/analyzer.js +32 -8
- package/dist/utils/config.d.ts +6 -0
- package/dist/utils/config.js +12 -2
- package/dist/utils/file-parser.d.ts +1 -1
- package/dist/utils/file-parser.js +34 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ npx dep-brain analyze ./path-to-project
|
|
|
46
46
|
npx dep-brain analyze --config depbrain.config.json
|
|
47
47
|
npx dep-brain analyze --min-score 90 --fail-on-risks
|
|
48
48
|
npx dep-brain analyze ./path-to-project --fail-on-unused --json
|
|
49
|
+
npx dep-brain analyze --md > depbrain.md
|
|
49
50
|
|
|
50
51
|
dep-brain config
|
|
51
52
|
dep-brain config --config depbrain.config.json
|
|
@@ -109,7 +110,9 @@ Create a `depbrain.config.json` file in the project root:
|
|
|
109
110
|
{
|
|
110
111
|
"ignore": {
|
|
111
112
|
"unused": ["eslint"],
|
|
112
|
-
"outdated": ["typescript"]
|
|
113
|
+
"outdated": ["typescript"],
|
|
114
|
+
"prefixes": ["@nestjs/"],
|
|
115
|
+
"patterns": ["^@aws-sdk/"]
|
|
113
116
|
},
|
|
114
117
|
"policy": {
|
|
115
118
|
"minScore": 90,
|
|
@@ -124,6 +127,9 @@ Create a `depbrain.config.json` file in the project root:
|
|
|
124
127
|
"outdatedWeight": 1,
|
|
125
128
|
"unusedWeight": 2,
|
|
126
129
|
"riskWeight": 6
|
|
130
|
+
},
|
|
131
|
+
"scan": {
|
|
132
|
+
"excludePaths": ["node_modules", "dist", "build", "coverage", ".git"]
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
```
|
|
@@ -146,6 +152,9 @@ Supported sections:
|
|
|
146
152
|
- `scoring.outdatedWeight`
|
|
147
153
|
- `scoring.unusedWeight`
|
|
148
154
|
- `scoring.riskWeight`
|
|
155
|
+
- `ignore.prefixes`
|
|
156
|
+
- `ignore.patterns`
|
|
157
|
+
- `scan.excludePaths`
|
|
149
158
|
|
|
150
159
|
Sample config file:
|
|
151
160
|
|
package/depbrain.config.json
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
"duplicates": [],
|
|
6
6
|
"risks": [],
|
|
7
7
|
"dependencies": [],
|
|
8
|
-
"devDependencies": []
|
|
8
|
+
"devDependencies": [],
|
|
9
|
+
"prefixes": [],
|
|
10
|
+
"patterns": []
|
|
9
11
|
},
|
|
10
12
|
"policy": {
|
|
11
13
|
"minScore": 85,
|
|
@@ -22,5 +24,8 @@
|
|
|
22
24
|
"outdatedWeight": 3,
|
|
23
25
|
"unusedWeight": 4,
|
|
24
26
|
"riskWeight": 10
|
|
27
|
+
},
|
|
28
|
+
"scan": {
|
|
29
|
+
"excludePaths": ["node_modules", "dist", "build", "coverage", ".git"]
|
|
25
30
|
}
|
|
26
31
|
}
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"unused": { "type": "array", "items": { "type": "string" } },
|
|
14
14
|
"duplicates": { "type": "array", "items": { "type": "string" } },
|
|
15
15
|
"outdated": { "type": "array", "items": { "type": "string" } },
|
|
16
|
-
"risks": { "type": "array", "items": { "type": "string" } }
|
|
16
|
+
"risks": { "type": "array", "items": { "type": "string" } },
|
|
17
|
+
"prefixes": { "type": "array", "items": { "type": "string" } },
|
|
18
|
+
"patterns": { "type": "array", "items": { "type": "string" } }
|
|
17
19
|
}
|
|
18
20
|
},
|
|
19
21
|
"policy": {
|
|
@@ -43,6 +45,13 @@
|
|
|
43
45
|
"unusedWeight": { "type": "number" },
|
|
44
46
|
"riskWeight": { "type": "number" }
|
|
45
47
|
}
|
|
48
|
+
},
|
|
49
|
+
"scan": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": false,
|
|
52
|
+
"properties": {
|
|
53
|
+
"excludePaths": { "type": "array", "items": { "type": "string" } }
|
|
54
|
+
}
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
}
|
package/dist/checks/unused.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { UnusedDependency } from "../core/analyzer.js";
|
|
2
2
|
import type { DependencyGraph } from "../core/graph-builder.js";
|
|
3
|
-
export declare function findUnusedDependencies(rootDir: string, graph: DependencyGraph
|
|
3
|
+
export declare function findUnusedDependencies(rootDir: string, graph: DependencyGraph, options?: {
|
|
4
|
+
excludePaths?: string[];
|
|
5
|
+
}): Promise<UnusedDependency[]>;
|
package/dist/checks/unused.js
CHANGED
|
@@ -4,8 +4,8 @@ const SOURCE_FILE_PATTERN = /\.(c|m)?(t|j)sx?$/;
|
|
|
4
4
|
const CONFIG_FILE_PATTERN = /(^|[\\/])(vite|vitest|jest|eslint|prettier|rollup|webpack|babel|tsup|eslint\.config|commitlint|playwright|storybook|tailwind|postcss)\.config\.(c|m)?(t|j)s$/;
|
|
5
5
|
const TEST_FILE_PATTERN = /(^|[\\/])(__tests__|test|tests|spec|specs)([\\/]|$)|\.(test|spec)\.(c|m)?(t|j)sx?$/;
|
|
6
6
|
const RUNTIME_DIR_PATTERN = /(^|[\\/])(src|app|lib|server|client|pages|components)([\\/]|$)/;
|
|
7
|
-
export async function findUnusedDependencies(rootDir, graph) {
|
|
8
|
-
const files = await collectProjectFiles(rootDir, SOURCE_FILE_PATTERN);
|
|
7
|
+
export async function findUnusedDependencies(rootDir, graph, options = {}) {
|
|
8
|
+
const files = await collectProjectFiles(rootDir, SOURCE_FILE_PATTERN, options.excludePaths ?? []);
|
|
9
9
|
const projectFiles = files.filter((filePath) => !filePath.includes(`${path.sep}node_modules${path.sep}`));
|
|
10
10
|
const runtimeUsed = new Set();
|
|
11
11
|
const devUsed = new Set();
|
package/dist/core/analyzer.js
CHANGED
|
@@ -18,7 +18,7 @@ export async function analyzeProject(options = {}) {
|
|
|
18
18
|
}
|
|
19
19
|
const rootGraph = await buildDependencyGraph(rootDir);
|
|
20
20
|
const rawDuplicates = await findDuplicateDependencies(rootGraph);
|
|
21
|
-
const duplicates = rawDuplicates.filter((item) => !
|
|
21
|
+
const duplicates = rawDuplicates.filter((item) => !shouldIgnorePackage(item.name, "duplicates", config));
|
|
22
22
|
const packages = [];
|
|
23
23
|
for (const workspace of workspaces) {
|
|
24
24
|
const result = await analyzeSingleProject(workspace.rootDir, config, {
|
|
@@ -81,7 +81,9 @@ function mergeConfig(base, overrides) {
|
|
|
81
81
|
duplicates: overrides.ignore?.duplicates ?? base.ignore.duplicates,
|
|
82
82
|
outdated: overrides.ignore?.outdated ?? base.ignore.outdated,
|
|
83
83
|
risks: overrides.ignore?.risks ?? base.ignore.risks,
|
|
84
|
-
unused: overrides.ignore?.unused ?? base.ignore.unused
|
|
84
|
+
unused: overrides.ignore?.unused ?? base.ignore.unused,
|
|
85
|
+
prefixes: overrides.ignore?.prefixes ?? base.ignore.prefixes,
|
|
86
|
+
patterns: overrides.ignore?.patterns ?? base.ignore.patterns
|
|
85
87
|
},
|
|
86
88
|
policy: {
|
|
87
89
|
minScore: overrides.policy?.minScore ?? base.policy.minScore,
|
|
@@ -98,6 +100,9 @@ function mergeConfig(base, overrides) {
|
|
|
98
100
|
outdatedWeight: overrides.scoring?.outdatedWeight ?? base.scoring.outdatedWeight,
|
|
99
101
|
unusedWeight: overrides.scoring?.unusedWeight ?? base.scoring.unusedWeight,
|
|
100
102
|
riskWeight: overrides.scoring?.riskWeight ?? base.scoring.riskWeight
|
|
103
|
+
},
|
|
104
|
+
scan: {
|
|
105
|
+
excludePaths: overrides.scan?.excludePaths ?? base.scan.excludePaths
|
|
101
106
|
}
|
|
102
107
|
};
|
|
103
108
|
}
|
|
@@ -127,15 +132,17 @@ async function analyzeSingleProject(rootDir, config, options = {}) {
|
|
|
127
132
|
const graph = await buildDependencyGraph(rootDir);
|
|
128
133
|
const [rawDuplicates, rawUnused, rawOutdated, rawRisks] = await Promise.all([
|
|
129
134
|
findDuplicateDependencies(graph),
|
|
130
|
-
findUnusedDependencies(rootDir, graph
|
|
135
|
+
findUnusedDependencies(rootDir, graph, {
|
|
136
|
+
excludePaths: config.scan.excludePaths
|
|
137
|
+
}),
|
|
131
138
|
findOutdatedDependencies(graph),
|
|
132
139
|
findRiskDependencies(graph)
|
|
133
140
|
]);
|
|
134
|
-
const duplicates = rawDuplicates.filter((item) => !
|
|
135
|
-
const unused = rawUnused.filter((item) => !
|
|
136
|
-
!
|
|
137
|
-
const outdated = rawOutdated.filter((item) => !
|
|
138
|
-
const risks = rawRisks.filter((item) => !
|
|
141
|
+
const duplicates = rawDuplicates.filter((item) => !shouldIgnorePackage(item.name, "duplicates", config));
|
|
142
|
+
const unused = rawUnused.filter((item) => !shouldIgnorePackage(item.name, "unused", config) &&
|
|
143
|
+
!shouldIgnorePackage(item.name, item.section, config));
|
|
144
|
+
const outdated = rawOutdated.filter((item) => !shouldIgnorePackage(item.name, "outdated", config));
|
|
145
|
+
const risks = rawRisks.filter((item) => !shouldIgnorePackage(item.name, "risks", config));
|
|
139
146
|
const score = calculateHealthScore({
|
|
140
147
|
duplicates: duplicates.length,
|
|
141
148
|
unused: unused.length,
|
|
@@ -187,6 +194,23 @@ async function analyzeSingleProject(rootDir, config, options = {}) {
|
|
|
187
194
|
config
|
|
188
195
|
};
|
|
189
196
|
}
|
|
197
|
+
function shouldIgnorePackage(name, bucket, config) {
|
|
198
|
+
if (config.ignore[bucket].includes(name)) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
if (config.ignore.prefixes.some((prefix) => name.startsWith(prefix))) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
return config.ignore.patterns.some((pattern) => {
|
|
205
|
+
try {
|
|
206
|
+
const regex = new RegExp(pattern);
|
|
207
|
+
return regex.test(name);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
190
214
|
function buildScoreBreakdown(counts, config) {
|
|
191
215
|
return {
|
|
192
216
|
baseScore: 100,
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface DepBrainConfig {
|
|
|
6
6
|
outdated: string[];
|
|
7
7
|
risks: string[];
|
|
8
8
|
unused: string[];
|
|
9
|
+
prefixes: string[];
|
|
10
|
+
patterns: string[];
|
|
9
11
|
};
|
|
10
12
|
policy: {
|
|
11
13
|
minScore: number;
|
|
@@ -23,12 +25,16 @@ export interface DepBrainConfig {
|
|
|
23
25
|
unusedWeight: number;
|
|
24
26
|
riskWeight: number;
|
|
25
27
|
};
|
|
28
|
+
scan: {
|
|
29
|
+
excludePaths: string[];
|
|
30
|
+
};
|
|
26
31
|
}
|
|
27
32
|
export interface DepBrainConfigOverrides {
|
|
28
33
|
ignore?: Partial<DepBrainConfig["ignore"]>;
|
|
29
34
|
policy?: Partial<DepBrainConfig["policy"]>;
|
|
30
35
|
report?: Partial<DepBrainConfig["report"]>;
|
|
31
36
|
scoring?: Partial<DepBrainConfig["scoring"]>;
|
|
37
|
+
scan?: Partial<DepBrainConfig["scan"]>;
|
|
32
38
|
}
|
|
33
39
|
export declare const defaultConfig: DepBrainConfig;
|
|
34
40
|
export declare function loadDepBrainConfig(rootDir: string, configPath?: string): Promise<DepBrainConfig>;
|
package/dist/utils/config.js
CHANGED
|
@@ -7,7 +7,9 @@ export const defaultConfig = {
|
|
|
7
7
|
duplicates: [],
|
|
8
8
|
outdated: [],
|
|
9
9
|
risks: [],
|
|
10
|
-
unused: []
|
|
10
|
+
unused: [],
|
|
11
|
+
prefixes: [],
|
|
12
|
+
patterns: []
|
|
11
13
|
},
|
|
12
14
|
policy: {
|
|
13
15
|
minScore: 0,
|
|
@@ -24,6 +26,9 @@ export const defaultConfig = {
|
|
|
24
26
|
outdatedWeight: 3,
|
|
25
27
|
unusedWeight: 4,
|
|
26
28
|
riskWeight: 10
|
|
29
|
+
},
|
|
30
|
+
scan: {
|
|
31
|
+
excludePaths: ["node_modules", "dist", "build", "coverage", ".git"]
|
|
27
32
|
}
|
|
28
33
|
};
|
|
29
34
|
export async function loadDepBrainConfig(rootDir, configPath) {
|
|
@@ -44,7 +49,9 @@ function normalizeConfig(loaded) {
|
|
|
44
49
|
duplicates: normalizeStringArray(loaded.ignore?.duplicates, defaultConfig.ignore.duplicates),
|
|
45
50
|
outdated: normalizeStringArray(loaded.ignore?.outdated, defaultConfig.ignore.outdated),
|
|
46
51
|
risks: normalizeStringArray(loaded.ignore?.risks, defaultConfig.ignore.risks),
|
|
47
|
-
unused: normalizeStringArray(loaded.ignore?.unused, defaultConfig.ignore.unused)
|
|
52
|
+
unused: normalizeStringArray(loaded.ignore?.unused, defaultConfig.ignore.unused),
|
|
53
|
+
prefixes: normalizeStringArray(loaded.ignore?.prefixes, defaultConfig.ignore.prefixes),
|
|
54
|
+
patterns: normalizeStringArray(loaded.ignore?.patterns, defaultConfig.ignore.patterns)
|
|
48
55
|
},
|
|
49
56
|
policy: {
|
|
50
57
|
minScore: normalizeNumber(loaded.policy?.minScore, defaultConfig.policy.minScore),
|
|
@@ -61,6 +68,9 @@ function normalizeConfig(loaded) {
|
|
|
61
68
|
outdatedWeight: normalizeNumber(loaded.scoring?.outdatedWeight, defaultConfig.scoring.outdatedWeight),
|
|
62
69
|
unusedWeight: normalizeNumber(loaded.scoring?.unusedWeight, defaultConfig.scoring.unusedWeight),
|
|
63
70
|
riskWeight: normalizeNumber(loaded.scoring?.riskWeight, defaultConfig.scoring.riskWeight)
|
|
71
|
+
},
|
|
72
|
+
scan: {
|
|
73
|
+
excludePaths: normalizeStringArray(loaded.scan?.excludePaths, defaultConfig.scan.excludePaths)
|
|
64
74
|
}
|
|
65
75
|
};
|
|
66
76
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare function readJsonFile<T>(filePath: string): Promise<T>;
|
|
2
2
|
export declare function readTextFile(filePath: string): Promise<string>;
|
|
3
|
-
export declare function collectProjectFiles(rootDir: string, pattern: RegExp): Promise<string[]>;
|
|
3
|
+
export declare function collectProjectFiles(rootDir: string, pattern: RegExp, excludePaths?: string[]): Promise<string[]>;
|
|
@@ -7,16 +7,23 @@ export async function readJsonFile(filePath) {
|
|
|
7
7
|
export async function readTextFile(filePath) {
|
|
8
8
|
return fs.readFile(filePath, "utf8");
|
|
9
9
|
}
|
|
10
|
-
export async function collectProjectFiles(rootDir, pattern) {
|
|
11
|
-
|
|
10
|
+
export async function collectProjectFiles(rootDir, pattern, excludePaths = []) {
|
|
11
|
+
return collectProjectFilesInternal(rootDir, rootDir, pattern, excludePaths);
|
|
12
|
+
}
|
|
13
|
+
async function collectProjectFilesInternal(currentDir, baseDir, pattern, excludePaths) {
|
|
14
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
12
15
|
const files = [];
|
|
13
16
|
for (const entry of entries) {
|
|
14
|
-
const fullPath = path.join(
|
|
17
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
18
|
+
const relPath = path.relative(baseDir, fullPath);
|
|
19
|
+
if (matchesAnyPattern(relPath, excludePaths)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
15
22
|
if (entry.isDirectory()) {
|
|
16
23
|
if (entry.name === ".git") {
|
|
17
24
|
continue;
|
|
18
25
|
}
|
|
19
|
-
files.push(...(await
|
|
26
|
+
files.push(...(await collectProjectFilesInternal(fullPath, baseDir, pattern, excludePaths)));
|
|
20
27
|
continue;
|
|
21
28
|
}
|
|
22
29
|
if (pattern.test(entry.name)) {
|
|
@@ -25,3 +32,26 @@ export async function collectProjectFiles(rootDir, pattern) {
|
|
|
25
32
|
}
|
|
26
33
|
return files;
|
|
27
34
|
}
|
|
35
|
+
function matchesAnyPattern(value, patterns) {
|
|
36
|
+
if (patterns.length === 0) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const normalized = normalizePath(value);
|
|
40
|
+
return patterns.some((pattern) => {
|
|
41
|
+
const regex = globToRegExp(pattern);
|
|
42
|
+
return regex.test(normalized);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function globToRegExp(pattern) {
|
|
46
|
+
const normalized = normalizePath(pattern)
|
|
47
|
+
.replace(/\/+$/, "")
|
|
48
|
+
.replace(/^\//, "")
|
|
49
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
50
|
+
.replace(/\*\*/g, "___DEPBRAIN_GLOBSTAR___")
|
|
51
|
+
.replace(/\*/g, "[^/]*")
|
|
52
|
+
.replace(/___DEPBRAIN_GLOBSTAR___/g, ".*");
|
|
53
|
+
return new RegExp(`(^|.*/)${normalized}($|/.*)`);
|
|
54
|
+
}
|
|
55
|
+
function normalizePath(value) {
|
|
56
|
+
return value.split(path.sep).join("/");
|
|
57
|
+
}
|