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 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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Node.js Version](https://img.shields.io/node/v/reqscan.svg)](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
+ };
@@ -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
+ });