reqscan 1.0.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 +238 -0
- package/package.json +28 -0
- package/src/colors.js +30 -0
- package/src/commands.js +290 -0
- package/src/index.js +92 -0
- package/src/scanner.js +140 -0
- package/test/test.js +152 -0
package/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# reqscan โ Dependency Manager for Node.js
|
|
2
|
+
|
|
3
|
+
**reqscan** is a powerful CLI tool that scans your Node.js project, detects missing dependencies, and helps you manage them with simple commands. Zero external dependencies.
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## โจ Features
|
|
11
|
+
|
|
12
|
+
- ๐ **Check** โ Find imported packages missing from `package.json`
|
|
13
|
+
- ๐ฆ **Install** โ Install all missing packages at once
|
|
14
|
+
- ๐งน **Clean** โ Remove unused/undeclared packages
|
|
15
|
+
- ๐ง **Fix** โ Install missing + clean unused in one shot
|
|
16
|
+
- ๐ฏ **Audit** โ Full dependency health report with score
|
|
17
|
+
- ๐ **List** โ Show every dependency with its status
|
|
18
|
+
- ๐ซ **Smart filtering** โ Ignores built-ins, `node_modules`, relative imports
|
|
19
|
+
- ๐ **Multi-language** โ Supports `.js`, `.ts`, `.jsx`, `.tsx`, `.mjs`, `.cjs`
|
|
20
|
+
- ๐จ **Beautiful output** โ Color-coded terminal UI
|
|
21
|
+
- โก **Zero dependencies** โ Lightweight and fast
|
|
22
|
+
- ๐ง **Programmatic API** โ Use it in your own tools
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## ๐ฆ Installation
|
|
27
|
+
|
|
28
|
+
### Global Installation (Recommended)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g reqscan
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Run without installing (npx)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx reqscan check
|
|
38
|
+
npx reqscan install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Local Installation (for CI/CD)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install --save-dev reqscan
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then add to your `package.json` scripts:
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"scripts": {
|
|
51
|
+
"deps:check": "reqscan check",
|
|
52
|
+
"deps:fix": "reqscan fix"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## ๐ Usage
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Scan current directory
|
|
63
|
+
reqscan check
|
|
64
|
+
|
|
65
|
+
# Scan specific project
|
|
66
|
+
reqscan check /path/to/project
|
|
67
|
+
|
|
68
|
+
# Install all missing dependencies
|
|
69
|
+
reqscan install
|
|
70
|
+
|
|
71
|
+
# Remove unused dependencies
|
|
72
|
+
reqscan clean
|
|
73
|
+
|
|
74
|
+
# Fix everything (install missing + remove unused)
|
|
75
|
+
reqscan fix
|
|
76
|
+
|
|
77
|
+
# Show full project health report
|
|
78
|
+
reqscan audit
|
|
79
|
+
|
|
80
|
+
# Show all packages with their status
|
|
81
|
+
reqscan list
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## ๐ Command Reference
|
|
87
|
+
|
|
88
|
+
| Command | Description |
|
|
89
|
+
|---------|-------------|
|
|
90
|
+
| `check [dir]` | List missing, present, and unused packages |
|
|
91
|
+
| `install` | Install all missing packages found by `check` |
|
|
92
|
+
| `clean` | Remove packages declared but never imported |
|
|
93
|
+
| `fix` | Run `install` + `clean` in one command |
|
|
94
|
+
| `audit` | Full dependency health report with score |
|
|
95
|
+
| `list` | Show all dependencies with status |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## ๐ค Example Outputs
|
|
100
|
+
|
|
101
|
+
### `reqscan check`
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
๐ฆ Project: my-app
|
|
105
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
106
|
+
Summary
|
|
107
|
+
Total imports found : 9
|
|
108
|
+
Declared in pkg.json : 4
|
|
109
|
+
Already installed : 3
|
|
110
|
+
Missing (not declared): 2
|
|
111
|
+
|
|
112
|
+
โ Missing Packages (not in package.json)
|
|
113
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
114
|
+
โ axios
|
|
115
|
+
โ uuid
|
|
116
|
+
|
|
117
|
+
๐ก Run this to install all missing packages:
|
|
118
|
+
npm install axios uuid
|
|
119
|
+
|
|
120
|
+
โ
Already Declared Packages
|
|
121
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
122
|
+
โ express ^4.18.0
|
|
123
|
+
โ mongoose ^7.0.0
|
|
124
|
+
โ dotenv ^16.0.0
|
|
125
|
+
|
|
126
|
+
โ ๏ธ Declared but NOT imported in source (possibly unused)
|
|
127
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
128
|
+
~ jest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `reqscan audit`
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
๐ฅ Dependency Health Report
|
|
135
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
136
|
+
Health Score : 75%
|
|
137
|
+
Files Scanned : 12
|
|
138
|
+
Total Imports : 8
|
|
139
|
+
Declared : 6
|
|
140
|
+
โ Present : 6
|
|
141
|
+
โ Missing : 2
|
|
142
|
+
~ Unused declared : 1
|
|
143
|
+
|
|
144
|
+
๐ package.json Breakdown
|
|
145
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
146
|
+
dependencies : 4
|
|
147
|
+
devDependencies : 2
|
|
148
|
+
peerDependencies : 0
|
|
149
|
+
optionalDependencies : 0
|
|
150
|
+
|
|
151
|
+
๐ก Recommendations
|
|
152
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
153
|
+
โ Run reqscan install to install 2 missing package(s).
|
|
154
|
+
โ Run reqscan clean to remove 1 unused package(s).
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `reqscan list`
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
๐ All Dependencies with Status
|
|
161
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
162
|
+
โ axios ^1.6.0 present
|
|
163
|
+
โ date-fns missing
|
|
164
|
+
โ express ^4.18.0 present
|
|
165
|
+
~ jest ^29.0.0 unused
|
|
166
|
+
|
|
167
|
+
Legend: โ present โ missing ~ declared but unused
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## ๐ ๏ธ How It Works
|
|
173
|
+
|
|
174
|
+
1. **Scans** all `.js/.ts/.jsx/.tsx/.mjs/.cjs` files (skips `node_modules`, `dist`, `.next`, etc.)
|
|
175
|
+
2. **Extracts** package names from all import styles:
|
|
176
|
+
- `require('pkg')`
|
|
177
|
+
- `import x from 'pkg'`
|
|
178
|
+
- `import('pkg')`
|
|
179
|
+
- `require.resolve('pkg')`
|
|
180
|
+
- `export { x } from 'pkg'`
|
|
181
|
+
3. **Filters out** Node.js built-ins (`fs`, `path`, `http`, `node:*`, etc.)
|
|
182
|
+
4. **Compares** against `package.json` (all dependency types)
|
|
183
|
+
5. **Reports** and optionally acts on findings
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## ๐ง Programmatic API
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
const { scanProject } = require('reqscan');
|
|
191
|
+
|
|
192
|
+
const result = await scanProject('/path/to/project');
|
|
193
|
+
|
|
194
|
+
console.log(result.missing); // ['axios', 'uuid']
|
|
195
|
+
console.log(result.present); // ['express', 'mongoose']
|
|
196
|
+
console.log(result.unused); // ['jest']
|
|
197
|
+
console.log(result.allImports); // all detected package names
|
|
198
|
+
console.log(result.scannedFiles); // list of files scanned
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Return Value
|
|
202
|
+
|
|
203
|
+
| Field | Type | Description |
|
|
204
|
+
|-------|------|-------------|
|
|
205
|
+
| `projectName` | `string` | Name from package.json |
|
|
206
|
+
| `allImports` | `string[]` | All unique packages found in source |
|
|
207
|
+
| `declared` | `string[]` | All packages in package.json |
|
|
208
|
+
| `missing` | `string[]` | Imported but not declared |
|
|
209
|
+
| `present` | `string[]` | Imported AND declared |
|
|
210
|
+
| `unused` | `string[]` | Declared but never imported |
|
|
211
|
+
| `scannedFiles` | `string[]` | All files that were scanned |
|
|
212
|
+
| `packageJson` | `object\|null` | Parsed package.json content |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## ๐งช Running Tests
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
node test/test.js
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## ๐บ๏ธ Roadmap
|
|
225
|
+
|
|
226
|
+
- [ ] `reqscan outdated` โ Check for newer package versions
|
|
227
|
+
- [ ] `reqscan upgrade` โ Update all outdated packages
|
|
228
|
+
- [ ] `--save-dev` flag for install command
|
|
229
|
+
- [ ] `--dry-run` flag for previewing changes
|
|
230
|
+
- [ ] Configuration file (`.reqscanrc`)
|
|
231
|
+
- [ ] Monorepo/workspace support
|
|
232
|
+
- [ ] JSON output mode (`--json` flag)
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## ๐ License
|
|
237
|
+
|
|
238
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reqscan",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dependency Manager for Node.js โ scan, install, clean, and audit your project dependencies",
|
|
5
|
+
"main": "src/scanner.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"depm": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/index.js",
|
|
11
|
+
"test": "node test/test.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"npm",
|
|
15
|
+
"dependencies",
|
|
16
|
+
"missing",
|
|
17
|
+
"packages",
|
|
18
|
+
"audit",
|
|
19
|
+
"scanner",
|
|
20
|
+
"cli",
|
|
21
|
+
"depm"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=14.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/colors.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const c = {
|
|
4
|
+
reset : '\x1b[0m',
|
|
5
|
+
bold : '\x1b[1m',
|
|
6
|
+
dim : '\x1b[2m',
|
|
7
|
+
red : '\x1b[31m',
|
|
8
|
+
green : '\x1b[32m',
|
|
9
|
+
yellow : '\x1b[33m',
|
|
10
|
+
cyan : '\x1b[36m',
|
|
11
|
+
white : '\x1b[37m',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const paint = (color, text) => `${color}${text}${c.reset}`;
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
...c,
|
|
18
|
+
bold : (t) => paint(c.bold, t),
|
|
19
|
+
dim : (t) => paint(c.dim, t),
|
|
20
|
+
red : (t) => paint(c.red, t),
|
|
21
|
+
green : (t) => paint(c.green, t),
|
|
22
|
+
yellow : (t) => paint(c.yellow, t),
|
|
23
|
+
cyan : (t) => paint(c.cyan, t),
|
|
24
|
+
white : (t) => paint(c.white, t),
|
|
25
|
+
boldCyan : (t) => `${c.bold}${c.cyan}${t}${c.reset}`,
|
|
26
|
+
boldGreen : (t) => `${c.bold}${c.green}${t}${c.reset}`,
|
|
27
|
+
boldRed : (t) => `${c.bold}${c.red}${t}${c.reset}`,
|
|
28
|
+
boldYellow : (t) => `${c.bold}${c.yellow}${t}${c.reset}`,
|
|
29
|
+
LINE : 'โ'.repeat(50),
|
|
30
|
+
};
|
package/src/commands.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const { scanProject} = require('./scanner');
|
|
6
|
+
const col = require('./colors');
|
|
7
|
+
|
|
8
|
+
// โโโ Shared helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
9
|
+
|
|
10
|
+
function header(projectName) {
|
|
11
|
+
console.log();
|
|
12
|
+
console.log(col.boldCyan(`๐ฆ Project: ${projectName}`));
|
|
13
|
+
console.log(col.dim(col.LINE));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function prompt(question) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
19
|
+
rl.question(question, (ans) => { rl.close(); resolve(ans.trim()); });
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runNpm(args, cwd) {
|
|
24
|
+
try {
|
|
25
|
+
execSync(`npm ${args}`, { cwd, stdio: 'inherit' });
|
|
26
|
+
return true;
|
|
27
|
+
} catch (_) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// โโโ check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
33
|
+
|
|
34
|
+
async function cmdCheck(targetDir) {
|
|
35
|
+
const r = await scanProject(targetDir);
|
|
36
|
+
header(r.projectName);
|
|
37
|
+
|
|
38
|
+
// Summary
|
|
39
|
+
console.log(col.bold('Summary'));
|
|
40
|
+
console.log(` Total imports found : ${col.bold(String(r.allImports.length))}`);
|
|
41
|
+
console.log(` Declared in pkg.json : ${col.bold(String(r.declared.length))}`);
|
|
42
|
+
console.log(` Already installed : ${col.boldGreen(String(r.present.length))}`);
|
|
43
|
+
console.log(` Missing (not declared): ${r.missing.length > 0 ? col.boldRed(String(r.missing.length)) : col.boldGreen('0')}`);
|
|
44
|
+
console.log();
|
|
45
|
+
|
|
46
|
+
// Missing
|
|
47
|
+
if (r.missing.length > 0) {
|
|
48
|
+
console.log(col.boldRed('โ Missing Packages (not in package.json)'));
|
|
49
|
+
console.log(col.dim(col.LINE));
|
|
50
|
+
r.missing.forEach(p => console.log(` ${col.red('โ')} ${p}`));
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(col.boldYellow('๐ก Run this to install all missing packages:'));
|
|
53
|
+
console.log(col.boldCyan(` npm install ${r.missing.join(' ')}`));
|
|
54
|
+
console.log();
|
|
55
|
+
if (r.missing.length > 1) {
|
|
56
|
+
console.log(col.dim('Or install one by one:'));
|
|
57
|
+
r.missing.forEach(p => console.log(col.dim(` npm install ${p}`)));
|
|
58
|
+
console.log();
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
console.log(col.boldGreen('โ
All imported packages are declared in package.json!'));
|
|
62
|
+
console.log();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Present
|
|
66
|
+
if (r.present.length > 0) {
|
|
67
|
+
console.log(col.boldGreen('โ
Already Declared Packages'));
|
|
68
|
+
console.log(col.dim(col.LINE));
|
|
69
|
+
r.present.forEach(p => {
|
|
70
|
+
const ver =
|
|
71
|
+
r.packageJson?.dependencies?.[p] ||
|
|
72
|
+
r.packageJson?.devDependencies?.[p] ||
|
|
73
|
+
r.packageJson?.peerDependencies?.[p] ||
|
|
74
|
+
r.packageJson?.optionalDependencies?.[p] || '';
|
|
75
|
+
console.log(` ${col.green('โ')} ${p} ${col.dim(ver)}`);
|
|
76
|
+
});
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Unused
|
|
81
|
+
if (r.unused.length > 0) {
|
|
82
|
+
console.log(col.boldYellow('โ ๏ธ Declared but NOT imported in source (possibly unused)'));
|
|
83
|
+
console.log(col.dim(col.LINE));
|
|
84
|
+
r.unused.forEach(p => console.log(` ${col.yellow('~')} ${p}`));
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(col.dim('Tip: Run `deptrack clean` to remove unused packages.'));
|
|
87
|
+
console.log();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(col.dim(col.LINE));
|
|
91
|
+
console.log(col.dim('Scan complete.'));
|
|
92
|
+
console.log();
|
|
93
|
+
|
|
94
|
+
return r;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// โโโ install โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
98
|
+
|
|
99
|
+
async function cmdInstall(targetDir) {
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(col.boldCyan('๐ฆ Installing missing dependencies...'));
|
|
102
|
+
console.log();
|
|
103
|
+
|
|
104
|
+
const r = await scanProject(targetDir);
|
|
105
|
+
|
|
106
|
+
if (r.missing.length === 0) {
|
|
107
|
+
console.log(col.boldGreen('โ
No missing packages found. Everything is already declared!'));
|
|
108
|
+
console.log();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`Found ${col.boldRed(String(r.missing.length))} missing package(s):`);
|
|
113
|
+
r.missing.forEach(p => console.log(` โข ${p}`));
|
|
114
|
+
console.log();
|
|
115
|
+
console.log('Installing... โ๏ธ');
|
|
116
|
+
console.log();
|
|
117
|
+
|
|
118
|
+
const ok = runNpm(`install ${r.missing.join(' ')}`, targetDir);
|
|
119
|
+
|
|
120
|
+
if (ok) {
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(col.boldGreen('โ Successfully installed:'));
|
|
123
|
+
r.missing.forEach(p => console.log(` โข ${col.green(p)}`));
|
|
124
|
+
console.log();
|
|
125
|
+
console.log(col.dim('๐ก Run `deptrack check` again to verify all packages are declared.'));
|
|
126
|
+
} else {
|
|
127
|
+
console.log(col.boldRed('โ Installation failed. Please check your npm setup and try again.'));
|
|
128
|
+
}
|
|
129
|
+
console.log();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// โโโ clean โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
133
|
+
|
|
134
|
+
async function cmdClean(targetDir, { force = false } = {}) {
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(col.boldCyan('๐งน Scanning for unused dependencies...'));
|
|
137
|
+
console.log();
|
|
138
|
+
|
|
139
|
+
const r = await scanProject(targetDir);
|
|
140
|
+
|
|
141
|
+
if (r.unused.length === 0) {
|
|
142
|
+
console.log(col.boldGreen('โ
No unused packages found. Your package.json is clean!'));
|
|
143
|
+
console.log();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`Found ${col.boldYellow(String(r.unused.length))} unused package(s):`);
|
|
148
|
+
r.unused.forEach(p => {
|
|
149
|
+
const inDev = r.packageJson?.devDependencies?.[p];
|
|
150
|
+
const kind = inDev ? 'devDependencies' : 'dependencies';
|
|
151
|
+
console.log(` โข ${p} ${col.dim(`(${kind})`)}`);
|
|
152
|
+
});
|
|
153
|
+
console.log();
|
|
154
|
+
|
|
155
|
+
let confirm = 'y';
|
|
156
|
+
if (!force) {
|
|
157
|
+
confirm = await prompt('Remove these packages? [Y/n] ');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (confirm === '' || confirm.toLowerCase() === 'y') {
|
|
161
|
+
const depsBefore = Object.keys(r.packageJson?.dependencies || {}).length;
|
|
162
|
+
const devDepsBefore = Object.keys(r.packageJson?.devDependencies || {}).length;
|
|
163
|
+
|
|
164
|
+
console.log();
|
|
165
|
+
const ok = runNpm(`uninstall ${r.unused.join(' ')}`, targetDir);
|
|
166
|
+
|
|
167
|
+
if (ok) {
|
|
168
|
+
// Re-read to get updated counts
|
|
169
|
+
const { readPackageJson } = require('./scanner');
|
|
170
|
+
const updated = readPackageJson(targetDir);
|
|
171
|
+
const depsAfter = Object.keys(updated?.dependencies || {}).length;
|
|
172
|
+
const devDepsAfter = Object.keys(updated?.devDependencies || {}).length;
|
|
173
|
+
|
|
174
|
+
console.log();
|
|
175
|
+
console.log(col.boldGreen(`โ Successfully removed ${r.unused.length} package(s)`));
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(col.bold('๐ After cleanup:'));
|
|
178
|
+
console.log(` Dependencies : ${depsBefore} โ ${depsAfter}`);
|
|
179
|
+
console.log(` devDependencies : ${devDepsBefore} โ ${devDepsAfter}`);
|
|
180
|
+
} else {
|
|
181
|
+
console.log(col.boldRed('โ Removal failed. Please check your npm setup.'));
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
console.log(col.dim('Aborted. No changes made.'));
|
|
185
|
+
}
|
|
186
|
+
console.log();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// โโโ fix โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
190
|
+
|
|
191
|
+
async function cmdFix(targetDir) {
|
|
192
|
+
console.log();
|
|
193
|
+
console.log(col.boldCyan('๐ง Running deptrack fix (install missing + clean unused)...'));
|
|
194
|
+
console.log(col.dim(col.LINE));
|
|
195
|
+
await cmdInstall(targetDir);
|
|
196
|
+
console.log(col.dim(col.LINE));
|
|
197
|
+
await cmdClean(targetDir, { force: false });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// โโโ audit โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
201
|
+
|
|
202
|
+
async function cmdAudit(targetDir) {
|
|
203
|
+
const r = await scanProject(targetDir);
|
|
204
|
+
header(r.projectName);
|
|
205
|
+
|
|
206
|
+
// Health score
|
|
207
|
+
const total = r.allImports.length || 1;
|
|
208
|
+
const score = Math.round((r.present.length / total) * 100);
|
|
209
|
+
const scoreColor = score === 100 ? col.boldGreen : score >= 70 ? col.boldYellow : col.boldRed;
|
|
210
|
+
|
|
211
|
+
console.log(col.bold('๐ฅ Dependency Health Report'));
|
|
212
|
+
console.log(col.dim(col.LINE));
|
|
213
|
+
console.log(` Health Score : ${scoreColor(`${score}%`)}`);
|
|
214
|
+
console.log(` Files Scanned : ${col.bold(String(r.scannedFiles.length))}`);
|
|
215
|
+
console.log(` Total Imports : ${col.bold(String(r.allImports.length))}`);
|
|
216
|
+
console.log(` Declared : ${col.bold(String(r.declared.length))}`);
|
|
217
|
+
console.log(` โ Present : ${col.boldGreen(String(r.present.length))}`);
|
|
218
|
+
console.log(` โ Missing : ${r.missing.length > 0 ? col.boldRed(String(r.missing.length)) : col.boldGreen('0')}`);
|
|
219
|
+
console.log(` ~ Unused declared : ${r.unused.length > 0 ? col.boldYellow(String(r.unused.length)) : col.boldGreen('0')}`);
|
|
220
|
+
console.log();
|
|
221
|
+
|
|
222
|
+
// Dependency breakdown
|
|
223
|
+
const deps = r.packageJson?.dependencies || {};
|
|
224
|
+
const devDeps = r.packageJson?.devDependencies || {};
|
|
225
|
+
const peer = r.packageJson?.peerDependencies || {};
|
|
226
|
+
const opt = r.packageJson?.optionalDependencies|| {};
|
|
227
|
+
|
|
228
|
+
console.log(col.bold('๐ package.json Breakdown'));
|
|
229
|
+
console.log(col.dim(col.LINE));
|
|
230
|
+
console.log(` dependencies : ${Object.keys(deps).length}`);
|
|
231
|
+
console.log(` devDependencies : ${Object.keys(devDeps).length}`);
|
|
232
|
+
console.log(` peerDependencies : ${Object.keys(peer).length}`);
|
|
233
|
+
console.log(` optionalDependencies : ${Object.keys(opt).length}`);
|
|
234
|
+
console.log();
|
|
235
|
+
|
|
236
|
+
// Recommendations
|
|
237
|
+
const recs = [];
|
|
238
|
+
if (r.missing.length > 0) recs.push(`Run ${col.cyan('deptrack install')} to install ${r.missing.length} missing package(s).`);
|
|
239
|
+
if (r.unused.length > 0) recs.push(`Run ${col.cyan('deptrack clean')} to remove ${r.unused.length} unused package(s).`);
|
|
240
|
+
if (recs.length === 0) recs.push(col.green('Your project dependencies look great!'));
|
|
241
|
+
|
|
242
|
+
console.log(col.bold('๐ก Recommendations'));
|
|
243
|
+
console.log(col.dim(col.LINE));
|
|
244
|
+
recs.forEach(r => console.log(` โ ${r}`));
|
|
245
|
+
console.log();
|
|
246
|
+
console.log(col.dim(col.LINE));
|
|
247
|
+
console.log(col.dim('Audit complete.'));
|
|
248
|
+
console.log();
|
|
249
|
+
|
|
250
|
+
return r;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// โโโ list โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
254
|
+
|
|
255
|
+
async function cmdList(targetDir) {
|
|
256
|
+
const r = await scanProject(targetDir);
|
|
257
|
+
header(r.projectName);
|
|
258
|
+
|
|
259
|
+
console.log(col.bold('๐ All Dependencies with Status'));
|
|
260
|
+
console.log(col.dim(col.LINE));
|
|
261
|
+
|
|
262
|
+
const all = new Set([...r.allImports, ...r.declared]);
|
|
263
|
+
const sorted = [...all].sort();
|
|
264
|
+
|
|
265
|
+
sorted.forEach(pkg => {
|
|
266
|
+
const imported = r.allImports.includes(pkg);
|
|
267
|
+
const declared = r.declared.includes(pkg);
|
|
268
|
+
const ver =
|
|
269
|
+
r.packageJson?.dependencies?.[pkg] ||
|
|
270
|
+
r.packageJson?.devDependencies?.[pkg] ||
|
|
271
|
+
r.packageJson?.peerDependencies?.[pkg] ||
|
|
272
|
+
r.packageJson?.optionalDependencies?.[pkg] || '';
|
|
273
|
+
|
|
274
|
+
let status, icon;
|
|
275
|
+
if (imported && declared) { icon = col.green('โ'); status = col.dim('present'); }
|
|
276
|
+
else if (imported) { icon = col.red('โ'); status = col.red('missing'); }
|
|
277
|
+
else { icon = col.yellow('~'); status = col.yellow('unused'); }
|
|
278
|
+
|
|
279
|
+
console.log(` ${icon} ${pkg.padEnd(30)} ${col.dim(ver.padEnd(12))} ${status}`);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
console.log();
|
|
283
|
+
console.log(col.dim(`Legend: ${col.green('โ')} present ${col.red('โ')} missing ${col.yellow('~')} declared but unused`));
|
|
284
|
+
console.log();
|
|
285
|
+
console.log(col.dim(col.LINE));
|
|
286
|
+
console.log(col.dim('List complete.'));
|
|
287
|
+
console.log();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = { cmdCheck, cmdInstall, cmdClean, cmdFix, cmdAudit, cmdList };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const col = require('./colors');
|
|
7
|
+
const { cmdCheck, cmdInstall, cmdClean, cmdFix, cmdAudit, cmdList } = require('./commands');
|
|
8
|
+
|
|
9
|
+
// โโโ Parse args โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const command = args[0];
|
|
12
|
+
const dirArg = args[1];
|
|
13
|
+
|
|
14
|
+
// โโโ Help text โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
15
|
+
function printHelp() {
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(col.boldCyan('reqscan') + col.bold(' โ Dependency Manager for Node.js'));
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(col.bold('Usage:'));
|
|
20
|
+
console.log(' reqscan <command> [project-dir]');
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(col.bold('Commands:'));
|
|
23
|
+
const cmds = [
|
|
24
|
+
['check [dir]', 'List missing, present, and unused packages'],
|
|
25
|
+
['install', 'Install all missing packages found by check'],
|
|
26
|
+
['clean', 'Remove packages declared but never imported'],
|
|
27
|
+
['fix', 'Run install + clean in one command'],
|
|
28
|
+
['audit', 'Full dependency health report'],
|
|
29
|
+
['list', 'Show all dependencies with status'],
|
|
30
|
+
];
|
|
31
|
+
cmds.forEach(([cmd, desc]) => {
|
|
32
|
+
console.log(` ${col.cyan(cmd.padEnd(20))} ${desc}`);
|
|
33
|
+
});
|
|
34
|
+
console.log();
|
|
35
|
+
console.log(col.bold('Examples:'));
|
|
36
|
+
console.log(` ${col.dim('reqscan check')} # scan current directory`);
|
|
37
|
+
console.log(` ${col.dim('reqscan check ./my-app')} # scan specific folder`);
|
|
38
|
+
console.log(` ${col.dim('reqscan install')} # install all missing`);
|
|
39
|
+
console.log(` ${col.dim('reqscan clean')} # remove unused`);
|
|
40
|
+
console.log(` ${col.dim('reqscan fix')} # install missing + clean unused`);
|
|
41
|
+
console.log(` ${col.dim('reqscan audit')} # full health report`);
|
|
42
|
+
console.log(` ${col.dim('reqscan list')} # show all with status`);
|
|
43
|
+
console.log();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// โโโ Resolve target directory โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
47
|
+
function resolveDir(dirArg) {
|
|
48
|
+
const target = dirArg ? path.resolve(dirArg) : process.cwd();
|
|
49
|
+
if (!fs.existsSync(target)) {
|
|
50
|
+
console.error(col.boldRed(`โ Directory not found: ${target}`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
if (!fs.existsSync(path.join(target, 'package.json'))) {
|
|
54
|
+
console.warn(col.boldYellow(`โ ๏ธ No package.json found in: ${target}`));
|
|
55
|
+
console.warn(col.dim(' Results may be incomplete.\n'));
|
|
56
|
+
}
|
|
57
|
+
return target;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// โโโ Main โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
61
|
+
(async () => {
|
|
62
|
+
const validCmds = ['check','install','clean','fix','audit','list','help','--help','-h'];
|
|
63
|
+
|
|
64
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
65
|
+
printHelp();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!validCmds.includes(command)) {
|
|
70
|
+
console.error(col.boldRed(`โ Unknown command: "${command}"`));
|
|
71
|
+
printHelp();
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const targetDir = resolveDir(command === 'check' ? dirArg : (dirArg || undefined));
|
|
76
|
+
|
|
77
|
+
console.log(col.dim(`\n๐ Scanning project at: ${targetDir}`));
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
switch (command) {
|
|
81
|
+
case 'check' : await cmdCheck(targetDir); break;
|
|
82
|
+
case 'install': await cmdInstall(targetDir); break;
|
|
83
|
+
case 'clean' : await cmdClean(targetDir); break;
|
|
84
|
+
case 'fix' : await cmdFix(targetDir); break;
|
|
85
|
+
case 'audit' : await cmdAudit(targetDir); break;
|
|
86
|
+
case 'list' : await cmdList(targetDir); break;
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(col.boldRed(`\nโ Error: ${err.message}`));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
})();
|
package/src/scanner.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// โโโ Node.js built-in modules โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
7
|
+
const BUILTIN_MODULES = new Set([
|
|
8
|
+
'assert','async_hooks','buffer','child_process','cluster','console',
|
|
9
|
+
'constants','crypto','dgram','diagnostics_channel','dns','domain',
|
|
10
|
+
'events','fs','fs/promises','http','http2','https','inspector',
|
|
11
|
+
'module','net','os','path','path/posix','path/win32','perf_hooks',
|
|
12
|
+
'process','punycode','querystring','readline','repl','stream',
|
|
13
|
+
'stream/consumers','stream/promises','stream/web','string_decoder',
|
|
14
|
+
'sys','timers','timers/promises','tls','trace_events','tty','url',
|
|
15
|
+
'util','util/types','v8','vm','wasi','worker_threads','zlib',
|
|
16
|
+
// node: prefix variants
|
|
17
|
+
'node:assert','node:buffer','node:child_process','node:cluster',
|
|
18
|
+
'node:crypto','node:dgram','node:dns','node:domain','node:events',
|
|
19
|
+
'node:fs','node:http','node:http2','node:https','node:inspector',
|
|
20
|
+
'node:module','node:net','node:os','node:path','node:perf_hooks',
|
|
21
|
+
'node:process','node:readline','node:repl','node:stream','node:string_decoder',
|
|
22
|
+
'node:timers','node:tls','node:tty','node:url','node:util','node:v8',
|
|
23
|
+
'node:vm','node:worker_threads','node:zlib'
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
// โโโ Directories to skip โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
27
|
+
const IGNORE_DIRS = new Set([
|
|
28
|
+
'node_modules','.git','.next','.nuxt','dist','build',
|
|
29
|
+
'coverage','.cache','.turbo','out','.svelte-kit','.vercel',
|
|
30
|
+
'.output','public','static','assets'
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
// โโโ File extensions to scan โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
34
|
+
const JS_EXTENSIONS = new Set(['.js','.jsx','.mjs','.cjs','.ts','.tsx','.mts','.cts']);
|
|
35
|
+
|
|
36
|
+
// โโโ Import/require patterns โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
37
|
+
const IMPORT_PATTERNS = [
|
|
38
|
+
/require\s*\(\s*['"`]([^'"`\s]+)['"`]\s*\)/g,
|
|
39
|
+
/import\s+(?:[\w*{}\s,]+\s+from\s+)?['"`]([^'"`\s]+)['"`]/g,
|
|
40
|
+
/import\s*\(\s*['"`]([^'"`\s]+)['"`]\s*\)/g,
|
|
41
|
+
/require\.resolve\s*\(\s*['"`]([^'"`\s]+)['"`]\s*\)/g,
|
|
42
|
+
/(?:export\s+(?:[\w*{}\s,]+\s+)?from\s+)['"`]([^'"`\s]+)['"`]/g,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
46
|
+
|
|
47
|
+
function extractPackageName(raw) {
|
|
48
|
+
if (!raw) return null;
|
|
49
|
+
// Skip relative, absolute, URL imports
|
|
50
|
+
if (raw.startsWith('.') || raw.startsWith('/') || /^https?:\/\//.test(raw) || raw.startsWith('data:')) return null;
|
|
51
|
+
// Scoped: @scope/pkg
|
|
52
|
+
if (raw.startsWith('@')) {
|
|
53
|
+
const parts = raw.split('/');
|
|
54
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
|
|
55
|
+
}
|
|
56
|
+
// Regular: pkg or pkg/subpath โ pkg
|
|
57
|
+
return raw.split('/')[0];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function extractImportsFromFile(filePath) {
|
|
61
|
+
const found = new Set();
|
|
62
|
+
try {
|
|
63
|
+
const src = fs.readFileSync(filePath, 'utf8');
|
|
64
|
+
for (const re of IMPORT_PATTERNS) {
|
|
65
|
+
re.lastIndex = 0;
|
|
66
|
+
let m;
|
|
67
|
+
while ((m = re.exec(src)) !== null) {
|
|
68
|
+
const pkg = extractPackageName(m[1]);
|
|
69
|
+
if (pkg && !BUILTIN_MODULES.has(pkg)) found.add(pkg);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (_) { /* skip unreadable */ }
|
|
73
|
+
return found;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function walkDir(dir, allImports = new Set(), scannedFiles = []) {
|
|
77
|
+
let entries;
|
|
78
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
|
|
79
|
+
catch (_) { return { allImports, scannedFiles }; }
|
|
80
|
+
|
|
81
|
+
for (const e of entries) {
|
|
82
|
+
if (IGNORE_DIRS.has(e.name)) continue;
|
|
83
|
+
const full = path.join(dir, e.name);
|
|
84
|
+
if (e.isDirectory()) {
|
|
85
|
+
walkDir(full, allImports, scannedFiles);
|
|
86
|
+
} else if (e.isFile() && JS_EXTENSIONS.has(path.extname(e.name))) {
|
|
87
|
+
scannedFiles.push(full);
|
|
88
|
+
for (const pkg of extractImportsFromFile(full)) allImports.add(pkg);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { allImports, scannedFiles };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readPackageJson(dir) {
|
|
95
|
+
const p = path.join(dir, 'package.json');
|
|
96
|
+
if (!fs.existsSync(p)) return null;
|
|
97
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); }
|
|
98
|
+
catch (_) { return null; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// โโโ Public API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
102
|
+
|
|
103
|
+
async function scanProject(targetDir) {
|
|
104
|
+
const pkgJson = readPackageJson(targetDir);
|
|
105
|
+
const { allImports, scannedFiles } = walkDir(targetDir);
|
|
106
|
+
|
|
107
|
+
const declared = new Set([
|
|
108
|
+
...Object.keys(pkgJson?.dependencies || {}),
|
|
109
|
+
...Object.keys(pkgJson?.devDependencies || {}),
|
|
110
|
+
...Object.keys(pkgJson?.peerDependencies || {}),
|
|
111
|
+
...Object.keys(pkgJson?.optionalDependencies|| {}),
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
const missing = [];
|
|
115
|
+
const present = [];
|
|
116
|
+
const unused = [];
|
|
117
|
+
|
|
118
|
+
for (const pkg of [...allImports].sort()) {
|
|
119
|
+
if (declared.has(pkg)) present.push(pkg);
|
|
120
|
+
else missing.push(pkg);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const pkg of [...declared].sort()) {
|
|
124
|
+
if (!allImports.has(pkg)) unused.push(pkg);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
projectName : pkgJson?.name || path.basename(targetDir),
|
|
129
|
+
packageJson : pkgJson,
|
|
130
|
+
allImports : [...allImports].sort(),
|
|
131
|
+
declared : [...declared].sort(),
|
|
132
|
+
missing,
|
|
133
|
+
present,
|
|
134
|
+
unused,
|
|
135
|
+
scannedFiles,
|
|
136
|
+
targetDir,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { scanProject, readPackageJson };
|
package/test/test.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { scanProject } = require('../src/scanner');
|
|
7
|
+
const { cmdCheck, cmdAudit, cmdList } = require('../src/commands');
|
|
8
|
+
|
|
9
|
+
// โโโ Test helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
10
|
+
let passed = 0;
|
|
11
|
+
let failed = 0;
|
|
12
|
+
|
|
13
|
+
function assert(condition, label) {
|
|
14
|
+
if (condition) {
|
|
15
|
+
console.log(` \x1b[32mโ\x1b[0m ${label}`);
|
|
16
|
+
passed++;
|
|
17
|
+
} else {
|
|
18
|
+
console.error(` \x1b[31mโ\x1b[0m ${label}`);
|
|
19
|
+
failed++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function section(title) {
|
|
24
|
+
console.log(`\n\x1b[1m\x1b[36m${title}\x1b[0m`);
|
|
25
|
+
console.log('โ'.repeat(40));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// โโโ Build mock project โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
29
|
+
function buildMockProject() {
|
|
30
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'depm-test-'));
|
|
31
|
+
|
|
32
|
+
// package.json โ express declared, jest declared (but never imported)
|
|
33
|
+
fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({
|
|
34
|
+
name: 'mock-project',
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
dependencies: {
|
|
37
|
+
express: '^4.18.0',
|
|
38
|
+
mongoose: '^7.0.0',
|
|
39
|
+
},
|
|
40
|
+
devDependencies: {
|
|
41
|
+
jest: '^29.0.0',
|
|
42
|
+
},
|
|
43
|
+
}, null, 2));
|
|
44
|
+
|
|
45
|
+
// index.js โ requires express (declared), axios (missing), uuid (missing)
|
|
46
|
+
fs.writeFileSync(path.join(tmpDir, 'index.js'), `
|
|
47
|
+
const express = require('express');
|
|
48
|
+
const axios = require('axios');
|
|
49
|
+
const path = require('path'); // built-in
|
|
50
|
+
const fs = require('fs'); // built-in
|
|
51
|
+
import something from 'lodash';
|
|
52
|
+
`);
|
|
53
|
+
|
|
54
|
+
// utils/db.js โ subdir, scoped pkg, subpath import
|
|
55
|
+
fs.mkdirSync(path.join(tmpDir, 'utils'));
|
|
56
|
+
fs.writeFileSync(path.join(tmpDir, 'utils', 'db.js'), `
|
|
57
|
+
const mongoose = require('mongoose'); // declared
|
|
58
|
+
const { v4 } = require('uuid'); // missing
|
|
59
|
+
import { format } from 'date-fns'; // missing
|
|
60
|
+
import core from '@babel/core'; // missing scoped
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
// lib/helpers.ts โ TypeScript file
|
|
64
|
+
fs.mkdirSync(path.join(tmpDir, 'lib'));
|
|
65
|
+
fs.writeFileSync(path.join(tmpDir, 'lib', 'helpers.ts'), `
|
|
66
|
+
import { useState } from 'react'; // missing
|
|
67
|
+
export { something } from 'lodash'; // missing (already found, de-duped)
|
|
68
|
+
`);
|
|
69
|
+
|
|
70
|
+
// node_modules โ should be skipped entirely
|
|
71
|
+
fs.mkdirSync(path.join(tmpDir, 'node_modules', 'express'), { recursive: true });
|
|
72
|
+
fs.writeFileSync(path.join(tmpDir, 'node_modules', 'express', 'index.js'), `
|
|
73
|
+
require('some-internal-dep'); // should NOT be counted
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
return tmpDir;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// โโโ Run tests โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
80
|
+
async function run() {
|
|
81
|
+
console.log('\n\x1b[1mdepm โ Test Suite\x1b[0m');
|
|
82
|
+
|
|
83
|
+
const tmpDir = buildMockProject();
|
|
84
|
+
|
|
85
|
+
// โโ Scanner tests โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
86
|
+
section('Scanner: extracting imports');
|
|
87
|
+
const r = await scanProject(tmpDir);
|
|
88
|
+
|
|
89
|
+
assert(r.projectName === 'mock-project', 'project name read from package.json');
|
|
90
|
+
assert(r.scannedFiles.length >= 3, 'scanned js/ts files (not node_modules)');
|
|
91
|
+
assert(!r.allImports.includes('path'), 'built-in "path" excluded');
|
|
92
|
+
assert(!r.allImports.includes('fs'), 'built-in "fs" excluded');
|
|
93
|
+
assert(!r.allImports.includes('some-internal-dep'), 'node_modules skipped');
|
|
94
|
+
|
|
95
|
+
section('Scanner: detected packages');
|
|
96
|
+
assert(r.allImports.includes('express'), 'express detected');
|
|
97
|
+
assert(r.allImports.includes('axios'), 'axios detected');
|
|
98
|
+
assert(r.allImports.includes('lodash'), 'lodash detected (import default)');
|
|
99
|
+
assert(r.allImports.includes('mongoose'), 'mongoose detected');
|
|
100
|
+
assert(r.allImports.includes('uuid'), 'uuid detected');
|
|
101
|
+
assert(r.allImports.includes('date-fns'), 'date-fns detected');
|
|
102
|
+
assert(r.allImports.includes('@babel/core'),'scoped @babel/core detected');
|
|
103
|
+
assert(r.allImports.includes('react'), 'react detected from .ts file');
|
|
104
|
+
|
|
105
|
+
section('Scanner: missing vs present');
|
|
106
|
+
assert(r.present.includes('express'), 'express in present (declared)');
|
|
107
|
+
assert(r.present.includes('mongoose'), 'mongoose in present (declared)');
|
|
108
|
+
assert(r.missing.includes('axios'), 'axios in missing');
|
|
109
|
+
assert(r.missing.includes('uuid'), 'uuid in missing');
|
|
110
|
+
assert(r.missing.includes('date-fns'), 'date-fns in missing');
|
|
111
|
+
assert(r.missing.includes('@babel/core'), '@babel/core in missing');
|
|
112
|
+
assert(r.missing.includes('react'), 'react in missing');
|
|
113
|
+
assert(!r.missing.includes('express'), 'express NOT in missing');
|
|
114
|
+
assert(!r.missing.includes('mongoose'), 'mongoose NOT in missing');
|
|
115
|
+
|
|
116
|
+
section('Scanner: unused (declared but not imported)');
|
|
117
|
+
assert(r.unused.includes('jest'), 'jest in unused (declared, never imported)');
|
|
118
|
+
assert(!r.unused.includes('express'), 'express NOT in unused (it is imported)');
|
|
119
|
+
assert(!r.unused.includes('mongoose'), 'mongoose NOT in unused (it is imported)');
|
|
120
|
+
|
|
121
|
+
section('Commands: check (output only)');
|
|
122
|
+
console.log('\n--- depm check output ---');
|
|
123
|
+
await cmdCheck(tmpDir);
|
|
124
|
+
assert(true, 'cmdCheck ran without error');
|
|
125
|
+
|
|
126
|
+
section('Commands: audit (output only)');
|
|
127
|
+
console.log('\n--- depm audit output ---');
|
|
128
|
+
await cmdAudit(tmpDir);
|
|
129
|
+
assert(true, 'cmdAudit ran without error');
|
|
130
|
+
|
|
131
|
+
section('Commands: list (output only)');
|
|
132
|
+
console.log('\n--- depm list output ---');
|
|
133
|
+
await cmdList(tmpDir);
|
|
134
|
+
assert(true, 'cmdList ran without error');
|
|
135
|
+
|
|
136
|
+
// Cleanup
|
|
137
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
138
|
+
|
|
139
|
+
// โโ Results โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
140
|
+
console.log('\n' + 'โ'.repeat(40));
|
|
141
|
+
if (failed === 0) {
|
|
142
|
+
console.log(`\x1b[32m\x1b[1mโ
All ${passed} tests passed.\x1b[0m\n`);
|
|
143
|
+
} else {
|
|
144
|
+
console.error(`\x1b[31m\x1b[1mโ ${failed} test(s) failed. ${passed} passed.\x1b[0m\n`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
run().catch(err => {
|
|
150
|
+
console.error('Unexpected error:', err);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|