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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/dist/analyzer.d.ts +24 -0
  4. package/dist/analyzer.d.ts.map +1 -0
  5. package/dist/analyzer.js +75 -0
  6. package/dist/analyzer.js.map +1 -0
  7. package/dist/analyzer.test.d.ts +1 -0
  8. package/dist/analyzer.test.js +89 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +27 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/configLoader.d.ts +40 -0
  14. package/dist/configLoader.d.ts.map +1 -0
  15. package/dist/configLoader.js +97 -0
  16. package/dist/configLoader.js.map +1 -0
  17. package/dist/index.d.ts +24 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +179 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/index.test.d.ts +1 -0
  22. package/dist/index.test.js +316 -0
  23. package/dist/packageManager.d.ts +14 -0
  24. package/dist/packageManager.d.ts.map +1 -0
  25. package/dist/packageManager.js +67 -0
  26. package/dist/packageManager.js.map +1 -0
  27. package/dist/packageManagerFactory.d.ts +37 -0
  28. package/dist/packageManagerFactory.d.ts.map +1 -0
  29. package/dist/packageManagerFactory.js +158 -0
  30. package/dist/packageManagerFactory.js.map +1 -0
  31. package/dist/registry.d.ts +25 -0
  32. package/dist/registry.d.ts.map +1 -0
  33. package/dist/registry.js +61 -0
  34. package/dist/registry.js.map +1 -0
  35. package/dist/registry.test.d.ts +1 -0
  36. package/dist/registry.test.js +72 -0
  37. package/dist/scanner.d.ts +20 -0
  38. package/dist/scanner.d.ts.map +1 -0
  39. package/dist/scanner.js +89 -0
  40. package/dist/scanner.js.map +1 -0
  41. package/dist/scanner.test.d.ts +1 -0
  42. package/dist/scanner.test.js +79 -0
  43. package/package.json +53 -0
@@ -0,0 +1,158 @@
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.PackageManagerFactory = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * NPM package manager implementation
41
+ */
42
+ class NpmPackageManager {
43
+ getType() {
44
+ return "npm";
45
+ }
46
+ removeDependencies(projectRoot, packages) {
47
+ const pkgPath = path.join(projectRoot, "package.json");
48
+ if (!fs.existsSync(pkgPath)) {
49
+ throw new Error("package.json not found");
50
+ }
51
+ const content = fs.readFileSync(pkgPath, "utf-8");
52
+ const pkg = JSON.parse(content);
53
+ for (const packageName of packages) {
54
+ delete pkg.dependencies?.[packageName];
55
+ delete pkg.devDependencies?.[packageName];
56
+ }
57
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
58
+ return true;
59
+ }
60
+ }
61
+ /**
62
+ * Yarn package manager implementation
63
+ */
64
+ class YarnPackageManager {
65
+ getType() {
66
+ return "yarn";
67
+ }
68
+ removeDependencies(projectRoot, packages) {
69
+ const pkgPath = path.join(projectRoot, "package.json");
70
+ if (!fs.existsSync(pkgPath)) {
71
+ throw new Error("package.json not found");
72
+ }
73
+ const content = fs.readFileSync(pkgPath, "utf-8");
74
+ const pkg = JSON.parse(content);
75
+ for (const packageName of packages) {
76
+ delete pkg.dependencies?.[packageName];
77
+ delete pkg.devDependencies?.[packageName];
78
+ }
79
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
80
+ return true;
81
+ }
82
+ }
83
+ /**
84
+ * pnpm package manager implementation
85
+ */
86
+ class PnpmPackageManager {
87
+ getType() {
88
+ return "pnpm";
89
+ }
90
+ removeDependencies(projectRoot, packages) {
91
+ const pkgPath = path.join(projectRoot, "package.json");
92
+ if (!fs.existsSync(pkgPath)) {
93
+ throw new Error("package.json not found");
94
+ }
95
+ const content = fs.readFileSync(pkgPath, "utf-8");
96
+ const pkg = JSON.parse(content);
97
+ for (const packageName of packages) {
98
+ delete pkg.dependencies?.[packageName];
99
+ delete pkg.devDependencies?.[packageName];
100
+ }
101
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
102
+ return true;
103
+ }
104
+ }
105
+ /**
106
+ * Factory for creating package manager instances.
107
+ * Detects the package manager type based on lock files in the project.
108
+ */
109
+ class PackageManagerFactory {
110
+ /**
111
+ * Detects the package manager type by looking for lock files.
112
+ * @param projectRoot - The project root directory
113
+ * @returns The detected package manager type, defaults to 'npm'
114
+ */
115
+ static detect(projectRoot) {
116
+ // Check for yarn.lock
117
+ if (fs.existsSync(path.join(projectRoot, "yarn.lock"))) {
118
+ return "yarn";
119
+ }
120
+ // Check for pnpm-lock.yaml
121
+ if (fs.existsSync(path.join(projectRoot, "pnpm-lock.yaml"))) {
122
+ return "pnpm";
123
+ }
124
+ // Check for package-lock.json
125
+ if (fs.existsSync(path.join(projectRoot, "package-lock.json"))) {
126
+ return "npm";
127
+ }
128
+ // Default to npm
129
+ return "npm";
130
+ }
131
+ /**
132
+ * Creates a package manager instance.
133
+ * @param type - The type of package manager to create
134
+ * @returns An instance of the specified package manager
135
+ */
136
+ static create(type = "npm") {
137
+ switch (type) {
138
+ case "yarn":
139
+ return new YarnPackageManager();
140
+ case "pnpm":
141
+ return new PnpmPackageManager();
142
+ case "npm":
143
+ default:
144
+ return new NpmPackageManager();
145
+ }
146
+ }
147
+ /**
148
+ * Creates and detects the appropriate package manager for a project.
149
+ * @param projectRoot - The project root directory
150
+ * @returns An instance of the detected package manager
151
+ */
152
+ static createForProject(projectRoot) {
153
+ const type = this.detect(projectRoot);
154
+ return this.create(type);
155
+ }
156
+ }
157
+ exports.PackageManagerFactory = PackageManagerFactory;
158
+ //# sourceMappingURL=packageManagerFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageManagerFactory.js","sourceRoot":"","sources":["../src/packageManagerFactory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export interface DeprecationInfo {
2
+ name: string;
3
+ deprecated: boolean;
4
+ replacement?: string;
5
+ reason?: string;
6
+ }
7
+ /**
8
+ * Checks npm registry for package deprecation status.
9
+ * Results are cached to minimize network requests.
10
+ */
11
+ export declare class Registry {
12
+ private cache;
13
+ /**
14
+ * Checks if a package is deprecated on npm.
15
+ * @param packageName - The name of the package to check
16
+ * @returns Deprecation information for the package
17
+ */
18
+ checkDeprecation(packageName: string): Promise<DeprecationInfo>;
19
+ /**
20
+ * Checks deprecation status for multiple packages in parallel.
21
+ * @param packages - Array of package names to check
22
+ * @returns Array of deprecation info for each package
23
+ */
24
+ checkMultiple(packages: string[]): Promise<DeprecationInfo[]>;
25
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAAsC;IAEnD;;;;OAIG;IACG,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA2CrE;;;;OAIG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;CAGpE"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Registry = void 0;
4
+ /**
5
+ * Checks npm registry for package deprecation status.
6
+ * Results are cached to minimize network requests.
7
+ */
8
+ class Registry {
9
+ constructor() {
10
+ this.cache = new Map();
11
+ }
12
+ /**
13
+ * Checks if a package is deprecated on npm.
14
+ * @param packageName - The name of the package to check
15
+ * @returns Deprecation information for the package
16
+ */
17
+ async checkDeprecation(packageName) {
18
+ if (this.cache.has(packageName)) {
19
+ return this.cache.get(packageName);
20
+ }
21
+ try {
22
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
23
+ if (!response.ok) {
24
+ const result = {
25
+ name: packageName,
26
+ deprecated: false,
27
+ };
28
+ this.cache.set(packageName, result);
29
+ return result;
30
+ }
31
+ const data = (await response.json());
32
+ const deprecated = !!data.deprecated;
33
+ const reason = typeof data.deprecated === "string" ? data.deprecated : undefined;
34
+ const result = {
35
+ name: packageName,
36
+ deprecated,
37
+ reason,
38
+ };
39
+ this.cache.set(packageName, result);
40
+ return result;
41
+ }
42
+ catch (_a) {
43
+ // Network error or invalid package
44
+ const result = {
45
+ name: packageName,
46
+ deprecated: false,
47
+ };
48
+ this.cache.set(packageName, result);
49
+ return result;
50
+ }
51
+ }
52
+ /**
53
+ * Checks deprecation status for multiple packages in parallel.
54
+ * @param packages - Array of package names to check
55
+ * @returns Array of deprecation info for each package
56
+ */
57
+ async checkMultiple(packages) {
58
+ return Promise.all(packages.map((pkg) => this.checkDeprecation(pkg)));
59
+ }
60
+ }
61
+ exports.Registry = Registry;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":";;;AAOA;;;GAGG;AACH,MAAa,QAAQ;IAArB;QACU,UAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IA0DrD,CAAC;IAxDC;;;;OAIG;IACH,KAAK,CAAC,gBAAgB,CAAC,WAAmB;QACxC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAE,CAAC;QACtC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,8BAA8B,WAAW,EAAE,CAAC,CAAC;YAE1E,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAoB;oBAC9B,IAAI,EAAE,WAAW;oBACjB,UAAU,EAAE,KAAK;iBAClB,CAAC;gBACF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;gBACpC,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAElC,CAAC;YACF,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;YACrC,MAAM,MAAM,GACV,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;YAEpE,MAAM,MAAM,GAAoB;gBAC9B,IAAI,EAAE,WAAW;gBACjB,UAAU;gBACV,MAAM;aACP,CAAC;YAEF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YACpC,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;YACnC,MAAM,MAAM,GAAoB;gBAC9B,IAAI,EAAE,WAAW;gBACjB,UAAU,EAAE,KAAK;aAClB,CAAC;YACF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YACpC,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,aAAa,CAAC,QAAkB;QACpC,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC;CACF;AA3DD,4BA2DC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const registry_1 = require("./registry");
4
+ describe("Registry", () => {
5
+ let registry;
6
+ beforeEach(() => {
7
+ registry = new registry_1.Registry();
8
+ // Mock fetch globally
9
+ global.fetch = jest.fn();
10
+ });
11
+ afterEach(() => {
12
+ jest.restoreAllMocks();
13
+ });
14
+ it("should cache deprecation checks", async () => {
15
+ global.fetch.mockResolvedValueOnce({
16
+ ok: true,
17
+ json: async () => ({ deprecated: false }),
18
+ });
19
+ // First call
20
+ const result1 = await registry.checkDeprecation("commander");
21
+ // Second call should use cache (fetch not called again)
22
+ const result2 = await registry.checkDeprecation("commander");
23
+ expect(result1.name).toBe("commander");
24
+ expect(result1.deprecated).toBe(false);
25
+ expect(result2).toEqual(result1);
26
+ expect(global.fetch).toHaveBeenCalledTimes(1);
27
+ });
28
+ it("should handle network errors gracefully", async () => {
29
+ global.fetch.mockRejectedValueOnce(new Error("Network error"));
30
+ const result = await registry.checkDeprecation("invalid-pkg-name-xyz123");
31
+ expect(result.name).toBe("invalid-pkg-name-xyz123");
32
+ expect(result.deprecated).toBe(false);
33
+ });
34
+ it("should parse deprecated string reason", async () => {
35
+ global.fetch.mockResolvedValueOnce({
36
+ ok: true,
37
+ json: async () => ({
38
+ deprecated: "Use package-x instead",
39
+ }),
40
+ });
41
+ const result = await registry.checkDeprecation("old-package");
42
+ expect(result.name).toBe("old-package");
43
+ expect(result.deprecated).toBe(true);
44
+ expect(result.reason).toBe("Use package-x instead");
45
+ });
46
+ it("should handle 404 responses gracefully", async () => {
47
+ global.fetch.mockResolvedValueOnce({
48
+ ok: false,
49
+ status: 404,
50
+ });
51
+ const result = await registry.checkDeprecation("nonexistent-package");
52
+ expect(result.name).toBe("nonexistent-package");
53
+ expect(result.deprecated).toBe(false);
54
+ });
55
+ it("should check multiple packages in parallel", async () => {
56
+ global.fetch
57
+ .mockResolvedValueOnce({
58
+ ok: true,
59
+ json: async () => ({ deprecated: false }),
60
+ })
61
+ .mockResolvedValueOnce({
62
+ ok: true,
63
+ json: async () => ({ deprecated: true, reason: "Deprecated" }),
64
+ });
65
+ const results = await registry.checkMultiple(["commander", "old-pkg"]);
66
+ expect(results).toHaveLength(2);
67
+ expect(results[0].name).toBe("commander");
68
+ expect(results[0].deprecated).toBe(false);
69
+ expect(results[1].name).toBe("old-pkg");
70
+ expect(results[1].deprecated).toBe(true);
71
+ });
72
+ });
@@ -0,0 +1,20 @@
1
+ interface ScannerOptions {
2
+ ignore?: string[];
3
+ }
4
+ /**
5
+ * Scans project source files to extract imported package names.
6
+ * Supports ES6 imports and CommonJS require() statements.
7
+ */
8
+ export declare class Scanner {
9
+ private importRegex;
10
+ /**
11
+ * Recursively scans a directory for imported packages.
12
+ * @param projectRoot - The root directory to scan
13
+ * @param options - Scanner configuration options
14
+ * @param options.ignore - Array of directory names to exclude (default: node_modules, dist, build)
15
+ * @returns A set of unique package names found in imports
16
+ */
17
+ scan(projectRoot: string, options?: ScannerOptions): Promise<Set<string>>;
18
+ private isCodeFile;
19
+ }
20
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAGA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,OAAO;IAElB,OAAO,CAAC,WAAW,CAC8D;IAEjF;;;;;;OAMG;IACG,IAAI,CACR,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAmCvB,OAAO,CAAC,UAAU;CAInB"}
@@ -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
+ exports.Scanner = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Scans project source files to extract imported package names.
41
+ * Supports ES6 imports and CommonJS require() statements.
42
+ */
43
+ class Scanner {
44
+ constructor() {
45
+ // Matches: import 'x', import from 'x', require('x'), etc.
46
+ this.importRegex = /(?:import\s+(?:.*\s+)?from\s+|import\s+|require\s*\()\s*['"`]([^'"`]+)['"`]/g;
47
+ }
48
+ /**
49
+ * Recursively scans a directory for imported packages.
50
+ * @param projectRoot - The root directory to scan
51
+ * @param options - Scanner configuration options
52
+ * @param options.ignore - Array of directory names to exclude (default: node_modules, dist, build)
53
+ * @returns A set of unique package names found in imports
54
+ */
55
+ async scan(projectRoot, options = {}) {
56
+ const imports = new Set();
57
+ const ignore = new Set(options.ignore || ["node_modules", "dist", "build"]);
58
+ const scanDir = async (dir) => {
59
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
60
+ for (const entry of entries) {
61
+ if (ignore.has(entry.name))
62
+ continue;
63
+ const fullPath = path.join(dir, entry.name);
64
+ if (entry.isDirectory()) {
65
+ await scanDir(fullPath);
66
+ }
67
+ else if (this.isCodeFile(fullPath)) {
68
+ const content = fs.readFileSync(fullPath, "utf-8");
69
+ const matches = content.matchAll(this.importRegex);
70
+ for (const match of matches) {
71
+ const imported = match[1];
72
+ // Extract package name from path (handle scoped packages)
73
+ const packageName = imported.startsWith("@")
74
+ ? imported.split("/").slice(0, 2).join("/")
75
+ : imported.split("/")[0];
76
+ imports.add(packageName);
77
+ }
78
+ }
79
+ }
80
+ };
81
+ await scanDir(projectRoot);
82
+ return imports;
83
+ }
84
+ isCodeFile(filePath) {
85
+ const ext = path.extname(filePath);
86
+ return [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"].includes(ext);
87
+ }
88
+ }
89
+ exports.Scanner = Scanner;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAM7B;;;GAGG;AACH,MAAa,OAAO;IAApB;QACE,2DAA2D;QACnD,gBAAW,GACjB,8EAA8E,CAAC;IAmDnF,CAAC;IAjDC;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,CACR,WAAmB,EACnB,UAA0B,EAAE;QAE5B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAE5E,MAAM,OAAO,GAAG,KAAK,EAAE,GAAW,EAAE,EAAE;YACpC,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YAE7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAErC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAE5C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC1B,CAAC;qBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACrC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;oBACnD,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;oBAEnD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;wBAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;wBAC1B,0DAA0D;wBAC1D,MAAM,WAAW,GAAG,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;4BAC1C,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;4BAC3C,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;wBAE3B,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;oBAC3B,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;QAC3B,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,UAAU,CAAC,QAAgB;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnC,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACtE,CAAC;CACF;AAtDD,0BAsDC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
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 scanner_1 = require("./scanner");
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ describe("Scanner", () => {
40
+ const scanner = new scanner_1.Scanner();
41
+ const testDir = path.join(__dirname, "fixtures");
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 extract import statements", async () => {
53
+ const code = `
54
+ import lodash from 'lodash';
55
+ import { useState } from 'react';
56
+ const express = require('express');
57
+ `;
58
+ const file = path.join(testDir, "test1.js");
59
+ fs.writeFileSync(file, code);
60
+ const imports = await scanner.scan(testDir);
61
+ expect(imports.has("lodash")).toBe(true);
62
+ expect(imports.has("react")).toBe(true);
63
+ expect(imports.has("express")).toBe(true);
64
+ });
65
+ it("should handle scoped packages", async () => {
66
+ const code = `import { Button } from '@mui/material';`;
67
+ const file = path.join(testDir, "test2.tsx");
68
+ fs.writeFileSync(file, code);
69
+ const imports = await scanner.scan(testDir);
70
+ expect(imports.has("@mui/material")).toBe(true);
71
+ });
72
+ it("should ignore node_modules by default", async () => {
73
+ const nmDir = path.join(testDir, "node_modules");
74
+ fs.mkdirSync(nmDir, { recursive: true });
75
+ fs.writeFileSync(path.join(nmDir, "test.js"), "import 'should-be-ignored';");
76
+ const imports = await scanner.scan(testDir);
77
+ expect(imports.has("should-be-ignored")).toBe(false);
78
+ });
79
+ });
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "pkg-clean",
3
+ "version": "1.0.0",
4
+ "description": "Find and remove unused dependencies from your Node.js project",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "pkg-clean": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "test": "jest",
18
+ "test:watch": "jest --watch",
19
+ "prepublishOnly": "npm run build && npm test"
20
+ },
21
+ "keywords": [
22
+ "dependencies",
23
+ "package.json",
24
+ "cleanup",
25
+ "unused",
26
+ "npm",
27
+ "cli",
28
+ "tool",
29
+ "dependency-analysis"
30
+ ],
31
+ "author": "Andrew Passanisi <passanisi.andrew@gmail.com>",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/apassanisi/pkg-clean"
36
+ },
37
+ "homepage": "https://github.com/apassanisi/pkg-clean#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/apassanisi/pkg-clean/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=14.0.0"
43
+ },
44
+ "dependencies": {
45
+ "commander": "^11.1.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/jest": "^29.0.0",
49
+ "jest": "^28.1.3",
50
+ "ts-jest": "^28.0.8",
51
+ "typescript": "^5.3.2"
52
+ }
53
+ }