pkg-clean 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/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/analyzer.d.ts +24 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +75 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/analyzer.test.d.ts +1 -0
- package/dist/analyzer.test.js +89 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +27 -0
- package/dist/cli.js.map +1 -0
- package/dist/configLoader.d.ts +40 -0
- package/dist/configLoader.d.ts.map +1 -0
- package/dist/configLoader.js +97 -0
- package/dist/configLoader.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +316 -0
- package/dist/packageManager.d.ts +14 -0
- package/dist/packageManager.d.ts.map +1 -0
- package/dist/packageManager.js +67 -0
- package/dist/packageManager.js.map +1 -0
- package/dist/packageManagerFactory.d.ts +37 -0
- package/dist/packageManagerFactory.d.ts.map +1 -0
- package/dist/packageManagerFactory.js +158 -0
- package/dist/packageManagerFactory.js.map +1 -0
- package/dist/registry.d.ts +25 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +61 -0
- package/dist/registry.js.map +1 -0
- package/dist/registry.test.d.ts +1 -0
- package/dist/registry.test.js +72 -0
- package/dist/scanner.d.ts +20 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +89 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scanner.test.d.ts +1 -0
- package/dist/scanner.test.js +79 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# pkg-clean
|
|
2
|
+
|
|
3
|
+
> Find and remove unused dependencies from your Node.js project
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/pkg-clean)
|
|
6
|
+
[](https://www.npmjs.com/package/pkg-clean)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://github.com/apassanisi/pkg-clean/actions)
|
|
9
|
+
|
|
10
|
+
A minimal, fast CLI tool that scans your project for unused npm dependencies. Identifies unused packages, duplicates, and deprecated packages in seconds.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Scan for unused dependencies** - finds packages in package.json that aren't imported anywhere
|
|
15
|
+
- **Detect duplicates** - spots packages listed in both `dependencies` and `devDependencies`
|
|
16
|
+
- **Check deprecation status** - warns about deprecated packages via NPM registry
|
|
17
|
+
- **Safe by default** - uses dry-run mode, only removes with `--remove` flag
|
|
18
|
+
- **Fast** - scans large projects in under a second
|
|
19
|
+
- **Minimal & slick CLI** - beautiful colored output with clear formatting
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g pkg-clean
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or run directly without installing:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx pkg-clean
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Scan current project
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pkg-clean
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Output:
|
|
42
|
+
```
|
|
43
|
+
◆ pkg-clean
|
|
44
|
+
→ Scanning files...
|
|
45
|
+
✓ Found 12 imports
|
|
46
|
+
→ Analyzing dependencies...
|
|
47
|
+
→ Checking for deprecated packages...
|
|
48
|
+
|
|
49
|
+
◆ Report
|
|
50
|
+
◆ Unused dependencies (3)
|
|
51
|
+
● @types/jest
|
|
52
|
+
● @types/node
|
|
53
|
+
● ts-jest
|
|
54
|
+
|
|
55
|
+
→ Run with --remove to clean up unused dependencies
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Remove unused dependencies
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pkg-clean --remove
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This will:
|
|
65
|
+
1. Remove unused packages from your `package.json`
|
|
66
|
+
2. Show you which packages were removed
|
|
67
|
+
3. Suggest running `npm install`
|
|
68
|
+
|
|
69
|
+
### Show verbose output
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pkg-clean --verbose
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Displays detailed information about all imports, dependencies, and analysis results.
|
|
76
|
+
|
|
77
|
+
### Keep specific packages
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pkg-clean --keep lodash react
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Excludes specified packages from removal (even if unused).
|
|
84
|
+
|
|
85
|
+
### Scan a different directory
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pkg-clean /path/to/project
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Combine options
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pkg-clean /path/to/project --remove --verbose --keep lodash
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Dry-run to preview changes
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pkg-clean --remove --verbose
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Output (without `--remove`):
|
|
104
|
+
```
|
|
105
|
+
→ Run with --remove to clean up unused dependencies
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This allows you to review what would be removed before applying changes.
|
|
109
|
+
|
|
110
|
+
### Use with npm scripts
|
|
111
|
+
|
|
112
|
+
Add to your `package.json`:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"scripts": {
|
|
117
|
+
"clean:deps": "pkg-clean",
|
|
118
|
+
"clean:deps:remove": "pkg-clean --remove",
|
|
119
|
+
"clean:deps:check": "pkg-clean --verbose"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Then run:
|
|
125
|
+
```bash
|
|
126
|
+
npm run clean:deps:check # Preview unused dependencies
|
|
127
|
+
npm run clean:deps:remove # Remove unused dependencies
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### CI/CD Integration
|
|
131
|
+
|
|
132
|
+
Check for unused dependencies in your CI pipeline:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Fail if unused dependencies are found
|
|
136
|
+
pkg-clean --verbose
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Exit code will be non-zero if unused dependencies are detected (useful for CI gates).
|
|
140
|
+
|
|
141
|
+
## Configuration
|
|
142
|
+
|
|
143
|
+
Create a `pkg-clean.config.json` file in your project root to set default packages to keep:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"keep": ["lodash", "moment"]
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Example configurations:
|
|
152
|
+
|
|
153
|
+
**For a React project:**
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"keep": ["react", "react-dom", "react-router-dom"]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**For a monorepo:**
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"keep": ["@company/shared-utils", "@company/types"]
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**For a library with peer dependencies:**
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"keep": ["tslib", "@types/node"]
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## How it works
|
|
175
|
+
|
|
176
|
+
1. **Scans** all `.js`, `.ts`, `.jsx`, `.tsx` files in your project
|
|
177
|
+
2. **Extracts** import/require statements using regex patterns
|
|
178
|
+
3. **Compares** found imports against package.json entries
|
|
179
|
+
4. **Reports** unused, duplicates, and deprecated packages
|
|
180
|
+
5. **Removes** from package.json (if `--remove` flag is used)
|
|
181
|
+
|
|
182
|
+
## What it detects
|
|
183
|
+
|
|
184
|
+
### ✅ Detects
|
|
185
|
+
|
|
186
|
+
- `import` statements: `import foo from 'bar'`
|
|
187
|
+
- `require()` calls: `require('foo')`
|
|
188
|
+
- Scoped packages: `@babel/core`
|
|
189
|
+
- Nested imports: `lodash/map` → detects `lodash`
|
|
190
|
+
- Both `dependencies` and `devDependencies`
|
|
191
|
+
- Duplicate packages in both sections
|
|
192
|
+
- Deprecated packages from NPM registry
|
|
193
|
+
|
|
194
|
+
### ⚠️ Limitations
|
|
195
|
+
|
|
196
|
+
- Doesn't analyze dynamic imports like `require(variable)`
|
|
197
|
+
- Doesn't check `.json`, `.html`, or config files
|
|
198
|
+
- Doesn't evaluate webpack/babel configs
|
|
199
|
+
- Doesn't track indirect dependencies
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Install dependencies
|
|
205
|
+
npm install
|
|
206
|
+
|
|
207
|
+
# Build TypeScript
|
|
208
|
+
npm run build
|
|
209
|
+
|
|
210
|
+
# Run tests
|
|
211
|
+
npm test
|
|
212
|
+
|
|
213
|
+
# Run tests in watch mode
|
|
214
|
+
npm test:watch
|
|
215
|
+
|
|
216
|
+
# Run locally
|
|
217
|
+
node dist/cli.js
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Contributing
|
|
221
|
+
|
|
222
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT © 2026
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface PackageJson {
|
|
2
|
+
dependencies?: Record<string, string>;
|
|
3
|
+
devDependencies?: Record<string, string>;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface AnalysisResult {
|
|
7
|
+
unused: string[];
|
|
8
|
+
duplicates: string[];
|
|
9
|
+
allDeps: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Analyzes package.json dependencies against imported packages.
|
|
13
|
+
* Identifies unused packages and duplicates across dependency types.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Analyzer {
|
|
16
|
+
/**
|
|
17
|
+
* Analyzes a project's dependencies.
|
|
18
|
+
* @param projectRoot - The root directory containing package.json
|
|
19
|
+
* @param importedPackages - Set of package names found by Scanner
|
|
20
|
+
* @returns Analysis result containing unused packages, duplicates, and all dependencies
|
|
21
|
+
* @throws {Error} If package.json is not found
|
|
22
|
+
*/
|
|
23
|
+
analyzeProject(projectRoot: string, importedPackages: Set<string>): AnalysisResult;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,QAAQ;IACnB;;;;;;OAMG;IACH,cAAc,CACZ,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,GAC5B,cAAc;CA4BlB"}
|
package/dist/analyzer.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Analyzer = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
/**
|
|
40
|
+
* Analyzes package.json dependencies against imported packages.
|
|
41
|
+
* Identifies unused packages and duplicates across dependency types.
|
|
42
|
+
*/
|
|
43
|
+
class Analyzer {
|
|
44
|
+
/**
|
|
45
|
+
* Analyzes a project's dependencies.
|
|
46
|
+
* @param projectRoot - The root directory containing package.json
|
|
47
|
+
* @param importedPackages - Set of package names found by Scanner
|
|
48
|
+
* @returns Analysis result containing unused packages, duplicates, and all dependencies
|
|
49
|
+
* @throws {Error} If package.json is not found
|
|
50
|
+
*/
|
|
51
|
+
analyzeProject(projectRoot, importedPackages) {
|
|
52
|
+
const pkgPath = path.join(projectRoot, "package.json");
|
|
53
|
+
if (!fs.existsSync(pkgPath)) {
|
|
54
|
+
throw new Error("package.json not found");
|
|
55
|
+
}
|
|
56
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
57
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
58
|
+
const allDeps = Object.keys(deps);
|
|
59
|
+
const unused = allDeps.filter((dep) => !importedPackages.has(dep));
|
|
60
|
+
const duplicates = [];
|
|
61
|
+
if (pkg.dependencies && pkg.devDependencies) {
|
|
62
|
+
for (const dep of Object.keys(pkg.dependencies)) {
|
|
63
|
+
if (pkg.devDependencies[dep]) {
|
|
64
|
+
duplicates.push(dep);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
unused,
|
|
70
|
+
duplicates,
|
|
71
|
+
allDeps,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.Analyzer = Analyzer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAc7B;;;GAGG;AACH,MAAa,QAAQ;IACnB;;;;;;OAMG;IACH,cAAc,CACZ,WAAmB,EACnB,gBAA6B;QAE7B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QAEvD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;QAED,MAAM,GAAG,GAAgB,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACvE,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC,YAAY,EAAE,GAAG,GAAG,CAAC,eAAe,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAElC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAEnE,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,IAAI,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC;YAC5C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;gBAChD,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC7B,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACvB,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO;YACL,MAAM;YACN,UAAU;YACV,OAAO;SACR,CAAC;IACJ,CAAC;CACF;AAvCD,4BAuCC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const analyzer_1 = require("./analyzer");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
describe("Analyzer", () => {
|
|
40
|
+
const analyzer = new analyzer_1.Analyzer();
|
|
41
|
+
const testDir = path.join(__dirname, "fixtures-analyzer");
|
|
42
|
+
beforeAll(() => {
|
|
43
|
+
if (!fs.existsSync(testDir)) {
|
|
44
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
afterAll(() => {
|
|
48
|
+
if (fs.existsSync(testDir)) {
|
|
49
|
+
fs.rmSync(testDir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
it("should detect unused dependencies", () => {
|
|
53
|
+
const pkg = {
|
|
54
|
+
dependencies: {
|
|
55
|
+
lodash: "^4.17.0",
|
|
56
|
+
react: "^18.0.0",
|
|
57
|
+
},
|
|
58
|
+
devDependencies: {
|
|
59
|
+
jest: "^29.0.0",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
fs.writeFileSync(path.join(testDir, "package.json"), JSON.stringify(pkg));
|
|
63
|
+
const imported = new Set(["react"]);
|
|
64
|
+
const result = analyzer.analyzeProject(testDir, imported);
|
|
65
|
+
expect(result.unused).toContain("lodash");
|
|
66
|
+
expect(result.unused).toContain("jest");
|
|
67
|
+
expect(result.unused).not.toContain("react");
|
|
68
|
+
});
|
|
69
|
+
it("should detect duplicate dependencies", () => {
|
|
70
|
+
const pkg = {
|
|
71
|
+
dependencies: {
|
|
72
|
+
typescript: "^5.0.0",
|
|
73
|
+
},
|
|
74
|
+
devDependencies: {
|
|
75
|
+
typescript: "^5.0.0",
|
|
76
|
+
jest: "^29.0.0",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
fs.writeFileSync(path.join(testDir, "package.json"), JSON.stringify(pkg));
|
|
80
|
+
const imported = new Set(["typescript", "jest"]);
|
|
81
|
+
const result = analyzer.analyzeProject(testDir, imported);
|
|
82
|
+
expect(result.duplicates).toContain("typescript");
|
|
83
|
+
});
|
|
84
|
+
it("should throw error when package.json not found", () => {
|
|
85
|
+
const nonExistentDir = path.join(testDir, "nonexistent");
|
|
86
|
+
const imported = new Set();
|
|
87
|
+
expect(() => analyzer.analyzeProject(nonExistentDir, imported)).toThrow();
|
|
88
|
+
});
|
|
89
|
+
});
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const index_1 = require("./index");
|
|
6
|
+
commander_1.program
|
|
7
|
+
.name("pkg-clean")
|
|
8
|
+
.description("Find and remove unused dependencies")
|
|
9
|
+
.version("1.0.0");
|
|
10
|
+
commander_1.program
|
|
11
|
+
.arguments("[path]")
|
|
12
|
+
.option("--remove", "Remove unused dependencies from package.json")
|
|
13
|
+
.option("--verbose", "Show verbose output")
|
|
14
|
+
.option("--keep <packages...>", "Packages to keep (space-separated)")
|
|
15
|
+
.action(async (dirPath, options) => {
|
|
16
|
+
try {
|
|
17
|
+
const projectRoot = dirPath || process.cwd();
|
|
18
|
+
const cleaner = new index_1.Cleaner();
|
|
19
|
+
await cleaner.clean(projectRoot, options);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
23
|
+
console.error(`\x1b[31m✗\x1b[0m ${message}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
commander_1.program.parse();
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,mCAAkC;AAElC,mBAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,qCAAqC,CAAC;KAClD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,mBAAO;KACJ,SAAS,CAAC,QAAQ,CAAC;KACnB,MAAM,CAAC,UAAU,EAAE,8CAA8C,CAAC;KAClE,MAAM,CAAC,WAAW,EAAE,qBAAqB,CAAC;KAC1C,MAAM,CAAC,sBAAsB,EAAE,oCAAoC,CAAC;KACpE,MAAM,CAAC,KAAK,EAAE,OAA2B,EAAE,OAAO,EAAE,EAAE;IACrD,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,eAAO,EAAE,CAAC;QAC9B,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,mBAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for pkg-clean
|
|
3
|
+
*/
|
|
4
|
+
export interface PkgCleanConfig {
|
|
5
|
+
/** Directories to ignore during scanning */
|
|
6
|
+
ignore?: string[];
|
|
7
|
+
/** Packages to always keep, even if unused */
|
|
8
|
+
keep?: string[];
|
|
9
|
+
/** Check for deprecated packages */
|
|
10
|
+
checkDeprecation?: boolean;
|
|
11
|
+
/** Enable verbose output */
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Loads and manages pkg-clean configuration from pkg-clean.config.json
|
|
16
|
+
*/
|
|
17
|
+
export declare class ConfigLoader {
|
|
18
|
+
private static readonly CONFIG_FILE;
|
|
19
|
+
/**
|
|
20
|
+
* Loads configuration from pkg-clean.config.json if it exists
|
|
21
|
+
* @param projectRoot - The project root directory
|
|
22
|
+
* @returns The loaded configuration or empty object if file doesn't exist
|
|
23
|
+
*/
|
|
24
|
+
static load(projectRoot: string): PkgCleanConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a default configuration file
|
|
27
|
+
* @param projectRoot - The project root directory
|
|
28
|
+
* @returns The created configuration
|
|
29
|
+
*/
|
|
30
|
+
static createDefault(projectRoot: string): PkgCleanConfig;
|
|
31
|
+
/**
|
|
32
|
+
* Merges user options with loaded configuration
|
|
33
|
+
* CLI options take precedence over config file
|
|
34
|
+
* @param configFile - Configuration from file
|
|
35
|
+
* @param cliOptions - Command-line options
|
|
36
|
+
* @returns Merged configuration
|
|
37
|
+
*/
|
|
38
|
+
static merge(configFile: PkgCleanConfig, cliOptions: Partial<PkgCleanConfig>): PkgCleanConfig;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=configLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configLoader.d.ts","sourceRoot":"","sources":["../src/configLoader.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,oCAAoC;IACpC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,4BAA4B;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA2B;IAE9D;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc;IAiBhD;;;;OAIG;IACH,MAAM,CAAC,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc;IAczD;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CACV,UAAU,EAAE,cAAc,EAC1B,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,GAClC,cAAc;CASlB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ConfigLoader = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
/**
|
|
40
|
+
* Loads and manages pkg-clean configuration from pkg-clean.config.json
|
|
41
|
+
*/
|
|
42
|
+
class ConfigLoader {
|
|
43
|
+
/**
|
|
44
|
+
* Loads configuration from pkg-clean.config.json if it exists
|
|
45
|
+
* @param projectRoot - The project root directory
|
|
46
|
+
* @returns The loaded configuration or empty object if file doesn't exist
|
|
47
|
+
*/
|
|
48
|
+
static load(projectRoot) {
|
|
49
|
+
const configPath = path.join(projectRoot, this.CONFIG_FILE);
|
|
50
|
+
if (!fs.existsSync(configPath)) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
55
|
+
const config = JSON.parse(content);
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
60
|
+
throw new Error(`Failed to parse ${this.CONFIG_FILE}: ${message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Creates a default configuration file
|
|
65
|
+
* @param projectRoot - The project root directory
|
|
66
|
+
* @returns The created configuration
|
|
67
|
+
*/
|
|
68
|
+
static createDefault(projectRoot) {
|
|
69
|
+
const configPath = path.join(projectRoot, this.CONFIG_FILE);
|
|
70
|
+
const defaultConfig = {
|
|
71
|
+
ignore: ["node_modules", "dist", "build"],
|
|
72
|
+
keep: [],
|
|
73
|
+
checkDeprecation: true,
|
|
74
|
+
verbose: false,
|
|
75
|
+
};
|
|
76
|
+
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n");
|
|
77
|
+
return defaultConfig;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Merges user options with loaded configuration
|
|
81
|
+
* CLI options take precedence over config file
|
|
82
|
+
* @param configFile - Configuration from file
|
|
83
|
+
* @param cliOptions - Command-line options
|
|
84
|
+
* @returns Merged configuration
|
|
85
|
+
*/
|
|
86
|
+
static merge(configFile, cliOptions) {
|
|
87
|
+
return {
|
|
88
|
+
ignore: cliOptions.ignore ?? configFile.ignore,
|
|
89
|
+
keep: cliOptions.keep ?? configFile.keep,
|
|
90
|
+
checkDeprecation: cliOptions.checkDeprecation ?? configFile.checkDeprecation ?? true,
|
|
91
|
+
verbose: cliOptions.verbose ?? configFile.verbose ?? false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.ConfigLoader = ConfigLoader;
|
|
96
|
+
ConfigLoader.CONFIG_FILE = "pkg-clean.config.json";
|
|
97
|
+
//# sourceMappingURL=configLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configLoader.js","sourceRoot":"","sources":["../src/configLoader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAgB7B;;GAEG;AACH,MAAa,YAAY;IAGvB;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,WAAmB;QAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAE5D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACrD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACnC,OAAO,MAAwB,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,CAAC,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,aAAa,CAAC,WAAmB;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAE5D,MAAM,aAAa,GAAmB;YACpC,MAAM,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC;YACzC,IAAI,EAAE,EAAE;YACR,gBAAgB,EAAE,IAAI;YACtB,OAAO,EAAE,KAAK;SACf,CAAC;QAEF,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC5E,OAAO,aAAa,CAAC;IACvB,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CACV,UAA0B,EAC1B,UAAmC;QAEnC,OAAO;YACL,MAAM,EAAE,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,MAAM;YAC9C,IAAI,EAAE,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI;YACxC,gBAAgB,EACd,UAAU,CAAC,gBAAgB,IAAI,UAAU,CAAC,gBAAgB,IAAI,IAAI;YACpE,OAAO,EAAE,UAAU,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,IAAI,KAAK;SAC3D,CAAC;IACJ,CAAC;;AA9DH,oCA+DC;AA9DyB,wBAAW,GAAG,uBAAuB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface CleanerOptions {
|
|
2
|
+
remove?: boolean;
|
|
3
|
+
verbose?: boolean;
|
|
4
|
+
keep?: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Main cleaner class that orchestrates the dependency analysis workflow.
|
|
8
|
+
* Scans project files, analyzes dependencies, checks for deprecation, and optionally removes unused packages.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Cleaner {
|
|
11
|
+
private scanner;
|
|
12
|
+
private analyzer;
|
|
13
|
+
private registry;
|
|
14
|
+
constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Scans a project for unused dependencies and optionally removes them.
|
|
17
|
+
* @param projectRoot - The root directory of the project to scan
|
|
18
|
+
* @param options - Configuration options for the clean operation
|
|
19
|
+
* @param options.remove - If true, removes unused dependencies from package.json
|
|
20
|
+
* @param options.verbose - If true, outputs detailed diagnostic information
|
|
21
|
+
* @throws {Error} If package.json is not found in the project root
|
|
22
|
+
*/
|
|
23
|
+
clean(projectRoot: string, options?: CleanerOptions): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AA6ED;;;GAGG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAAW;;IAQ3B;;;;;;;OAOG;IACG,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB;CA8H9D"}
|