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
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# ghostdep 👻
|
|
2
|
+
|
|
3
|
+
[](https://github.com/yourusername/ghostdep/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/ghostdep)
|
|
5
|
+
[](https://github.com/yourusername/ghostdep/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
An ultra-fast, zero-config **npm dependency scanner** and **nodejs tooling** utility to analyze, audit, and optimize your project dependencies. Instantly audit project health, calculate a dependency health score, and prune duplicate or version drift mismatches.
|
|
8
|
+
|
|
9
|
+
Use **ghostdep** to optimize your dependency tree, secure build-time scripts, and clean up your `package.json` configurations.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 📖 Table of Contents
|
|
14
|
+
- [Introduction](#-introduction)
|
|
15
|
+
- [Features](#-features)
|
|
16
|
+
- [Installation](#-installation)
|
|
17
|
+
- [CLI Commands & Usage](#-cli-commands--usage)
|
|
18
|
+
- [Examples](#-examples)
|
|
19
|
+
- [Programmatic API](#-programmatic-api)
|
|
20
|
+
- [FAQ](#-faq)
|
|
21
|
+
- [Contributing](#-contributing)
|
|
22
|
+
- [License](#-license)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🌟 Introduction
|
|
27
|
+
|
|
28
|
+
In modern Node.js and TypeScript codebases, dependencies can rapidly drift. Issues like **ghost dependencies** (importing packages in your code that are not declared in `package.json`) and **unused dependencies** (packages declared but never actually used) bloat lockfiles, slow down installation times, and cause runtime crashes in containerized/production environments.
|
|
29
|
+
|
|
30
|
+
`ghostdep` resolves these problems by scanning your source code, comparing actual imports with your `package.json` declarations, walking the `node_modules` hierarchy, and calculating a precise **dependency health score**.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ✨ Features
|
|
35
|
+
|
|
36
|
+
- 👻 **Ghost Dependency Detection**: Identify packages installed in `node_modules` and used in code, but missing from `package.json`.
|
|
37
|
+
- 🧟 **Zombie Dependency Detection**: Spot **unused dependencies** that are declared but never imported or required in your codebase.
|
|
38
|
+
- 📦 **Duplicate Package Finder**: Scan the active node_modules tree (with full PNPM symlink cycle protection) to identify package bloat from multiple resolved versions.
|
|
39
|
+
- 🔄 **Version Drift Auditing**: Compare declared semver ranges in `package.json` with actual installed versions to trace major, minor, or patch drifts.
|
|
40
|
+
- 📊 **Dependency Health Score**: Get an automated quality grade (A-F) based on customizable penalties.
|
|
41
|
+
- ⚡ **TS Compiler AST Parser**: Scans code utilizing the TypeScript Compiler API for ESM imports/exports, dynamic imports, and CommonJS require statements.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🚀 Installation
|
|
46
|
+
|
|
47
|
+
Install globally to use the CLI anywhere:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install -g ghostdep
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or install locally in your project:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install --save-dev ghostdep
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 💻 CLI Commands & Usage
|
|
62
|
+
|
|
63
|
+
Run the scanner directly from your project folder:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx ghostdep
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Options
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Usage: ghostdep [options] [command]
|
|
73
|
+
|
|
74
|
+
Audit Node.js/TypeScript projects for ghost, zombie, duplicate, and drifted dependencies.
|
|
75
|
+
|
|
76
|
+
Options:
|
|
77
|
+
-V, --version output the version number
|
|
78
|
+
-d, --cwd <path> Working directory of the target project (default: current directory)
|
|
79
|
+
--include-dev Include devDependencies in analysis checks (default: false)
|
|
80
|
+
--include <patterns> Comma-separated glob patterns of source files to include
|
|
81
|
+
--exclude <patterns> Comma-separated glob patterns of source files to ignore
|
|
82
|
+
-h, --help display help for command
|
|
83
|
+
|
|
84
|
+
Commands:
|
|
85
|
+
scan Scan project dependencies and print a detailed console report (default)
|
|
86
|
+
json Scan project dependencies and output a raw JSON report
|
|
87
|
+
health Scan project dependencies and print only the health score & grade summary
|
|
88
|
+
help [command] display help for command
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 📝 Examples
|
|
94
|
+
|
|
95
|
+
### Pretty Report Output
|
|
96
|
+
To execute a standard audit scan in the current directory and display a clean terminal report:
|
|
97
|
+
```bash
|
|
98
|
+
npx ghostdep scan
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### CI/CD Automation (JSON Output)
|
|
102
|
+
To capture a full structured JSON report file for logging or build pipeline consumption:
|
|
103
|
+
```bash
|
|
104
|
+
npx ghostdep json > report.json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Quick Score Summary
|
|
108
|
+
To query just the final computed health grade of the codebase:
|
|
109
|
+
```bash
|
|
110
|
+
npx ghostdep health
|
|
111
|
+
# Output: Health Score: 95/100 (A)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## ⚙️ Programmatic API
|
|
117
|
+
|
|
118
|
+
You can import `ghostdep` directly into your Node.js/TypeScript scripts:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { runScan } from 'ghostdep';
|
|
122
|
+
|
|
123
|
+
async function audit() {
|
|
124
|
+
const report = await runScan({
|
|
125
|
+
cwd: '/path/to/project',
|
|
126
|
+
includeDev: true,
|
|
127
|
+
exclude: ['**/test/**', '**/dist/**']
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
console.log(`Project: ${report.project.name} (v${report.project.version})`);
|
|
131
|
+
console.log(`Overall Score: ${report.health.score}% (${report.health.grade})`);
|
|
132
|
+
|
|
133
|
+
if (report.ghosts.length > 0) {
|
|
134
|
+
console.log('Detected Ghost dependencies:', report.ghosts.map(g => g.name));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
audit();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ❓ FAQ
|
|
144
|
+
|
|
145
|
+
### What are "Ghost Dependencies"?
|
|
146
|
+
Ghost dependencies are packages that your code imports (e.g. `import axios from 'axios'`), but which are not listed in your `package.json` dependencies. This often happens because the package is installed transitively by another dependency, meaning your code works locally but will fail when deployed or run in environments with flat node_modules trees.
|
|
147
|
+
|
|
148
|
+
### What are "Zombie Dependencies"?
|
|
149
|
+
Zombie dependencies are the opposite: they are listed in your `package.json` file but are never imported or required in your codebase. These waste disk space, slow down `npm install` times, and create security/maintenance overhead.
|
|
150
|
+
|
|
151
|
+
### Does it support PNPM and Yarn?
|
|
152
|
+
Yes! `ghostdep` implements a symlink-resolving walk algorithm that successfully navigates PNPM virtual stores (`.pnpm`) and Yarn workspaces without infinite loops or duplicate evaluation of symlinks.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 🤝 Contributing
|
|
157
|
+
|
|
158
|
+
Contributions are welcome! Please follow these steps:
|
|
159
|
+
1. Fork the repository.
|
|
160
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`).
|
|
161
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`).
|
|
162
|
+
4. Push to the branch (`git push origin feature/amazing-feature`).
|
|
163
|
+
5. Open a Pull Request.
|
|
164
|
+
|
|
165
|
+
Make sure to run formatting and test suites before committing:
|
|
166
|
+
```bash
|
|
167
|
+
npm run format
|
|
168
|
+
npm run lint
|
|
169
|
+
npm test
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 📄 License
|
|
175
|
+
|
|
176
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":""}
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { runScan } from '../src/scanner.js';
|
|
5
|
+
import { reportToConsole } from '../src/reporter.js';
|
|
6
|
+
const program = new Command();
|
|
7
|
+
// Setup CLI defaults, options, and help metadata
|
|
8
|
+
program
|
|
9
|
+
.name('ghostdep')
|
|
10
|
+
.description('Audit Node.js/TypeScript projects for ghost, zombie, duplicate, and drifted dependencies.')
|
|
11
|
+
.version('0.1.0')
|
|
12
|
+
.option('-d, --cwd <path>', 'Working directory of the target project', process.cwd())
|
|
13
|
+
.option('--include-dev', 'Include devDependencies in analysis checks', false)
|
|
14
|
+
.option('--include <patterns>', 'Comma-separated glob patterns of source files to include')
|
|
15
|
+
.option('--exclude <patterns>', 'Comma-separated glob patterns of source files to ignore');
|
|
16
|
+
/**
|
|
17
|
+
* Utility helper to parse comma-separated CLI inputs into string arrays.
|
|
18
|
+
*/
|
|
19
|
+
function parseList(value) {
|
|
20
|
+
return value ? value.split(',').map((item) => item.trim()) : undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Common execution runner that triggers the scanner and formats the report.
|
|
24
|
+
*/
|
|
25
|
+
async function executeScan(format) {
|
|
26
|
+
const options = program.opts();
|
|
27
|
+
try {
|
|
28
|
+
const scanOptions = {
|
|
29
|
+
cwd: options['cwd'] || process.cwd(),
|
|
30
|
+
includeDev: !!options['includeDev'],
|
|
31
|
+
};
|
|
32
|
+
const includeList = parseList(options['include']);
|
|
33
|
+
if (includeList) {
|
|
34
|
+
scanOptions.include = includeList;
|
|
35
|
+
}
|
|
36
|
+
const excludeList = parseList(options['exclude']);
|
|
37
|
+
if (excludeList) {
|
|
38
|
+
scanOptions.exclude = excludeList;
|
|
39
|
+
}
|
|
40
|
+
// Import the type ScanOptions inside bin/cli.ts
|
|
41
|
+
const report = await runScan(scanOptions);
|
|
42
|
+
if (format === 'json') {
|
|
43
|
+
console.log(JSON.stringify(report, null, 2));
|
|
44
|
+
}
|
|
45
|
+
else if (format === 'health') {
|
|
46
|
+
const score = report.health.score;
|
|
47
|
+
const grade = report.health.grade;
|
|
48
|
+
const gradeColor = grade === 'A' || grade === 'B'
|
|
49
|
+
? chalk.bold.green
|
|
50
|
+
: grade === 'C' || grade === 'D'
|
|
51
|
+
? chalk.bold.yellow
|
|
52
|
+
: chalk.bold.red;
|
|
53
|
+
const scoreColor = score >= 90
|
|
54
|
+
? chalk.bold.green
|
|
55
|
+
: score >= 75
|
|
56
|
+
? chalk.bold.cyan
|
|
57
|
+
: score >= 60
|
|
58
|
+
? chalk.bold.yellow
|
|
59
|
+
: chalk.bold.red;
|
|
60
|
+
console.log(`Health Score: ${scoreColor(score)}/100 (${gradeColor(grade)})`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
reportToConsole(report);
|
|
64
|
+
}
|
|
65
|
+
// Set non-zero exit code if critical issue (ghost dependencies) is discovered
|
|
66
|
+
if (report.ghosts.length > 0) {
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const err = error;
|
|
72
|
+
console.error(chalk.red.bold(`\nError: ${err.message}`));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// 1. Default action (when no subcommand is passed)
|
|
77
|
+
program.action(async () => {
|
|
78
|
+
await executeScan('pretty');
|
|
79
|
+
});
|
|
80
|
+
// 2. "scan" subcommand (same behavior as default, but explicit)
|
|
81
|
+
program
|
|
82
|
+
.command('scan')
|
|
83
|
+
.description('Scan project dependencies and print a detailed console report (default action)')
|
|
84
|
+
.action(async () => {
|
|
85
|
+
await executeScan('pretty');
|
|
86
|
+
});
|
|
87
|
+
// 3. "json" subcommand (outputs raw JSON report for CI/pipeline consumption)
|
|
88
|
+
program
|
|
89
|
+
.command('json')
|
|
90
|
+
.description('Scan project dependencies and output a raw JSON report')
|
|
91
|
+
.action(async () => {
|
|
92
|
+
await executeScan('json');
|
|
93
|
+
});
|
|
94
|
+
// 4. "health" subcommand (outputs only the score summary)
|
|
95
|
+
program
|
|
96
|
+
.command('health')
|
|
97
|
+
.description('Scan project dependencies and print only the health score & grade summary')
|
|
98
|
+
.action(async () => {
|
|
99
|
+
await executeScan('health');
|
|
100
|
+
});
|
|
101
|
+
// Run parser
|
|
102
|
+
program.parse(process.argv);
|
|
103
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { VersionDrift, PackageJson } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detects version drift issues: discrepancies between the declared semver ranges
|
|
4
|
+
* in package.json and the actual installed package versions in node_modules.
|
|
5
|
+
*
|
|
6
|
+
* @param projectRoot Absolute path to the project root directory
|
|
7
|
+
* @param packageJson Parsed package.json object
|
|
8
|
+
* @param includeDev Whether to include devDependencies in the drift check
|
|
9
|
+
* @returns Array of VersionDrift results
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectDrifts(projectRoot: string, packageJson: PackageJson, includeDev?: boolean): Promise<VersionDrift[]>;
|
|
12
|
+
//# sourceMappingURL=drift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../../../src/detectors/drift.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAqDxD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,OAAe,GAC1B,OAAO,CAAC,YAAY,EAAE,CAAC,CA4CzB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the installed version of a package by reading its package.json inside node_modules.
|
|
6
|
+
*/
|
|
7
|
+
async function getInstalledVersion(projectRoot, packageName) {
|
|
8
|
+
const packageJsonPath = join(projectRoot, 'node_modules', packageName, 'package.json');
|
|
9
|
+
try {
|
|
10
|
+
const content = await fs.readFile(packageJsonPath, 'utf8');
|
|
11
|
+
const pkg = JSON.parse(content);
|
|
12
|
+
return pkg.version || null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Calculates the semver drift type between a declared range and the installed version.
|
|
20
|
+
*/
|
|
21
|
+
function calculateDriftType(declaredRange, installedVersion) {
|
|
22
|
+
if (!installedVersion) {
|
|
23
|
+
return 'missing';
|
|
24
|
+
}
|
|
25
|
+
// If the range is not valid semver (e.g. workspace:*, git URL, file path, latest)
|
|
26
|
+
if (!semver.validRange(declaredRange)) {
|
|
27
|
+
// Exact string match comparison fallback
|
|
28
|
+
return declaredRange === installedVersion ? 'none' : 'major';
|
|
29
|
+
}
|
|
30
|
+
const minVersion = semver.minVersion(declaredRange);
|
|
31
|
+
if (!minVersion) {
|
|
32
|
+
return 'none';
|
|
33
|
+
}
|
|
34
|
+
const diff = semver.diff(minVersion.version, installedVersion);
|
|
35
|
+
if (!diff) {
|
|
36
|
+
return 'none';
|
|
37
|
+
}
|
|
38
|
+
if (diff === 'major' || diff === 'premajor') {
|
|
39
|
+
return 'major';
|
|
40
|
+
}
|
|
41
|
+
if (diff === 'minor' || diff === 'preminor') {
|
|
42
|
+
return 'minor';
|
|
43
|
+
}
|
|
44
|
+
if (diff === 'patch' || diff === 'prepatch' || diff === 'prerelease') {
|
|
45
|
+
return 'patch';
|
|
46
|
+
}
|
|
47
|
+
return 'none';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Detects version drift issues: discrepancies between the declared semver ranges
|
|
51
|
+
* in package.json and the actual installed package versions in node_modules.
|
|
52
|
+
*
|
|
53
|
+
* @param projectRoot Absolute path to the project root directory
|
|
54
|
+
* @param packageJson Parsed package.json object
|
|
55
|
+
* @param includeDev Whether to include devDependencies in the drift check
|
|
56
|
+
* @returns Array of VersionDrift results
|
|
57
|
+
*/
|
|
58
|
+
export async function detectDrifts(projectRoot, packageJson, includeDev = false) {
|
|
59
|
+
try {
|
|
60
|
+
const candidates = [];
|
|
61
|
+
const addCandidates = (deps, dependencyType) => {
|
|
62
|
+
if (deps) {
|
|
63
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
64
|
+
candidates.push({ name, declaredVersion: version, dependencyType });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
addCandidates(packageJson.dependencies, 'dependencies');
|
|
69
|
+
if (includeDev) {
|
|
70
|
+
addCandidates(packageJson.devDependencies, 'devDependencies');
|
|
71
|
+
}
|
|
72
|
+
addCandidates(packageJson.peerDependencies, 'peerDependencies');
|
|
73
|
+
addCandidates(packageJson.optionalDependencies, 'optionalDependencies');
|
|
74
|
+
const drifts = [];
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
const installedVersion = await getInstalledVersion(projectRoot, candidate.name);
|
|
77
|
+
const driftType = calculateDriftType(candidate.declaredVersion, installedVersion);
|
|
78
|
+
if (driftType !== 'none') {
|
|
79
|
+
drifts.push({
|
|
80
|
+
name: candidate.name,
|
|
81
|
+
dependencyType: candidate.dependencyType,
|
|
82
|
+
declaredVersion: candidate.declaredVersion,
|
|
83
|
+
installedVersion,
|
|
84
|
+
driftType,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return drifts;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const err = error;
|
|
92
|
+
throw new Error(`Version drift detection failed: ${err.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=drift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift.js","sourceRoot":"","sources":["../../../src/detectors/drift.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DuplicateDependency } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detects packages with duplicate versions installed under node_modules.
|
|
4
|
+
*
|
|
5
|
+
* Works cross-platform and supports npm, pnpm, and yarn by walking the active
|
|
6
|
+
* dependency graph in node_modules, resolving symlinks canonical-paths (to handle
|
|
7
|
+
* pnpm structures without infinite loops) and tracking visited packages.
|
|
8
|
+
*
|
|
9
|
+
* @param projectRoot Absolute path to the project root directory
|
|
10
|
+
* @returns Array of DuplicateDependency details
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectDuplicates(projectRoot: string): Promise<DuplicateDependency[]>;
|
|
13
|
+
//# sourceMappingURL=duplicates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicates.d.ts","sourceRoot":"","sources":["../../../src/detectors/duplicates.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAA+B,MAAM,aAAa,CAAC;AAE/E;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAiJ1F"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Detects packages with duplicate versions installed under node_modules.
|
|
5
|
+
*
|
|
6
|
+
* Works cross-platform and supports npm, pnpm, and yarn by walking the active
|
|
7
|
+
* dependency graph in node_modules, resolving symlinks canonical-paths (to handle
|
|
8
|
+
* pnpm structures without infinite loops) and tracking visited packages.
|
|
9
|
+
*
|
|
10
|
+
* @param projectRoot Absolute path to the project root directory
|
|
11
|
+
* @returns Array of DuplicateDependency details
|
|
12
|
+
*/
|
|
13
|
+
export async function detectDuplicates(projectRoot) {
|
|
14
|
+
const nodeModulesPath = join(projectRoot, 'node_modules');
|
|
15
|
+
const visitedPaths = new Set();
|
|
16
|
+
// Maps: packageName -> Map(versionString -> Array of relative path strings)
|
|
17
|
+
const packageVersionsMap = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Helper recursive function to walk package directories.
|
|
20
|
+
*/
|
|
21
|
+
async function walk(dirPath, relativePathPrefix) {
|
|
22
|
+
try {
|
|
23
|
+
// Canonicalize the path to handle symlinks (crucial for pnpm / yarn berry)
|
|
24
|
+
const realPath = await fs.realpath(dirPath);
|
|
25
|
+
if (visitedPaths.has(realPath)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
visitedPaths.add(realPath);
|
|
29
|
+
let pkgName;
|
|
30
|
+
let pkgVersion;
|
|
31
|
+
// Try reading package.json of the current directory
|
|
32
|
+
try {
|
|
33
|
+
const pkgJsonContent = await fs.readFile(join(dirPath, 'package.json'), 'utf8');
|
|
34
|
+
const pkgJson = JSON.parse(pkgJsonContent);
|
|
35
|
+
pkgName = pkgJson.name;
|
|
36
|
+
pkgVersion = pkgJson.version;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// No valid package.json in this directory, skip reading it as a package
|
|
40
|
+
}
|
|
41
|
+
if (pkgName && pkgVersion) {
|
|
42
|
+
let versionMap = packageVersionsMap.get(pkgName);
|
|
43
|
+
if (!versionMap) {
|
|
44
|
+
versionMap = new Map();
|
|
45
|
+
packageVersionsMap.set(pkgName, versionMap);
|
|
46
|
+
}
|
|
47
|
+
let pathsList = versionMap.get(pkgVersion);
|
|
48
|
+
if (!pathsList) {
|
|
49
|
+
pathsList = [];
|
|
50
|
+
versionMap.set(pkgVersion, pathsList);
|
|
51
|
+
}
|
|
52
|
+
pathsList.push(relativePathPrefix);
|
|
53
|
+
}
|
|
54
|
+
// Check for nested node_modules folder (typical in npm / yarn classic)
|
|
55
|
+
const nestedNodeModules = join(dirPath, 'node_modules');
|
|
56
|
+
try {
|
|
57
|
+
const entries = await fs.readdir(nestedNodeModules, { withFileTypes: true });
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
// Skip internal/hidden folders
|
|
60
|
+
if (entry.name.startsWith('.')) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
64
|
+
const entryPath = join(nestedNodeModules, entry.name);
|
|
65
|
+
const nextPrefix = relativePathPrefix
|
|
66
|
+
? `${relativePathPrefix}/node_modules/${entry.name}`
|
|
67
|
+
: `node_modules/${entry.name}`;
|
|
68
|
+
if (entry.name.startsWith('@')) {
|
|
69
|
+
// Read scoped packages directory
|
|
70
|
+
try {
|
|
71
|
+
const scopeEntries = await fs.readdir(entryPath, { withFileTypes: true });
|
|
72
|
+
for (const scopeEntry of scopeEntries) {
|
|
73
|
+
if (scopeEntry.isDirectory() || scopeEntry.isSymbolicLink()) {
|
|
74
|
+
const scopeEntryPath = join(entryPath, scopeEntry.name);
|
|
75
|
+
const scopeNextPrefix = `${nextPrefix}/${scopeEntry.name}`;
|
|
76
|
+
await walk(scopeEntryPath, scopeNextPrefix);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Ignore failures to read scoped package directories
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
await walk(entryPath, nextPrefix);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Nested node_modules directory does not exist or is unreadable
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Path resolution failed (e.g. broken symlink), skip directory safely
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Start walking from the root node_modules directory
|
|
99
|
+
try {
|
|
100
|
+
const rootEntries = await fs.readdir(nodeModulesPath, { withFileTypes: true });
|
|
101
|
+
for (const entry of rootEntries) {
|
|
102
|
+
if (entry.name.startsWith('.')) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
106
|
+
const entryPath = join(nodeModulesPath, entry.name);
|
|
107
|
+
const relativePrefix = `node_modules/${entry.name}`;
|
|
108
|
+
if (entry.name.startsWith('@')) {
|
|
109
|
+
try {
|
|
110
|
+
const scopeEntries = await fs.readdir(entryPath, { withFileTypes: true });
|
|
111
|
+
for (const scopeEntry of scopeEntries) {
|
|
112
|
+
if (scopeEntry.isDirectory() || scopeEntry.isSymbolicLink()) {
|
|
113
|
+
const scopeEntryPath = join(entryPath, scopeEntry.name);
|
|
114
|
+
const scopeRelativePrefix = `${relativePrefix}/${scopeEntry.name}`;
|
|
115
|
+
await walk(scopeEntryPath, scopeRelativePrefix);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Ignore scoped subdirectory read errors
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
await walk(entryPath, relativePrefix);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// node_modules doesn't exist, is unreadable, or project isn't installed
|
|
131
|
+
}
|
|
132
|
+
// Format and collect final duplicates mapping
|
|
133
|
+
const duplicates = [];
|
|
134
|
+
for (const [name, versionMap] of packageVersionsMap.entries()) {
|
|
135
|
+
if (versionMap.size > 1) {
|
|
136
|
+
const versions = [];
|
|
137
|
+
for (const [version, paths] of versionMap.entries()) {
|
|
138
|
+
versions.push({
|
|
139
|
+
version,
|
|
140
|
+
paths,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
duplicates.push({
|
|
144
|
+
name,
|
|
145
|
+
versions,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return duplicates;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=duplicates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicates.js","sourceRoot":"","sources":["../../../src/detectors/duplicates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { GhostDependency, PackageJson } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detects ghost dependencies: packages imported in the source code
|
|
4
|
+
* that are installed in node_modules but not listed in package.json.
|
|
5
|
+
*
|
|
6
|
+
* @param projectRoot Absolute path to the project root directory
|
|
7
|
+
* @param sourceFiles List of absolute paths of source files to analyze
|
|
8
|
+
* @param packageJson Parsed package.json object
|
|
9
|
+
* @param includeDev Whether to include devDependencies as declared packages
|
|
10
|
+
* @returns Array of detected ghost dependencies
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectGhosts(projectRoot: string, sourceFiles: string[], packageJson: PackageJson, includeDev?: boolean): Promise<GhostDependency[]>;
|
|
13
|
+
//# sourceMappingURL=ghosts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ghosts.d.ts","sourceRoot":"","sources":["../../../src/detectors/ghosts.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiJ3D;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EAAE,EACrB,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,OAAe,GAC1B,OAAO,CAAC,eAAe,EAAE,CAAC,CAoE5B"}
|