ghostdep 0.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/README.md +176 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +103 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/detectors/drift.d.ts +12 -0
- package/dist/src/detectors/drift.d.ts.map +1 -0
- package/dist/src/detectors/drift.js +95 -0
- package/dist/src/detectors/drift.js.map +1 -0
- package/dist/src/detectors/duplicates.d.ts +13 -0
- package/dist/src/detectors/duplicates.d.ts.map +1 -0
- package/dist/src/detectors/duplicates.js +151 -0
- package/dist/src/detectors/duplicates.js.map +1 -0
- package/dist/src/detectors/ghosts.d.ts +13 -0
- package/dist/src/detectors/ghosts.d.ts.map +1 -0
- package/dist/src/detectors/ghosts.js +209 -0
- package/dist/src/detectors/ghosts.js.map +1 -0
- package/dist/src/detectors/zombies.d.ts +22 -0
- package/dist/src/detectors/zombies.d.ts.map +1 -0
- package/dist/src/detectors/zombies.js +127 -0
- package/dist/src/detectors/zombies.js.map +1 -0
- package/dist/src/health.d.ts +22 -0
- package/dist/src/health.d.ts.map +1 -0
- package/dist/src/health.js +123 -0
- package/dist/src/health.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/reporter.d.ts +9 -0
- package/dist/src/reporter.d.ts.map +1 -0
- package/dist/src/reporter.js +115 -0
- package/dist/src/reporter.js.map +1 -0
- package/dist/src/scanner.d.ts +12 -0
- package/dist/src/scanner.d.ts.map +1 -0
- package/dist/src/scanner.js +62 -0
- package/dist/src/scanner.js.map +1 -0
- package/dist/src/types.d.ts +115 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/fs.d.ts +29 -0
- package/dist/src/utils/fs.d.ts.map +1 -0
- package/dist/src/utils/fs.js +72 -0
- package/dist/src/utils/fs.js.map +1 -0
- package/dist/src/utils/packageManager.d.ts +20 -0
- package/dist/src/utils/packageManager.d.ts.map +1 -0
- package/dist/src/utils/packageManager.js +74 -0
- package/dist/src/utils/packageManager.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
const BUILTINS = new Set([
|
|
4
|
+
'assert',
|
|
5
|
+
'async_hooks',
|
|
6
|
+
'buffer',
|
|
7
|
+
'child_process',
|
|
8
|
+
'cluster',
|
|
9
|
+
'console',
|
|
10
|
+
'constants',
|
|
11
|
+
'crypto',
|
|
12
|
+
'dgram',
|
|
13
|
+
'diagnostics_channel',
|
|
14
|
+
'dns',
|
|
15
|
+
'domain',
|
|
16
|
+
'events',
|
|
17
|
+
'fs',
|
|
18
|
+
'http',
|
|
19
|
+
'http2',
|
|
20
|
+
'https',
|
|
21
|
+
'inspector',
|
|
22
|
+
'module',
|
|
23
|
+
'net',
|
|
24
|
+
'os',
|
|
25
|
+
'path',
|
|
26
|
+
'perf_hooks',
|
|
27
|
+
'process',
|
|
28
|
+
'punycode',
|
|
29
|
+
'querystring',
|
|
30
|
+
'readline',
|
|
31
|
+
'repl',
|
|
32
|
+
'stream',
|
|
33
|
+
'string_decoder',
|
|
34
|
+
'sys',
|
|
35
|
+
'timers',
|
|
36
|
+
'tls',
|
|
37
|
+
'trace_events',
|
|
38
|
+
'tty',
|
|
39
|
+
'url',
|
|
40
|
+
'util',
|
|
41
|
+
'v8',
|
|
42
|
+
'vm',
|
|
43
|
+
'wasi',
|
|
44
|
+
'worker_threads',
|
|
45
|
+
'zlib',
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a package name is a Node.js built-in module.
|
|
49
|
+
*/
|
|
50
|
+
function isBuiltin(name) {
|
|
51
|
+
return name.startsWith('node:') || BUILTINS.has(name);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Extracts the core package name from an import specifier.
|
|
55
|
+
* E.g., "lodash/map" -> "lodash", "@types/node/index.d.ts" -> "@types/node"
|
|
56
|
+
*/
|
|
57
|
+
function getPackageName(specifier) {
|
|
58
|
+
if (!specifier || specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const parts = specifier.split('/');
|
|
62
|
+
if (specifier.startsWith('@')) {
|
|
63
|
+
return parts[0] && parts[1] ? `${parts[0]}/${parts[1]}` : null;
|
|
64
|
+
}
|
|
65
|
+
return parts[0] || null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Scans a file to extract all external import/require specifiers.
|
|
69
|
+
*/
|
|
70
|
+
async function extractImports(filePath) {
|
|
71
|
+
try {
|
|
72
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
73
|
+
const specifiers = new Set();
|
|
74
|
+
// ESM Imports & Exports matching
|
|
75
|
+
const esmImportRegex = /import\s+(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
76
|
+
const esmExportRegex = /export\s+(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
77
|
+
// CommonJS require & Dynamic imports matching
|
|
78
|
+
const dynamicOrCjsRegex = /(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
79
|
+
let match;
|
|
80
|
+
while ((match = esmImportRegex.exec(content)) !== null) {
|
|
81
|
+
if (match[1])
|
|
82
|
+
specifiers.add(match[1]);
|
|
83
|
+
}
|
|
84
|
+
while ((match = esmExportRegex.exec(content)) !== null) {
|
|
85
|
+
if (match[1])
|
|
86
|
+
specifiers.add(match[1]);
|
|
87
|
+
}
|
|
88
|
+
while ((match = dynamicOrCjsRegex.exec(content)) !== null) {
|
|
89
|
+
if (match[1])
|
|
90
|
+
specifiers.add(match[1]);
|
|
91
|
+
}
|
|
92
|
+
return Array.from(specifiers);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// If a file cannot be read, return empty imports
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Scans the node_modules folder to find all installed package names,
|
|
101
|
+
* ignoring internal folders starting with a dot (like .bin, .cache, .pnpm).
|
|
102
|
+
*/
|
|
103
|
+
async function getInstalledPackages(projectRoot) {
|
|
104
|
+
const installed = new Set();
|
|
105
|
+
const nodeModulesPath = join(projectRoot, 'node_modules');
|
|
106
|
+
try {
|
|
107
|
+
const entries = await fs.readdir(nodeModulesPath, { withFileTypes: true });
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (entry.isDirectory()) {
|
|
110
|
+
const name = entry.name;
|
|
111
|
+
// Ignore internal directories
|
|
112
|
+
if (name.startsWith('.')) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (name.startsWith('@')) {
|
|
116
|
+
// Read scope folder entries
|
|
117
|
+
const scopePath = join(nodeModulesPath, name);
|
|
118
|
+
try {
|
|
119
|
+
const subEntries = await fs.readdir(scopePath, { withFileTypes: true });
|
|
120
|
+
for (const subEntry of subEntries) {
|
|
121
|
+
if (subEntry.isDirectory()) {
|
|
122
|
+
installed.add(`${name}/${subEntry.name}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Ignore reading sub-scopes failure
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
installed.add(name);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Return empty set if node_modules does not exist
|
|
138
|
+
}
|
|
139
|
+
return installed;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Detects ghost dependencies: packages imported in the source code
|
|
143
|
+
* that are installed in node_modules but not listed in package.json.
|
|
144
|
+
*
|
|
145
|
+
* @param projectRoot Absolute path to the project root directory
|
|
146
|
+
* @param sourceFiles List of absolute paths of source files to analyze
|
|
147
|
+
* @param packageJson Parsed package.json object
|
|
148
|
+
* @param includeDev Whether to include devDependencies as declared packages
|
|
149
|
+
* @returns Array of detected ghost dependencies
|
|
150
|
+
*/
|
|
151
|
+
export async function detectGhosts(projectRoot, sourceFiles, packageJson, includeDev = false) {
|
|
152
|
+
try {
|
|
153
|
+
// 1. Gather all declared package names from package.json
|
|
154
|
+
const declaredPackages = new Set();
|
|
155
|
+
if (packageJson.dependencies) {
|
|
156
|
+
Object.keys(packageJson.dependencies).forEach((dep) => declaredPackages.add(dep));
|
|
157
|
+
}
|
|
158
|
+
if (includeDev && packageJson.devDependencies) {
|
|
159
|
+
Object.keys(packageJson.devDependencies).forEach((dep) => declaredPackages.add(dep));
|
|
160
|
+
}
|
|
161
|
+
if (packageJson.peerDependencies) {
|
|
162
|
+
Object.keys(packageJson.peerDependencies).forEach((dep) => declaredPackages.add(dep));
|
|
163
|
+
}
|
|
164
|
+
if (packageJson.optionalDependencies) {
|
|
165
|
+
Object.keys(packageJson.optionalDependencies).forEach((dep) => declaredPackages.add(dep));
|
|
166
|
+
}
|
|
167
|
+
// 2. Identify all installed packages in node_modules
|
|
168
|
+
const installedPackages = await getInstalledPackages(projectRoot);
|
|
169
|
+
// 3. Scan source files for imports and identify undeclared usages
|
|
170
|
+
const ghostMap = new Map();
|
|
171
|
+
for (const file of sourceFiles) {
|
|
172
|
+
const cleanRoot = projectRoot.replace(/\\/g, '/');
|
|
173
|
+
const cleanFile = file.replace(/\\/g, '/');
|
|
174
|
+
const relativePath = cleanFile.replace(cleanRoot, '').replace(/^[\\/]/, '');
|
|
175
|
+
const importSpecifiers = await extractImports(file);
|
|
176
|
+
for (const specifier of importSpecifiers) {
|
|
177
|
+
const pkgName = getPackageName(specifier);
|
|
178
|
+
if (!pkgName || isBuiltin(pkgName)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// It is a ghost dependency if it is NOT declared, but is installed in node_modules
|
|
182
|
+
if (!declaredPackages.has(pkgName) && installedPackages.has(pkgName)) {
|
|
183
|
+
let entry = ghostMap.get(pkgName);
|
|
184
|
+
if (!entry) {
|
|
185
|
+
entry = { files: new Set(), specifiers: new Set() };
|
|
186
|
+
ghostMap.set(pkgName, entry);
|
|
187
|
+
}
|
|
188
|
+
entry.files.add(relativePath);
|
|
189
|
+
entry.specifiers.add(specifier);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// 4. Map the results into the typed output
|
|
194
|
+
const results = [];
|
|
195
|
+
for (const [name, data] of ghostMap.entries()) {
|
|
196
|
+
results.push({
|
|
197
|
+
name,
|
|
198
|
+
files: Array.from(data.files),
|
|
199
|
+
specifiers: Array.from(data.specifiers),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
const err = error;
|
|
206
|
+
throw new Error(`Ghost dependency detection failed: ${err.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=ghosts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ghosts.js","sourceRoot":"","sources":["../../../src/detectors/ghosts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ZombieDependency, PackageJson } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses a TS/JS file using the TypeScript Compiler API AST,
|
|
4
|
+
* returning a Set of all imported package specifiers.
|
|
5
|
+
*
|
|
6
|
+
* Supports:
|
|
7
|
+
* - import declarations: static imports and re-exports
|
|
8
|
+
* - dynamic imports: import('pkg')
|
|
9
|
+
* - CommonJS require statements
|
|
10
|
+
*/
|
|
11
|
+
export declare function findImportsInAST(filePath: string, fileContent: string): Set<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Detects zombie dependencies: packages declared in package.json dependencies list
|
|
14
|
+
* but never imported or required in the codebase.
|
|
15
|
+
*
|
|
16
|
+
* @param sourceFiles Absolute paths of all source files in the project
|
|
17
|
+
* @param packageJson Parsed package.json object
|
|
18
|
+
* @param includeDev Whether to scan and include devDependencies as candidate zombies
|
|
19
|
+
* @returns Array of ZombieDependency findings
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectZombies(sourceFiles: string[], packageJson: PackageJson, includeDev?: boolean): Promise<ZombieDependency[]>;
|
|
22
|
+
//# sourceMappingURL=zombies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zombies.d.ts","sourceRoot":"","sources":["../../../src/detectors/zombies.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAkB5D;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA4CnF;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EAAE,EACrB,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,OAAe,GAC1B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyD7B"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the base package name from an import specifier.
|
|
5
|
+
* E.g., "lodash/map" -> "lodash", "@types/node" -> "@types/node"
|
|
6
|
+
*/
|
|
7
|
+
function getPackageName(specifier) {
|
|
8
|
+
if (!specifier || specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const parts = specifier.split('/');
|
|
12
|
+
if (specifier.startsWith('@')) {
|
|
13
|
+
return parts[0] && parts[1] ? `${parts[0]}/${parts[1]}` : null;
|
|
14
|
+
}
|
|
15
|
+
return parts[0] || null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parses a TS/JS file using the TypeScript Compiler API AST,
|
|
19
|
+
* returning a Set of all imported package specifiers.
|
|
20
|
+
*
|
|
21
|
+
* Supports:
|
|
22
|
+
* - import declarations: static imports and re-exports
|
|
23
|
+
* - dynamic imports: import('pkg')
|
|
24
|
+
* - CommonJS require statements
|
|
25
|
+
*/
|
|
26
|
+
export function findImportsInAST(filePath, fileContent) {
|
|
27
|
+
const imports = new Set();
|
|
28
|
+
// Create AST representation of the source file
|
|
29
|
+
const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true);
|
|
30
|
+
function visit(node) {
|
|
31
|
+
// 1. Static imports: e.g. import x from 'y'
|
|
32
|
+
if (ts.isImportDeclaration(node)) {
|
|
33
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
34
|
+
imports.add(node.moduleSpecifier.text);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// 2. Export re-exports: e.g. export * from 'y'
|
|
38
|
+
else if (ts.isExportDeclaration(node)) {
|
|
39
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
40
|
+
imports.add(node.moduleSpecifier.text);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// 3. Dynamic imports or CJS require expressions
|
|
44
|
+
else if (ts.isCallExpression(node)) {
|
|
45
|
+
const expression = node.expression;
|
|
46
|
+
// Dynamic import: import('y')
|
|
47
|
+
if (expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
48
|
+
const arg = node.arguments[0];
|
|
49
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
50
|
+
imports.add(arg.text);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// require('y')
|
|
54
|
+
else if (ts.isIdentifier(expression) && expression.text === 'require') {
|
|
55
|
+
const arg = node.arguments[0];
|
|
56
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
57
|
+
imports.add(arg.text);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
ts.forEachChild(node, visit);
|
|
62
|
+
}
|
|
63
|
+
visit(sourceFile);
|
|
64
|
+
return imports;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Detects zombie dependencies: packages declared in package.json dependencies list
|
|
68
|
+
* but never imported or required in the codebase.
|
|
69
|
+
*
|
|
70
|
+
* @param sourceFiles Absolute paths of all source files in the project
|
|
71
|
+
* @param packageJson Parsed package.json object
|
|
72
|
+
* @param includeDev Whether to scan and include devDependencies as candidate zombies
|
|
73
|
+
* @returns Array of ZombieDependency findings
|
|
74
|
+
*/
|
|
75
|
+
export async function detectZombies(sourceFiles, packageJson, includeDev = false) {
|
|
76
|
+
try {
|
|
77
|
+
// 1. Map all candidate declared dependencies to check
|
|
78
|
+
const declaredMap = new Map();
|
|
79
|
+
const addCandidates = (deps, type) => {
|
|
80
|
+
if (deps) {
|
|
81
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
82
|
+
declaredMap.set(name, { type, declaredVersion: version });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
addCandidates(packageJson.dependencies, 'dependencies');
|
|
87
|
+
if (includeDev) {
|
|
88
|
+
addCandidates(packageJson.devDependencies, 'devDependencies');
|
|
89
|
+
}
|
|
90
|
+
addCandidates(packageJson.peerDependencies, 'peerDependencies');
|
|
91
|
+
addCandidates(packageJson.optionalDependencies, 'optionalDependencies');
|
|
92
|
+
// 2. Traverse all source files to find all imports
|
|
93
|
+
const usedPackages = new Set();
|
|
94
|
+
for (const file of sourceFiles) {
|
|
95
|
+
try {
|
|
96
|
+
const content = await fs.readFile(file, 'utf8');
|
|
97
|
+
const fileImports = findImportsInAST(file, content);
|
|
98
|
+
for (const specifier of fileImports) {
|
|
99
|
+
const pkgName = getPackageName(specifier);
|
|
100
|
+
if (pkgName) {
|
|
101
|
+
usedPackages.add(pkgName);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Skip file quietly if reading/parsing fails (e.g. binary or empty file)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 3. Find packages that are declared but never used in any scanned source file
|
|
110
|
+
const zombies = [];
|
|
111
|
+
for (const [name, details] of declaredMap.entries()) {
|
|
112
|
+
if (!usedPackages.has(name)) {
|
|
113
|
+
zombies.push({
|
|
114
|
+
name,
|
|
115
|
+
type: details.type,
|
|
116
|
+
declaredVersion: details.declaredVersion,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return zombies;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const err = error;
|
|
124
|
+
throw new Error(`Zombie dependency detection failed: ${err.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=zombies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zombies.js","sourceRoot":"","sources":["../../../src/detectors/zombies.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HealthScore, GhostDependency, ZombieDependency, DuplicateDependency, VersionDrift, ScanOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Calculates the dependency health score and grade of a project.
|
|
4
|
+
*
|
|
5
|
+
* Score Formula:
|
|
6
|
+
* - Starting Base: 100
|
|
7
|
+
* - Deductions:
|
|
8
|
+
* - -5 per ghost dependency
|
|
9
|
+
* - -5 per zombie dependency
|
|
10
|
+
* - -3 per duplicate dependency
|
|
11
|
+
* - -2 per version drift (by default)
|
|
12
|
+
* - Minimum bounded score: 0
|
|
13
|
+
*
|
|
14
|
+
* @param ghosts List of detected ghost dependencies
|
|
15
|
+
* @param zombies List of detected zombie dependencies
|
|
16
|
+
* @param duplicates List of detected duplicate dependencies
|
|
17
|
+
* @param drifts List of detected version drift issues
|
|
18
|
+
* @param customPenalties Optional custom penalty configuration override
|
|
19
|
+
* @returns Fully computed HealthScore object
|
|
20
|
+
*/
|
|
21
|
+
export declare function calculateHealthScore(ghosts: GhostDependency[], zombies: ZombieDependency[], duplicates: DuplicateDependency[], drifts: VersionDrift[], customPenalties?: ScanOptions['penalties']): HealthScore;
|
|
22
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/health.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EAEZ,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,eAAe,EAAE,EACzB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,UAAU,EAAE,mBAAmB,EAAE,EACjC,MAAM,EAAE,YAAY,EAAE,EACtB,eAAe,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,GACzC,WAAW,CAsGb"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the dependency health score and grade of a project.
|
|
3
|
+
*
|
|
4
|
+
* Score Formula:
|
|
5
|
+
* - Starting Base: 100
|
|
6
|
+
* - Deductions:
|
|
7
|
+
* - -5 per ghost dependency
|
|
8
|
+
* - -5 per zombie dependency
|
|
9
|
+
* - -3 per duplicate dependency
|
|
10
|
+
* - -2 per version drift (by default)
|
|
11
|
+
* - Minimum bounded score: 0
|
|
12
|
+
*
|
|
13
|
+
* @param ghosts List of detected ghost dependencies
|
|
14
|
+
* @param zombies List of detected zombie dependencies
|
|
15
|
+
* @param duplicates List of detected duplicate dependencies
|
|
16
|
+
* @param drifts List of detected version drift issues
|
|
17
|
+
* @param customPenalties Optional custom penalty configuration override
|
|
18
|
+
* @returns Fully computed HealthScore object
|
|
19
|
+
*/
|
|
20
|
+
export function calculateHealthScore(ghosts, zombies, duplicates, drifts, customPenalties) {
|
|
21
|
+
const deductions = [];
|
|
22
|
+
let score = 100;
|
|
23
|
+
// 1. Ghost Dependencies deduction
|
|
24
|
+
if (ghosts.length > 0) {
|
|
25
|
+
const penaltyPerGhost = customPenalties?.ghost ?? 5;
|
|
26
|
+
const penalty = ghosts.length * penaltyPerGhost;
|
|
27
|
+
deductions.push({
|
|
28
|
+
reason: `${ghosts.length} ghost dependenc${ghosts.length === 1 ? 'y' : 'ies'} detected`,
|
|
29
|
+
penalty,
|
|
30
|
+
});
|
|
31
|
+
score -= penalty;
|
|
32
|
+
}
|
|
33
|
+
// 2. Zombie Dependencies deduction
|
|
34
|
+
if (zombies.length > 0) {
|
|
35
|
+
const penaltyPerZombie = customPenalties?.zombie ?? 5;
|
|
36
|
+
const penalty = zombies.length * penaltyPerZombie;
|
|
37
|
+
deductions.push({
|
|
38
|
+
reason: `${zombies.length} zombie dependenc${zombies.length === 1 ? 'y' : 'ies'} detected`,
|
|
39
|
+
penalty,
|
|
40
|
+
});
|
|
41
|
+
score -= penalty;
|
|
42
|
+
}
|
|
43
|
+
// 3. Duplicate Dependencies deduction
|
|
44
|
+
if (duplicates.length > 0) {
|
|
45
|
+
const penaltyPerDuplicate = customPenalties?.duplicate ?? 3;
|
|
46
|
+
const penalty = duplicates.length * penaltyPerDuplicate;
|
|
47
|
+
deductions.push({
|
|
48
|
+
reason: `${duplicates.length} duplicate package${duplicates.length === 1 ? '' : 's'} detected`,
|
|
49
|
+
penalty,
|
|
50
|
+
});
|
|
51
|
+
score -= penalty;
|
|
52
|
+
}
|
|
53
|
+
// 4. Version Drift deduction (aggregating major, minor, patch, and missing)
|
|
54
|
+
if (drifts.length > 0) {
|
|
55
|
+
let driftPenaltyTotal = 0;
|
|
56
|
+
let majorCount = 0;
|
|
57
|
+
let minorCount = 0;
|
|
58
|
+
let patchCount = 0;
|
|
59
|
+
let missingCount = 0;
|
|
60
|
+
for (const drift of drifts) {
|
|
61
|
+
if (drift.driftType === 'major') {
|
|
62
|
+
driftPenaltyTotal += customPenalties?.driftMajor ?? 2;
|
|
63
|
+
majorCount++;
|
|
64
|
+
}
|
|
65
|
+
else if (drift.driftType === 'minor') {
|
|
66
|
+
driftPenaltyTotal += customPenalties?.driftMinor ?? 2;
|
|
67
|
+
minorCount++;
|
|
68
|
+
}
|
|
69
|
+
else if (drift.driftType === 'patch') {
|
|
70
|
+
driftPenaltyTotal += customPenalties?.driftPatch ?? 2;
|
|
71
|
+
patchCount++;
|
|
72
|
+
}
|
|
73
|
+
else if (drift.driftType === 'missing') {
|
|
74
|
+
driftPenaltyTotal += customPenalties?.missing ?? 2;
|
|
75
|
+
missingCount++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (driftPenaltyTotal > 0) {
|
|
79
|
+
const details = [];
|
|
80
|
+
if (majorCount > 0)
|
|
81
|
+
details.push(`${majorCount} major`);
|
|
82
|
+
if (minorCount > 0)
|
|
83
|
+
details.push(`${minorCount} minor`);
|
|
84
|
+
if (patchCount > 0)
|
|
85
|
+
details.push(`${patchCount} patch`);
|
|
86
|
+
if (missingCount > 0)
|
|
87
|
+
details.push(`${missingCount} missing`);
|
|
88
|
+
deductions.push({
|
|
89
|
+
reason: `${drifts.length} version drift${drifts.length === 1 ? '' : 's'} (${details.join(', ')})`,
|
|
90
|
+
penalty: driftPenaltyTotal,
|
|
91
|
+
});
|
|
92
|
+
score -= driftPenaltyTotal;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Limit score to a minimum of 0
|
|
96
|
+
score = Math.max(0, score);
|
|
97
|
+
// Grade Assignment
|
|
98
|
+
let grade = 'F';
|
|
99
|
+
if (score >= 90) {
|
|
100
|
+
grade = 'A';
|
|
101
|
+
}
|
|
102
|
+
else if (score >= 80) {
|
|
103
|
+
grade = 'B';
|
|
104
|
+
}
|
|
105
|
+
else if (score >= 70) {
|
|
106
|
+
grade = 'C';
|
|
107
|
+
}
|
|
108
|
+
else if (score >= 60) {
|
|
109
|
+
grade = 'D';
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
score,
|
|
113
|
+
grade,
|
|
114
|
+
metrics: {
|
|
115
|
+
ghosts: ghosts.length,
|
|
116
|
+
zombies: zombies.length,
|
|
117
|
+
duplicates: duplicates.length,
|
|
118
|
+
drifts: drifts.length,
|
|
119
|
+
},
|
|
120
|
+
deductions,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ScanReport } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Outputs a beautifully formatted CLI report summarizing the dependency audit scan results,
|
|
4
|
+
* color-coded by issue severity using chalk.
|
|
5
|
+
*
|
|
6
|
+
* @param report The final ScanReport resulting from the scan
|
|
7
|
+
*/
|
|
8
|
+
export declare function reportToConsole(report: ScanReport): void;
|
|
9
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/reporter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CA2HxD"}
|