dep-brain 0.4.0 → 0.5.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/dist/checks/duplicate.d.ts +2 -0
- package/dist/checks/duplicate.js +17 -0
- package/dist/checks/outdated.d.ts +2 -0
- package/dist/checks/outdated.js +18 -0
- package/dist/checks/risk.d.ts +2 -0
- package/dist/checks/risk.js +16 -0
- package/dist/checks/unused.d.ts +7 -2
- package/dist/checks/unused.js +28 -19
- package/dist/cli.js +44 -10
- package/dist/core/analyzer.js +98 -20
- package/dist/core/context.d.ts +3 -0
- package/dist/core/context.js +31 -0
- package/dist/core/types.d.ts +28 -0
- package/dist/core/types.js +1 -0
- package/dist/index.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,7 +46,9 @@ 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
|
-
|
|
49
|
+
npx dep-brain analyze --md > depbrain.md
|
|
50
|
+
npx dep-brain analyze --json --out depbrain.json
|
|
51
|
+
npx dep-brain report --from depbrain.json --md --out depbrain.md
|
|
50
52
|
|
|
51
53
|
dep-brain config
|
|
52
54
|
dep-brain config --config depbrain.config.json
|
|
@@ -102,6 +104,13 @@ Output includes `outputVersion` for schema stability and can be validated with:
|
|
|
102
104
|
dep-brain analyze --md
|
|
103
105
|
```
|
|
104
106
|
|
|
107
|
+
## Report From JSON
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
dep-brain analyze --json --out depbrain.json
|
|
111
|
+
dep-brain report --from depbrain.json --md --out depbrain.md
|
|
112
|
+
```
|
|
113
|
+
|
|
105
114
|
## Config File
|
|
106
115
|
|
|
107
116
|
Create a `depbrain.config.json` file in the project root:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { DuplicateDependency } from "../core/analyzer.js";
|
|
2
2
|
import type { DependencyGraph } from "../core/graph-builder.js";
|
|
3
|
+
import type { CheckResult } from "../core/types.js";
|
|
3
4
|
export declare function findDuplicateDependencies(graph: DependencyGraph): Promise<DuplicateDependency[]>;
|
|
5
|
+
export declare function runDuplicateCheck(graph: DependencyGraph): Promise<CheckResult>;
|
package/dist/checks/duplicate.js
CHANGED
|
@@ -8,3 +8,20 @@ export async function findDuplicateDependencies(graph) {
|
|
|
8
8
|
.filter((dependency) => dependency.versions.length > 1)
|
|
9
9
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
10
10
|
}
|
|
11
|
+
export async function runDuplicateCheck(graph) {
|
|
12
|
+
const duplicates = await findDuplicateDependencies(graph);
|
|
13
|
+
return {
|
|
14
|
+
name: "duplicate",
|
|
15
|
+
summary: `${duplicates.length} duplicate dependencies found`,
|
|
16
|
+
issues: duplicates.map((item) => ({
|
|
17
|
+
id: `duplicate:${item.name}`,
|
|
18
|
+
message: `${item.name} has ${item.versions.length} versions`,
|
|
19
|
+
severity: "warning",
|
|
20
|
+
meta: {
|
|
21
|
+
name: item.name,
|
|
22
|
+
versions: item.versions,
|
|
23
|
+
instances: item.instances
|
|
24
|
+
}
|
|
25
|
+
}))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { OutdatedDependency } from "../core/analyzer.js";
|
|
2
2
|
import type { DependencyGraph } from "../core/graph-builder.js";
|
|
3
|
+
import type { CheckResult } from "../core/types.js";
|
|
3
4
|
export interface OutdatedOptions {
|
|
4
5
|
resolveLatestVersion?: (name: string) => Promise<string | null>;
|
|
5
6
|
}
|
|
6
7
|
export declare function findOutdatedDependencies(graph: DependencyGraph, options?: OutdatedOptions): Promise<OutdatedDependency[]>;
|
|
8
|
+
export declare function runOutdatedCheck(graph: DependencyGraph): Promise<CheckResult>;
|
package/dist/checks/outdated.js
CHANGED
|
@@ -22,6 +22,24 @@ export async function findOutdatedDependencies(graph, options = {}) {
|
|
|
22
22
|
.filter((item) => item !== null)
|
|
23
23
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
24
24
|
}
|
|
25
|
+
export async function runOutdatedCheck(graph) {
|
|
26
|
+
const outdated = await findOutdatedDependencies(graph);
|
|
27
|
+
return {
|
|
28
|
+
name: "outdated",
|
|
29
|
+
summary: `${outdated.length} outdated dependencies found`,
|
|
30
|
+
issues: outdated.map((item) => ({
|
|
31
|
+
id: `outdated:${item.name}`,
|
|
32
|
+
message: `${item.name} ${item.current} -> ${item.latest}`,
|
|
33
|
+
severity: item.updateType === "major" ? "critical" : "warning",
|
|
34
|
+
meta: {
|
|
35
|
+
name: item.name,
|
|
36
|
+
current: item.current,
|
|
37
|
+
latest: item.latest,
|
|
38
|
+
updateType: item.updateType
|
|
39
|
+
}
|
|
40
|
+
}))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
25
43
|
function normalizeVersion(versionRange) {
|
|
26
44
|
return versionRange.trim().replace(/^[~^><=\s]+/, "");
|
|
27
45
|
}
|
package/dist/checks/risk.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { DependencyGraph } from "../core/graph-builder.js";
|
|
2
2
|
import type { RiskDependency } from "../core/analyzer.js";
|
|
3
|
+
import type { CheckResult } from "../core/types.js";
|
|
3
4
|
export declare function findRiskDependencies(graph: DependencyGraph): Promise<RiskDependency[]>;
|
|
5
|
+
export declare function runRiskCheck(graph: DependencyGraph): Promise<CheckResult>;
|
package/dist/checks/risk.js
CHANGED
|
@@ -29,3 +29,19 @@ export async function findRiskDependencies(graph) {
|
|
|
29
29
|
.filter((item) => item !== null)
|
|
30
30
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
31
31
|
}
|
|
32
|
+
export async function runRiskCheck(graph) {
|
|
33
|
+
const risks = await findRiskDependencies(graph);
|
|
34
|
+
return {
|
|
35
|
+
name: "risk",
|
|
36
|
+
summary: `${risks.length} risky dependencies found`,
|
|
37
|
+
issues: risks.map((item) => ({
|
|
38
|
+
id: `risk:${item.name}`,
|
|
39
|
+
message: `${item.name}: ${item.reasons.join("; ")}`,
|
|
40
|
+
severity: "warning",
|
|
41
|
+
meta: {
|
|
42
|
+
name: item.name,
|
|
43
|
+
reasons: item.reasons
|
|
44
|
+
}
|
|
45
|
+
}))
|
|
46
|
+
};
|
|
47
|
+
}
|
package/dist/checks/unused.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { UnusedDependency } from "../core/analyzer.js";
|
|
2
2
|
import type { DependencyGraph } from "../core/graph-builder.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import type { AnalysisContext, CheckResult } from "../core/types.js";
|
|
4
|
+
export declare function findUnusedDependencies(rootDir: string, graph: DependencyGraph, fileEntries: {
|
|
5
|
+
path: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}[], options: {
|
|
8
|
+
hasTypeScriptConfig: boolean;
|
|
5
9
|
}): Promise<UnusedDependency[]>;
|
|
10
|
+
export declare function runUnusedCheck(context: AnalysisContext): Promise<CheckResult>;
|
package/dist/checks/unused.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { collectProjectFiles, readTextFile } from "../utils/file-parser.js";
|
|
3
2
|
const SOURCE_FILE_PATTERN = /\.(c|m)?(t|j)sx?$/;
|
|
4
3
|
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
4
|
const TEST_FILE_PATTERN = /(^|[\\/])(__tests__|test|tests|spec|specs)([\\/]|$)|\.(test|spec)\.(c|m)?(t|j)sx?$/;
|
|
6
5
|
const RUNTIME_DIR_PATTERN = /(^|[\\/])(src|app|lib|server|client|pages|components)([\\/]|$)/;
|
|
7
|
-
export async function findUnusedDependencies(rootDir, graph, options
|
|
8
|
-
const
|
|
9
|
-
|
|
6
|
+
export async function findUnusedDependencies(rootDir, graph, fileEntries, options) {
|
|
7
|
+
const projectFiles = fileEntries
|
|
8
|
+
.map((entry) => entry.path)
|
|
9
|
+
.filter((filePath) => !filePath.includes(`${path.sep}node_modules${path.sep}`));
|
|
10
10
|
const runtimeUsed = new Set();
|
|
11
11
|
const devUsed = new Set();
|
|
12
|
-
for (const
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
for (const entry of fileEntries) {
|
|
13
|
+
if (!SOURCE_FILE_PATTERN.test(entry.path)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const imports = extractImportedPackages(entry.content);
|
|
17
|
+
const filePath = entry.path;
|
|
15
18
|
const isDevOnlyFile = isDevelopmentOnlyFile(rootDir, filePath);
|
|
16
19
|
const target = isDevOnlyFile ? devUsed : runtimeUsed;
|
|
17
20
|
for (const importedPackage of imports) {
|
|
@@ -25,8 +28,7 @@ export async function findUnusedDependencies(rootDir, graph, options = {}) {
|
|
|
25
28
|
devUsed.add(referencedBinary);
|
|
26
29
|
}
|
|
27
30
|
const hasTypeScriptSources = projectFiles.some((filePath) => /\.(c|m)?tsx?$/.test(filePath));
|
|
28
|
-
|
|
29
|
-
if (hasTypeScriptConfig) {
|
|
31
|
+
if (options.hasTypeScriptConfig) {
|
|
30
32
|
devUsed.add("typescript");
|
|
31
33
|
}
|
|
32
34
|
const unusedDependencies = Object.keys(graph.dependencies)
|
|
@@ -34,10 +36,26 @@ export async function findUnusedDependencies(rootDir, graph, options = {}) {
|
|
|
34
36
|
.map((name) => ({ name, section: "dependencies" }));
|
|
35
37
|
const unusedDevDependencies = Object.keys(graph.devDependencies)
|
|
36
38
|
.filter((name) => !devUsed.has(name) && !runtimeUsed.has(name))
|
|
37
|
-
.filter((name) => !isImplicitlyUsedDevDependency(name, hasTypeScriptSources, hasTypeScriptConfig))
|
|
39
|
+
.filter((name) => !isImplicitlyUsedDevDependency(name, hasTypeScriptSources, options.hasTypeScriptConfig))
|
|
38
40
|
.map((name) => ({ name, section: "devDependencies" }));
|
|
39
41
|
return [...unusedDependencies, ...unusedDevDependencies].sort((left, right) => left.name.localeCompare(right.name));
|
|
40
42
|
}
|
|
43
|
+
export async function runUnusedCheck(context) {
|
|
44
|
+
const unused = await findUnusedDependencies(context.rootDir, context.graph, context.fileEntries, { hasTypeScriptConfig: context.hasTypeScriptConfig });
|
|
45
|
+
return {
|
|
46
|
+
name: "unused",
|
|
47
|
+
summary: `${unused.length} unused dependencies found`,
|
|
48
|
+
issues: unused.map((item) => ({
|
|
49
|
+
id: `unused:${item.section}:${item.name}`,
|
|
50
|
+
message: `${item.name} appears unused`,
|
|
51
|
+
severity: "warning",
|
|
52
|
+
meta: {
|
|
53
|
+
name: item.name,
|
|
54
|
+
section: item.section
|
|
55
|
+
}
|
|
56
|
+
}))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
41
59
|
function extractImportedPackages(content) {
|
|
42
60
|
const imports = new Set();
|
|
43
61
|
const patterns = [
|
|
@@ -109,12 +127,3 @@ function isImplicitlyUsedDevDependency(name, hasTypeScriptSources, hasTypeScript
|
|
|
109
127
|
}
|
|
110
128
|
return false;
|
|
111
129
|
}
|
|
112
|
-
async function hasFile(rootDir, fileName) {
|
|
113
|
-
try {
|
|
114
|
-
await readTextFile(path.join(rootDir, fileName));
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
package/dist/cli.js
CHANGED
|
@@ -39,6 +39,30 @@ async function main() {
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
if (command !== "analyze") {
|
|
42
|
+
if (command === "report") {
|
|
43
|
+
const fromPath = optionValues.get("--from") ?? positionals[0];
|
|
44
|
+
if (!fromPath) {
|
|
45
|
+
console.error("Missing --from <file> for report");
|
|
46
|
+
printHelp();
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const raw = await fs.readFile(fromPath, "utf8");
|
|
52
|
+
const reportData = JSON.parse(raw);
|
|
53
|
+
const output = flags.has("--json")
|
|
54
|
+
? JSON.stringify(reportData, null, 2)
|
|
55
|
+
: renderMarkdownReport(reportData);
|
|
56
|
+
await writeOutput(output, optionValues.get("--out"));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error("Failed to render report.");
|
|
61
|
+
console.error(error);
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
42
66
|
if (command === "config") {
|
|
43
67
|
if (!(await hasPackageJson(targetPath))) {
|
|
44
68
|
console.error(`No package.json found at ${targetPath}`);
|
|
@@ -78,21 +102,21 @@ async function main() {
|
|
|
78
102
|
configPath: optionValues.get("--config"),
|
|
79
103
|
config: cliConfig
|
|
80
104
|
});
|
|
105
|
+
let output;
|
|
81
106
|
if (flags.has("--json")) {
|
|
82
|
-
|
|
107
|
+
output = renderJsonReport(result);
|
|
83
108
|
}
|
|
84
109
|
else if (flags.has("--md")) {
|
|
85
|
-
|
|
110
|
+
output = renderMarkdownReport(result);
|
|
86
111
|
}
|
|
87
112
|
else {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
process.stdout.write(`${output}\n`);
|
|
94
|
-
}
|
|
113
|
+
const consoleOutput = renderConsoleReport(result);
|
|
114
|
+
output =
|
|
115
|
+
!consoleOutput || consoleOutput.trim().length === 0
|
|
116
|
+
? renderJsonReport(result)
|
|
117
|
+
: consoleOutput;
|
|
95
118
|
}
|
|
119
|
+
await writeOutput(output, optionValues.get("--out"));
|
|
96
120
|
if (!result.policy.passed) {
|
|
97
121
|
process.exitCode = 1;
|
|
98
122
|
}
|
|
@@ -148,7 +172,8 @@ function printHelp() {
|
|
|
148
172
|
console.log("Dependency Brain");
|
|
149
173
|
console.log("");
|
|
150
174
|
console.log("Usage:");
|
|
151
|
-
console.log(" dep-brain analyze [path] [--json] [--md] [--config path] [--min-score n] [--fail-on-risks] [--fail-on-outdated] [--fail-on-unused] [--fail-on-duplicates]");
|
|
175
|
+
console.log(" dep-brain analyze [path] [--json] [--md] [--out path] [--config path] [--min-score n] [--fail-on-risks] [--fail-on-outdated] [--fail-on-unused] [--fail-on-duplicates]");
|
|
176
|
+
console.log(" dep-brain report --from <file> [--md] [--json] [--out path]");
|
|
152
177
|
console.log(" dep-brain config [path] [--config path]");
|
|
153
178
|
console.log(" dep-brain help");
|
|
154
179
|
console.log(" dep-brain --version");
|
|
@@ -157,6 +182,8 @@ function printHelp() {
|
|
|
157
182
|
console.log(" --json Output JSON for analysis");
|
|
158
183
|
console.log(" --md Output Markdown report");
|
|
159
184
|
console.log(" --config <path> Path to depbrain.config.json");
|
|
185
|
+
console.log(" --from <file> Read analysis JSON from file");
|
|
186
|
+
console.log(" --out <path> Write output to a file");
|
|
160
187
|
console.log(" --min-score <n> Minimum score required to pass");
|
|
161
188
|
console.log(" --fail-on-risks Fail when risky dependencies exist");
|
|
162
189
|
console.log(" --fail-on-outdated Fail when outdated dependencies exist");
|
|
@@ -176,3 +203,10 @@ async function loadPackageVersion() {
|
|
|
176
203
|
return null;
|
|
177
204
|
}
|
|
178
205
|
}
|
|
206
|
+
async function writeOutput(output, outPath) {
|
|
207
|
+
if (outPath) {
|
|
208
|
+
await fs.writeFile(outPath, `${output}\n`, "utf8");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
process.stdout.write(`${output}\n`);
|
|
212
|
+
}
|
package/dist/core/analyzer.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { runDuplicateCheck } from "../checks/duplicate.js";
|
|
3
|
+
import { runOutdatedCheck } from "../checks/outdated.js";
|
|
4
|
+
import { runRiskCheck } from "../checks/risk.js";
|
|
5
|
+
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
9
|
import { calculateHealthScore } from "./scorer.js";
|
|
10
|
+
import { buildAnalysisContext } from "./context.js";
|
|
10
11
|
export const OUTPUT_VERSION = "1.0";
|
|
11
12
|
export async function analyzeProject(options = {}) {
|
|
12
13
|
const rootDir = path.resolve(options.rootDir ?? process.cwd());
|
|
@@ -17,8 +18,9 @@ export async function analyzeProject(options = {}) {
|
|
|
17
18
|
return analyzeSingleProject(rootDir, config);
|
|
18
19
|
}
|
|
19
20
|
const rootGraph = await buildDependencyGraph(rootDir);
|
|
20
|
-
const
|
|
21
|
-
const
|
|
21
|
+
const duplicateCheck = await runDuplicateCheck(rootGraph);
|
|
22
|
+
const filteredDuplicateIssues = filterIssues(duplicateCheck.issues, "duplicates", config);
|
|
23
|
+
const duplicates = mapDuplicateIssues(filteredDuplicateIssues);
|
|
22
24
|
const packages = [];
|
|
23
25
|
for (const workspace of workspaces) {
|
|
24
26
|
const result = await analyzeSingleProject(workspace.rootDir, config, {
|
|
@@ -129,20 +131,13 @@ function evaluatePolicy(summary, config) {
|
|
|
129
131
|
};
|
|
130
132
|
}
|
|
131
133
|
async function analyzeSingleProject(rootDir, config, options = {}) {
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
findRiskDependencies(graph)
|
|
140
|
-
]);
|
|
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));
|
|
134
|
+
const context = await buildAnalysisContext(rootDir, config);
|
|
135
|
+
const results = await runChecks(context);
|
|
136
|
+
const issueGroups = normalizeIssues(results, config);
|
|
137
|
+
const duplicates = mapDuplicateIssues(issueGroups.duplicates);
|
|
138
|
+
const unused = mapUnusedIssues(issueGroups.unused);
|
|
139
|
+
const outdated = mapOutdatedIssues(issueGroups.outdated);
|
|
140
|
+
const risks = mapRiskIssues(issueGroups.risks);
|
|
146
141
|
const score = calculateHealthScore({
|
|
147
142
|
duplicates: duplicates.length,
|
|
148
143
|
unused: unused.length,
|
|
@@ -211,6 +206,89 @@ function shouldIgnorePackage(name, bucket, config) {
|
|
|
211
206
|
}
|
|
212
207
|
});
|
|
213
208
|
}
|
|
209
|
+
async function runChecks(context) {
|
|
210
|
+
const checks = [
|
|
211
|
+
{
|
|
212
|
+
name: "duplicate",
|
|
213
|
+
run: () => runDuplicateCheck(context.graph)
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "unused",
|
|
217
|
+
run: () => runUnusedCheck(context)
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "outdated",
|
|
221
|
+
run: () => runOutdatedCheck(context.graph)
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "risk",
|
|
225
|
+
run: () => runRiskCheck(context.graph)
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
const results = [];
|
|
229
|
+
for (const check of checks) {
|
|
230
|
+
results.push(await check.run());
|
|
231
|
+
}
|
|
232
|
+
return results;
|
|
233
|
+
}
|
|
234
|
+
function normalizeIssues(results, config) {
|
|
235
|
+
const map = new Map();
|
|
236
|
+
for (const result of results) {
|
|
237
|
+
map.set(result.name, result.issues);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
duplicates: filterIssues(map.get("duplicate") ?? [], "duplicates", config),
|
|
241
|
+
unused: filterIssues(map.get("unused") ?? [], "unused", config),
|
|
242
|
+
outdated: filterIssues(map.get("outdated") ?? [], "outdated", config),
|
|
243
|
+
risks: filterIssues(map.get("risk") ?? [], "risks", config)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function filterIssues(issues, bucket, config) {
|
|
247
|
+
return issues.filter((issue) => {
|
|
248
|
+
const name = typeof issue.meta?.name === "string" ? issue.meta.name : issue.package ?? "";
|
|
249
|
+
if (!name) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
if (bucket === "unused") {
|
|
253
|
+
const section = issue.meta?.section === "devDependencies" ? "devDependencies" : "dependencies";
|
|
254
|
+
if (shouldIgnorePackage(name, section, config)) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return !shouldIgnorePackage(name, bucket, config);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function mapDuplicateIssues(issues) {
|
|
262
|
+
return issues.map((issue) => ({
|
|
263
|
+
name: String(issue.meta?.name ?? issue.package ?? "unknown"),
|
|
264
|
+
versions: Array.isArray(issue.meta?.versions) ? issue.meta?.versions : [],
|
|
265
|
+
instances: Array.isArray(issue.meta?.instances)
|
|
266
|
+
? issue.meta?.instances
|
|
267
|
+
: []
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
function mapUnusedIssues(issues) {
|
|
271
|
+
return issues.map((issue) => ({
|
|
272
|
+
name: String(issue.meta?.name ?? issue.package ?? "unknown"),
|
|
273
|
+
section: issue.meta?.section === "devDependencies" ? "devDependencies" : "dependencies"
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
function mapOutdatedIssues(issues) {
|
|
277
|
+
return issues.map((issue) => ({
|
|
278
|
+
name: String(issue.meta?.name ?? issue.package ?? "unknown"),
|
|
279
|
+
current: String(issue.meta?.current ?? ""),
|
|
280
|
+
latest: String(issue.meta?.latest ?? ""),
|
|
281
|
+
updateType: issue.meta?.updateType === "major" || issue.meta?.updateType === "minor" || issue.meta?.updateType === "patch"
|
|
282
|
+
? issue.meta.updateType
|
|
283
|
+
: "unknown"
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
function mapRiskIssues(issues) {
|
|
287
|
+
return issues.map((issue) => ({
|
|
288
|
+
name: String(issue.meta?.name ?? issue.package ?? "unknown"),
|
|
289
|
+
reasons: Array.isArray(issue.meta?.reasons) ? issue.meta?.reasons : []
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
214
292
|
function buildScoreBreakdown(counts, config) {
|
|
215
293
|
return {
|
|
216
294
|
baseScore: 100,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { buildDependencyGraph } from "./graph-builder.js";
|
|
3
|
+
import { collectProjectFiles, readTextFile } from "../utils/file-parser.js";
|
|
4
|
+
const SOURCE_FILE_PATTERN = /\.(c|m)?(t|j)sx?$/;
|
|
5
|
+
export async function buildAnalysisContext(rootDir, config) {
|
|
6
|
+
const graph = await buildDependencyGraph(rootDir);
|
|
7
|
+
const projectFiles = await collectProjectFiles(rootDir, SOURCE_FILE_PATTERN, config.scan.excludePaths);
|
|
8
|
+
const fileEntries = await Promise.all(projectFiles.map(async (filePath) => ({
|
|
9
|
+
path: filePath,
|
|
10
|
+
content: await readTextFile(filePath)
|
|
11
|
+
})));
|
|
12
|
+
const sourceText = fileEntries.map((entry) => entry.content).join("\n");
|
|
13
|
+
const hasTypeScriptConfig = await hasFile(rootDir, "tsconfig.json");
|
|
14
|
+
return {
|
|
15
|
+
rootDir,
|
|
16
|
+
graph,
|
|
17
|
+
sourceText,
|
|
18
|
+
projectFiles,
|
|
19
|
+
fileEntries,
|
|
20
|
+
hasTypeScriptConfig
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function hasFile(rootDir, fileName) {
|
|
24
|
+
try {
|
|
25
|
+
await readTextFile(path.join(rootDir, fileName));
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type IssueSeverity = "info" | "warning" | "critical";
|
|
2
|
+
export type Issue = {
|
|
3
|
+
id: string;
|
|
4
|
+
message: string;
|
|
5
|
+
package?: string;
|
|
6
|
+
severity: IssueSeverity;
|
|
7
|
+
meta?: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
export type CheckResult = {
|
|
10
|
+
name: string;
|
|
11
|
+
issues: Issue[];
|
|
12
|
+
summary: string;
|
|
13
|
+
};
|
|
14
|
+
export type AnalysisContext = {
|
|
15
|
+
rootDir: string;
|
|
16
|
+
graph: import("./graph-builder.js").DependencyGraph;
|
|
17
|
+
sourceText: string;
|
|
18
|
+
projectFiles: string[];
|
|
19
|
+
fileEntries: {
|
|
20
|
+
path: string;
|
|
21
|
+
content: string;
|
|
22
|
+
}[];
|
|
23
|
+
hasTypeScriptConfig: boolean;
|
|
24
|
+
};
|
|
25
|
+
export type CheckRunner = {
|
|
26
|
+
name: string;
|
|
27
|
+
run: (context: AnalysisContext) => Promise<CheckResult>;
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { analyzeProject } from "./core/analyzer.js";
|
|
2
2
|
export type { AnalysisOptions, AnalysisResult, DuplicateDependency, OutdatedDependency, PolicyResult, PackageAnalysisResult, ScoreBreakdown, RiskDependency, UnusedDependency } from "./core/analyzer.js";
|
|
3
3
|
export { OUTPUT_VERSION } from "./core/analyzer.js";
|
|
4
|
+
export type { AnalysisContext, CheckResult, Issue } from "./core/types.js";
|
|
4
5
|
export type { DepBrainConfig, DepBrainConfigOverrides } from "./utils/config.js";
|
|
5
6
|
export type { WorkspacePackage } from "./utils/workspaces.js";
|