whyinstall 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Saumya Kushwah
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,69 @@
1
+ # whyinstall
2
+
3
+ CLI tool to find why a dependency exists in your JS/TS project. Works with npm, yarn, and pnpm.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g whyinstall
9
+ ```
10
+
11
+ Or use with npx:
12
+
13
+ ```bash
14
+ npx whyinstall <package-name>
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ whyinstall lodash
21
+ ```
22
+
23
+ ### Options
24
+
25
+ - `-j, --json` - Output results as JSON
26
+ - `-c, --cwd <path>` - Set working directory (default: current directory)
27
+
28
+ ## Features
29
+
30
+ - Works with npm, Yarn, and pnpm (auto-detection)
31
+ - Shows dependency chains for any package
32
+ - Displays dependency type (prod, dev, peer, optional)
33
+ - Shows package description, version, and size
34
+ - Finds source files that actually use the package
35
+ - Colored tree output for readability
36
+ - JSON output for CI/CD
37
+ - Actionable suggestions for optimization
38
+
39
+ ## Example Output
40
+
41
+ ```
42
+ chalk v5.3.0 (43 KB)
43
+ Terminal string styling done right
44
+
45
+ installed via 1 path
46
+
47
+ 1. prod
48
+ whyinstall
49
+ └─> chalk
50
+
51
+ Used in (2):
52
+ src/cli.ts
53
+ src/formatter.ts
54
+
55
+ Suggested actions:
56
+ 1. Can be removed from direct dependencies - it's installed transitively
57
+ ```
58
+
59
+ ## Development
60
+
61
+ ```bash
62
+ npm install
63
+ npm run build
64
+ npm link
65
+ ```
66
+
67
+ ## License
68
+
69
+ MIT
@@ -0,0 +1,3 @@
1
+ import { AnalyzeResult } from './types';
2
+ export declare function analyzePackage(packageName: string, cwd?: string): AnalyzeResult;
3
+ //# sourceMappingURL=analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,EAA+B,aAAa,EAAE,MAAM,SAAS,CAAC;AA+LrE,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,aAAa,CA6D9F"}
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzePackage = analyzePackage;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const fileFinder_1 = require("./fileFinder");
7
+ function readPackageJson(path) {
8
+ try {
9
+ if ((0, fs_1.existsSync)(path)) {
10
+ return JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
11
+ }
12
+ }
13
+ catch {
14
+ // ignore
15
+ }
16
+ return null;
17
+ }
18
+ function findPackageJsonPath(packageName, cwd) {
19
+ try {
20
+ const resolved = require.resolve(`${packageName}/package.json`, { paths: [cwd] });
21
+ return resolved;
22
+ }
23
+ catch {
24
+ const nodeModulesPath = (0, path_1.join)(cwd, 'node_modules', packageName, 'package.json');
25
+ if ((0, fs_1.existsSync)(nodeModulesPath)) {
26
+ return nodeModulesPath;
27
+ }
28
+ return null;
29
+ }
30
+ }
31
+ function findPackageInNodeModules(packageName, basePath) {
32
+ const possiblePaths = [
33
+ (0, path_1.join)(basePath, 'node_modules', packageName, 'package.json'),
34
+ (0, path_1.join)(basePath, packageName, 'package.json')
35
+ ];
36
+ for (const path of possiblePaths) {
37
+ if ((0, fs_1.existsSync)(path)) {
38
+ return path;
39
+ }
40
+ }
41
+ const parent = (0, path_1.dirname)(basePath);
42
+ if (parent !== basePath && parent !== '/') {
43
+ return findPackageInNodeModules(packageName, parent);
44
+ }
45
+ return null;
46
+ }
47
+ function findPackageSize(packagePath) {
48
+ try {
49
+ const stats = (0, fs_1.statSync)(packagePath);
50
+ if (stats.isDirectory()) {
51
+ let totalSize = 0;
52
+ const files = (0, fs_1.readdirSync)(packagePath);
53
+ for (const file of files) {
54
+ const filePath = (0, path_1.join)(packagePath, file);
55
+ try {
56
+ const fileStats = (0, fs_1.statSync)(filePath);
57
+ if (fileStats.isDirectory()) {
58
+ const subSize = findPackageSize(filePath);
59
+ if (subSize)
60
+ totalSize += subSize;
61
+ }
62
+ else {
63
+ totalSize += fileStats.size;
64
+ }
65
+ }
66
+ catch {
67
+ // ignore
68
+ }
69
+ }
70
+ return totalSize;
71
+ }
72
+ }
73
+ catch {
74
+ // ignore
75
+ }
76
+ return undefined;
77
+ }
78
+ function getAllDependencies(pkg) {
79
+ const deps = {};
80
+ if (pkg.dependencies) {
81
+ for (const name of Object.keys(pkg.dependencies)) {
82
+ deps[name] = 'prod';
83
+ }
84
+ }
85
+ if (pkg.devDependencies) {
86
+ for (const name of Object.keys(pkg.devDependencies)) {
87
+ deps[name] = 'dev';
88
+ }
89
+ }
90
+ if (pkg.peerDependencies) {
91
+ for (const name of Object.keys(pkg.peerDependencies)) {
92
+ deps[name] = 'peer';
93
+ }
94
+ }
95
+ if (pkg.optionalDependencies) {
96
+ for (const name of Object.keys(pkg.optionalDependencies)) {
97
+ deps[name] = 'optional';
98
+ }
99
+ }
100
+ return deps;
101
+ }
102
+ function findDependencyPaths(targetPackage, cwd, maxDepth = 10) {
103
+ const paths = [];
104
+ const visited = new Set();
105
+ const rootPackageJson = (0, path_1.join)(cwd, 'package.json');
106
+ if (!(0, fs_1.existsSync)(rootPackageJson)) {
107
+ return [];
108
+ }
109
+ const queue = [{ packageName: '', chain: [], packageJsonPath: rootPackageJson }];
110
+ while (queue.length > 0) {
111
+ const current = queue.shift();
112
+ if (current.chain.length > maxDepth) {
113
+ continue;
114
+ }
115
+ const pkg = readPackageJson(current.packageJsonPath);
116
+ if (!pkg) {
117
+ continue;
118
+ }
119
+ const allDeps = getAllDependencies(pkg);
120
+ const currentPackageName = current.packageName || pkg.name || '';
121
+ const visitKey = `${currentPackageName}:${current.packageJsonPath}`;
122
+ if (visited.has(visitKey)) {
123
+ continue;
124
+ }
125
+ visited.add(visitKey);
126
+ for (const [depName, depType] of Object.entries(allDeps)) {
127
+ if (depName === targetPackage) {
128
+ const chain = currentPackageName
129
+ ? (current.chain.length > 0 ? [...current.chain, currentPackageName, depName] : [currentPackageName, depName])
130
+ : [depName];
131
+ paths.push({
132
+ chain,
133
+ type: depType,
134
+ packageJsonPath: current.packageJsonPath
135
+ });
136
+ }
137
+ else {
138
+ const depPackageJsonPath = findPackageInNodeModules(depName, (0, path_1.dirname)(current.packageJsonPath));
139
+ if (depPackageJsonPath) {
140
+ const newChain = currentPackageName
141
+ ? (current.chain.length > 0 ? [...current.chain, currentPackageName] : [currentPackageName])
142
+ : [];
143
+ queue.push({
144
+ packageName: depName,
145
+ chain: newChain,
146
+ packageJsonPath: depPackageJsonPath
147
+ });
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return paths;
153
+ }
154
+ function formatSize(bytes) {
155
+ if (!bytes)
156
+ return '';
157
+ if (bytes < 1024)
158
+ return `${bytes} B`;
159
+ if (bytes < 1024 * 1024)
160
+ return `${(bytes / 1024).toFixed(0)} KB`;
161
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
162
+ }
163
+ function analyzePackage(packageName, cwd = process.cwd()) {
164
+ const rootPackageJson = (0, path_1.join)(cwd, 'package.json');
165
+ const packageJsonPath = findPackageJsonPath(packageName, cwd);
166
+ if (!packageJsonPath) {
167
+ throw new Error(`Package "${packageName}" not found in node_modules`);
168
+ }
169
+ const packageDir = (0, path_1.dirname)(packageJsonPath);
170
+ const pkg = readPackageJson(packageJsonPath);
171
+ const version = pkg?.version || 'unknown';
172
+ const description = pkg?.description;
173
+ const size = findPackageSize(packageDir);
174
+ let paths = findDependencyPaths(packageName, cwd);
175
+ // Deduplicate paths with same chain and type
176
+ const pathKeys = new Set();
177
+ paths = paths.filter(path => {
178
+ const key = `${path.type}:${path.chain.join('->')}`;
179
+ if (pathKeys.has(key)) {
180
+ return false;
181
+ }
182
+ pathKeys.add(key);
183
+ return true;
184
+ });
185
+ const suggestions = [];
186
+ if (paths.length === 0) {
187
+ suggestions.push(`Package "${packageName}" is not in dependency tree`);
188
+ }
189
+ else {
190
+ const devPaths = paths.filter(p => p.type === 'dev');
191
+ const peerPaths = paths.filter(p => p.type === 'peer');
192
+ if (devPaths.length > 0) {
193
+ suggestions.push(`Consider removing from devDependencies if not needed for development`);
194
+ }
195
+ if (peerPaths.length > 0) {
196
+ suggestions.push(`This is a peer dependency - ensure all consumers satisfy the peer requirement`);
197
+ }
198
+ const directDeps = paths.filter(p => p.chain.length === 1 && p.packageJsonPath === rootPackageJson);
199
+ if (directDeps.length > 0 && paths.length > directDeps.length) {
200
+ suggestions.push(`Can be removed from direct dependencies - it's installed transitively`);
201
+ }
202
+ }
203
+ const sourceFiles = (0, fileFinder_1.findFilesUsingPackage)(packageName, cwd);
204
+ return {
205
+ package: {
206
+ name: packageName,
207
+ version,
208
+ description,
209
+ size,
210
+ paths,
211
+ sourceFiles: sourceFiles.length > 0 ? sourceFiles : undefined
212
+ },
213
+ suggestions
214
+ };
215
+ }
216
+ //# sourceMappingURL=analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -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,39 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const analyzer_1 = require("./analyzer");
10
+ const formatter_1 = require("./formatter");
11
+ const packageManager_1 = require("./packageManager");
12
+ const program = new commander_1.Command();
13
+ program
14
+ .name('whyinstall')
15
+ .description('Find why a dependency exists in your JS/TS project')
16
+ .version('0.1.0')
17
+ .argument('<package-name>', 'Package name to analyze')
18
+ .option('-j, --json', 'Output as JSON')
19
+ .option('-c, --cwd <path>', 'Working directory', process.cwd())
20
+ .action((packageName, options) => {
21
+ try {
22
+ const cwd = options.cwd || process.cwd();
23
+ const pm = (0, packageManager_1.detectPackageManager)(cwd);
24
+ if (!options.json) {
25
+ console.log(`\n${chalk_1.default.gray(`Detected package manager: ${pm}`)}\n`);
26
+ }
27
+ const result = (0, analyzer_1.analyzePackage)(packageName, cwd);
28
+ const output = (0, formatter_1.formatOutput)(result, options.json);
29
+ console.log(output);
30
+ process.exit(0);
31
+ }
32
+ catch (error) {
33
+ const message = error instanceof Error ? error.message : String(error);
34
+ console.error(chalk_1.default.red(`Error: ${message}`));
35
+ process.exit(1);
36
+ }
37
+ });
38
+ program.parse();
39
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;AAEA,yCAAoC;AACpC,kDAA0B;AAC1B,yCAA4C;AAC5C,2CAA2C;AAC3C,qDAAwD;AAExD,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,oDAAoD,CAAC;KACjE,OAAO,CAAC,OAAO,CAAC;KAChB,QAAQ,CAAC,gBAAgB,EAAE,yBAAyB,CAAC;KACrD,MAAM,CAAC,YAAY,EAAE,gBAAgB,CAAC;KACtC,MAAM,CAAC,kBAAkB,EAAE,mBAAmB,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;KAC9D,MAAM,CAAC,CAAC,WAAmB,EAAE,OAAyC,EAAE,EAAE;IACzE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,IAAA,qCAAoB,EAAC,GAAG,CAAC,CAAC;QAErC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,KAAK,eAAK,CAAC,IAAI,CAAC,6BAA6B,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,MAAM,GAAG,IAAA,yBAAc,EAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAA,wBAAY,EAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,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,eAAK,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function findFilesUsingPackage(packageName: string, cwd: string): string[];
2
+ //# sourceMappingURL=fileFinder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileFinder.d.ts","sourceRoot":"","sources":["../src/fileFinder.ts"],"names":[],"mappings":"AAiEA,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAYhF"}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findFilesUsingPackage = findFilesUsingPackage;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
7
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];
8
+ function shouldIgnoreDir(dirName) {
9
+ return IGNORE_DIRS.includes(dirName) || dirName.startsWith('.');
10
+ }
11
+ function isSourceFile(filePath) {
12
+ return SOURCE_EXTENSIONS.includes((0, path_1.extname)(filePath));
13
+ }
14
+ function findSourceFiles(dir, maxDepth = 5, currentDepth = 0) {
15
+ if (currentDepth > maxDepth) {
16
+ return [];
17
+ }
18
+ const files = [];
19
+ try {
20
+ const entries = (0, fs_1.readdirSync)(dir);
21
+ for (const entry of entries) {
22
+ const fullPath = (0, path_1.join)(dir, entry);
23
+ try {
24
+ const stats = (0, fs_1.statSync)(fullPath);
25
+ if (stats.isDirectory()) {
26
+ if (!shouldIgnoreDir(entry)) {
27
+ files.push(...findSourceFiles(fullPath, maxDepth, currentDepth + 1));
28
+ }
29
+ }
30
+ else if (stats.isFile() && isSourceFile(fullPath)) {
31
+ files.push(fullPath);
32
+ }
33
+ }
34
+ catch {
35
+ // ignore errors for individual files/dirs
36
+ }
37
+ }
38
+ }
39
+ catch {
40
+ // ignore errors
41
+ }
42
+ return files;
43
+ }
44
+ function fileContainsPackage(filePath, packageName) {
45
+ try {
46
+ const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
47
+ const patterns = [
48
+ new RegExp(`require\\(['"]${packageName}(/.*)?['"]\\)`, 'g'),
49
+ new RegExp(`from\\s+['"]${packageName}(/.*)?['"]`, 'g'),
50
+ new RegExp(`import\\s+.*\\s+from\\s+['"]${packageName}(/.*)?['"]`, 'g'),
51
+ new RegExp(`import\\s+['"]${packageName}(/.*)?['"]`, 'g'),
52
+ ];
53
+ return patterns.some(pattern => pattern.test(content));
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ function findFilesUsingPackage(packageName, cwd) {
60
+ const sourceFiles = findSourceFiles(cwd);
61
+ const matchingFiles = [];
62
+ for (const file of sourceFiles) {
63
+ if (fileContainsPackage(file, packageName)) {
64
+ const relativePath = file.replace(cwd + '/', '');
65
+ matchingFiles.push(relativePath);
66
+ }
67
+ }
68
+ return matchingFiles;
69
+ }
70
+ //# sourceMappingURL=fileFinder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileFinder.js","sourceRoot":"","sources":["../src/fileFinder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { AnalyzeResult } from './types';
2
+ export declare function formatOutput(result: AnalyzeResult, json?: boolean): string;
3
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../src/formatter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAkB,MAAM,SAAS,CAAC;AAwDxD,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,GAAE,OAAe,GAAG,MAAM,CA8CjF"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.formatOutput = formatOutput;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ function formatSize(bytes) {
9
+ if (!bytes)
10
+ return '';
11
+ if (bytes < 1024)
12
+ return `${bytes} B`;
13
+ if (bytes < 1024 * 1024)
14
+ return `${(bytes / 1024).toFixed(0)} KB`;
15
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
16
+ }
17
+ function formatChain(chain, isLast) {
18
+ if (chain.length === 0)
19
+ return '';
20
+ const parts = [];
21
+ for (let i = 0; i < chain.length; i++) {
22
+ const isLastInChain = i === chain.length - 1;
23
+ let prefix;
24
+ let indent = '';
25
+ if (i === 0) {
26
+ prefix = '';
27
+ indent = ' ';
28
+ }
29
+ else {
30
+ indent = ' ' + ' '.repeat((i - 1) * 3);
31
+ if (isLastInChain) {
32
+ prefix = '└─> ';
33
+ }
34
+ else {
35
+ prefix = '├─> ';
36
+ }
37
+ }
38
+ let packageName = chain[i];
39
+ if (packageName.includes('node_modules/')) {
40
+ packageName = chalk_1.default.gray(packageName.replace(/.*node_modules\//, 'node_modules/'));
41
+ }
42
+ else if (packageName.includes('/')) {
43
+ packageName = chalk_1.default.white(packageName);
44
+ }
45
+ else {
46
+ packageName = chalk_1.default.cyan(packageName);
47
+ }
48
+ parts.push(`${indent}${prefix}${packageName}`);
49
+ }
50
+ return parts.join('\n');
51
+ }
52
+ function getTypeLabel(type) {
53
+ const labels = {
54
+ prod: chalk_1.default.green('prod'),
55
+ dev: chalk_1.default.yellow('dev'),
56
+ peer: chalk_1.default.blue('peer'),
57
+ optional: chalk_1.default.gray('optional')
58
+ };
59
+ return labels[type] || type;
60
+ }
61
+ function formatOutput(result, json = false) {
62
+ if (json) {
63
+ return JSON.stringify(result, null, 2);
64
+ }
65
+ const { package: pkg, suggestions } = result;
66
+ const sizeStr = pkg.size ? formatSize(pkg.size) : '';
67
+ const sizeDisplay = sizeStr ? ` (${sizeStr})` : '';
68
+ let output = '';
69
+ output += chalk_1.default.bold.cyan(`${pkg.name}`) + chalk_1.default.gray(` v${pkg.version}`) + chalk_1.default.dim(`${sizeDisplay}`) + '\n';
70
+ if (pkg.description) {
71
+ output += chalk_1.default.white(` ${pkg.description}\n`);
72
+ }
73
+ output += '\n' + chalk_1.default.dim(` installed via ${pkg.paths.length} path${pkg.paths.length !== 1 ? 's' : ''}\n\n`);
74
+ if (pkg.paths.length === 0) {
75
+ output += chalk_1.default.yellow(' No dependency paths found.\n');
76
+ }
77
+ else {
78
+ pkg.paths.forEach((path, index) => {
79
+ const pathNum = chalk_1.default.gray(`${index + 1}.`);
80
+ const typeLabel = getTypeLabel(path.type);
81
+ let chainDisplay = path.chain.length > 0 ? path.chain : [pkg.name];
82
+ output += `${pathNum} ${typeLabel}\n`;
83
+ output += formatChain(chainDisplay, index === pkg.paths.length - 1);
84
+ output += '\n';
85
+ });
86
+ }
87
+ if (pkg.sourceFiles && pkg.sourceFiles.length > 0) {
88
+ output += '\n' + chalk_1.default.bold(`Used in (${pkg.sourceFiles.length}):`) + '\n';
89
+ pkg.sourceFiles.forEach((file) => {
90
+ output += ` ${chalk_1.default.blue(file)}\n`;
91
+ });
92
+ }
93
+ if (suggestions.length > 0) {
94
+ output += '\n' + chalk_1.default.bold('Suggested actions:') + '\n';
95
+ suggestions.forEach((suggestion, index) => {
96
+ output += ` ${index + 1}. ${chalk_1.default.gray(suggestion)}\n`;
97
+ });
98
+ }
99
+ return output;
100
+ }
101
+ //# sourceMappingURL=formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../src/formatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { PackageManager } from './types';
2
+ export declare function detectPackageManager(cwd?: string): PackageManager;
3
+ export declare function getLockFilePath(cwd: string, pm: PackageManager): string;
4
+ //# sourceMappingURL=packageManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageManager.d.ts","sourceRoot":"","sources":["../src/packageManager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAsB,GAAG,cAAc,CAWhF;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,GAAG,MAAM,CAOvE"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectPackageManager = detectPackageManager;
4
+ exports.getLockFilePath = getLockFilePath;
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ function detectPackageManager(cwd = process.cwd()) {
8
+ if ((0, fs_1.existsSync)((0, path_1.join)(cwd, 'pnpm-lock.yaml'))) {
9
+ return 'pnpm';
10
+ }
11
+ if ((0, fs_1.existsSync)((0, path_1.join)(cwd, 'yarn.lock'))) {
12
+ return 'yarn';
13
+ }
14
+ if ((0, fs_1.existsSync)((0, path_1.join)(cwd, 'package-lock.json'))) {
15
+ return 'npm';
16
+ }
17
+ return 'npm';
18
+ }
19
+ function getLockFilePath(cwd, pm) {
20
+ const paths = {
21
+ npm: 'package-lock.json',
22
+ yarn: 'yarn.lock',
23
+ pnpm: 'pnpm-lock.yaml'
24
+ };
25
+ return (0, path_1.join)(cwd, paths[pm]);
26
+ }
27
+ //# sourceMappingURL=packageManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageManager.js","sourceRoot":"","sources":["../src/packageManager.ts"],"names":[],"mappings":";;AAIA,oDAWC;AAED,0CAOC;AAxBD,2BAAgC;AAChC,+BAA4B;AAG5B,SAAgB,oBAAoB,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC9D,IAAI,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC;QAC5C,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,GAAG,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;QACvC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAgB,eAAe,CAAC,GAAW,EAAE,EAAkB;IAC7D,MAAM,KAAK,GAAG;QACZ,GAAG,EAAE,mBAAmB;QACxB,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,gBAAgB;KACvB,CAAC;IACF,OAAO,IAAA,WAAI,EAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,19 @@
1
+ export interface DependencyPath {
2
+ chain: string[];
3
+ type: 'prod' | 'dev' | 'peer' | 'optional';
4
+ packageJsonPath?: string;
5
+ }
6
+ export interface PackageInfo {
7
+ name: string;
8
+ version: string;
9
+ description?: string;
10
+ size?: number;
11
+ paths: DependencyPath[];
12
+ sourceFiles?: string[];
13
+ }
14
+ export interface AnalyzeResult {
15
+ package: PackageInfo;
16
+ suggestions: string[];
17
+ }
18
+ export type PackageManager = 'npm' | 'yarn' | 'pnpm';
19
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,CAAC;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "whyinstall",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool to find why a dependency exists in your JS/TS project",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "whyinstall": "dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc --watch",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "dependencies",
16
+ "cli",
17
+ "npm",
18
+ "yarn",
19
+ "pnpm",
20
+ "dependency-tree"
21
+ ],
22
+ "author": "Saumya Kushwah",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "chalk": "^5.3.0",
26
+ "commander": "^11.1.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.10.0",
30
+ "typescript": "^5.3.3"
31
+ },
32
+ "engines": {
33
+ "node": ">=16.0.0"
34
+ }
35
+ }
@@ -0,0 +1,255 @@
1
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { PackageInfo, DependencyPath, AnalyzeResult } from './types';
4
+ import { detectPackageManager } from './packageManager';
5
+ import { findFilesUsingPackage } from './fileFinder';
6
+
7
+ interface PackageJson {
8
+ name?: string;
9
+ version?: string;
10
+ description?: string;
11
+ dependencies?: Record<string, string>;
12
+ devDependencies?: Record<string, string>;
13
+ peerDependencies?: Record<string, string>;
14
+ optionalDependencies?: Record<string, string>;
15
+ }
16
+
17
+ function readPackageJson(path: string): PackageJson | null {
18
+ try {
19
+ if (existsSync(path)) {
20
+ return JSON.parse(readFileSync(path, 'utf-8'));
21
+ }
22
+ } catch {
23
+ // ignore
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function findPackageJsonPath(packageName: string, cwd: string): string | null {
29
+ try {
30
+ const resolved = require.resolve(`${packageName}/package.json`, { paths: [cwd] });
31
+ return resolved;
32
+ } catch {
33
+ const nodeModulesPath = join(cwd, 'node_modules', packageName, 'package.json');
34
+ if (existsSync(nodeModulesPath)) {
35
+ return nodeModulesPath;
36
+ }
37
+ return null;
38
+ }
39
+ }
40
+
41
+ function findPackageInNodeModules(packageName: string, basePath: string): string | null {
42
+ const possiblePaths = [
43
+ join(basePath, 'node_modules', packageName, 'package.json'),
44
+ join(basePath, packageName, 'package.json')
45
+ ];
46
+
47
+ for (const path of possiblePaths) {
48
+ if (existsSync(path)) {
49
+ return path;
50
+ }
51
+ }
52
+
53
+ const parent = dirname(basePath);
54
+ if (parent !== basePath && parent !== '/') {
55
+ return findPackageInNodeModules(packageName, parent);
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ function findPackageSize(packagePath: string): number | undefined {
62
+ try {
63
+ const stats = statSync(packagePath);
64
+ if (stats.isDirectory()) {
65
+ let totalSize = 0;
66
+ const files = readdirSync(packagePath);
67
+ for (const file of files) {
68
+ const filePath = join(packagePath, file);
69
+ try {
70
+ const fileStats = statSync(filePath);
71
+ if (fileStats.isDirectory()) {
72
+ const subSize = findPackageSize(filePath);
73
+ if (subSize) totalSize += subSize;
74
+ } else {
75
+ totalSize += fileStats.size;
76
+ }
77
+ } catch {
78
+ // ignore
79
+ }
80
+ }
81
+ return totalSize;
82
+ }
83
+ } catch {
84
+ // ignore
85
+ }
86
+ return undefined;
87
+ }
88
+
89
+ function getAllDependencies(pkg: PackageJson): Record<string, 'prod' | 'dev' | 'peer' | 'optional'> {
90
+ const deps: Record<string, 'prod' | 'dev' | 'peer' | 'optional'> = {};
91
+
92
+ if (pkg.dependencies) {
93
+ for (const name of Object.keys(pkg.dependencies)) {
94
+ deps[name] = 'prod';
95
+ }
96
+ }
97
+ if (pkg.devDependencies) {
98
+ for (const name of Object.keys(pkg.devDependencies)) {
99
+ deps[name] = 'dev';
100
+ }
101
+ }
102
+ if (pkg.peerDependencies) {
103
+ for (const name of Object.keys(pkg.peerDependencies)) {
104
+ deps[name] = 'peer';
105
+ }
106
+ }
107
+ if (pkg.optionalDependencies) {
108
+ for (const name of Object.keys(pkg.optionalDependencies)) {
109
+ deps[name] = 'optional';
110
+ }
111
+ }
112
+
113
+ return deps;
114
+ }
115
+
116
+ function findDependencyPaths(
117
+ targetPackage: string,
118
+ cwd: string,
119
+ maxDepth: number = 10
120
+ ): DependencyPath[] {
121
+ const paths: DependencyPath[] = [];
122
+ const visited = new Set<string>();
123
+
124
+ interface QueueItem {
125
+ packageName: string;
126
+ chain: string[];
127
+ packageJsonPath: string;
128
+ }
129
+
130
+ const rootPackageJson = join(cwd, 'package.json');
131
+ if (!existsSync(rootPackageJson)) {
132
+ return [];
133
+ }
134
+
135
+ const queue: QueueItem[] = [{ packageName: '', chain: [], packageJsonPath: rootPackageJson }];
136
+
137
+ while (queue.length > 0) {
138
+ const current = queue.shift()!;
139
+
140
+ if (current.chain.length > maxDepth) {
141
+ continue;
142
+ }
143
+
144
+ const pkg = readPackageJson(current.packageJsonPath);
145
+ if (!pkg) {
146
+ continue;
147
+ }
148
+
149
+ const allDeps = getAllDependencies(pkg);
150
+ const currentPackageName = current.packageName || pkg.name || '';
151
+
152
+ const visitKey = `${currentPackageName}:${current.packageJsonPath}`;
153
+ if (visited.has(visitKey)) {
154
+ continue;
155
+ }
156
+ visited.add(visitKey);
157
+
158
+ for (const [depName, depType] of Object.entries(allDeps)) {
159
+ if (depName === targetPackage) {
160
+ const chain = currentPackageName
161
+ ? (current.chain.length > 0 ? [...current.chain, currentPackageName, depName] : [currentPackageName, depName])
162
+ : [depName];
163
+ paths.push({
164
+ chain,
165
+ type: depType,
166
+ packageJsonPath: current.packageJsonPath
167
+ });
168
+ } else {
169
+ const depPackageJsonPath = findPackageInNodeModules(depName, dirname(current.packageJsonPath));
170
+ if (depPackageJsonPath) {
171
+ const newChain = currentPackageName
172
+ ? (current.chain.length > 0 ? [...current.chain, currentPackageName] : [currentPackageName])
173
+ : [];
174
+ queue.push({
175
+ packageName: depName,
176
+ chain: newChain,
177
+ packageJsonPath: depPackageJsonPath
178
+ });
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ return paths;
185
+ }
186
+
187
+ function formatSize(bytes: number | undefined): string {
188
+ if (!bytes) return '';
189
+ if (bytes < 1024) return `${bytes} B`;
190
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
191
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
192
+ }
193
+
194
+ export function analyzePackage(packageName: string, cwd: string = process.cwd()): AnalyzeResult {
195
+ const rootPackageJson = join(cwd, 'package.json');
196
+ const packageJsonPath = findPackageJsonPath(packageName, cwd);
197
+ if (!packageJsonPath) {
198
+ throw new Error(`Package "${packageName}" not found in node_modules`);
199
+ }
200
+
201
+ const packageDir = dirname(packageJsonPath);
202
+ const pkg = readPackageJson(packageJsonPath);
203
+ const version = pkg?.version || 'unknown';
204
+ const description = pkg?.description;
205
+
206
+ const size = findPackageSize(packageDir);
207
+
208
+ let paths = findDependencyPaths(packageName, cwd);
209
+
210
+ // Deduplicate paths with same chain and type
211
+ const pathKeys = new Set<string>();
212
+ paths = paths.filter(path => {
213
+ const key = `${path.type}:${path.chain.join('->')}`;
214
+ if (pathKeys.has(key)) {
215
+ return false;
216
+ }
217
+ pathKeys.add(key);
218
+ return true;
219
+ });
220
+
221
+ const suggestions: string[] = [];
222
+
223
+ if (paths.length === 0) {
224
+ suggestions.push(`Package "${packageName}" is not in dependency tree`);
225
+ } else {
226
+ const devPaths = paths.filter(p => p.type === 'dev');
227
+ const peerPaths = paths.filter(p => p.type === 'peer');
228
+
229
+ if (devPaths.length > 0) {
230
+ suggestions.push(`Consider removing from devDependencies if not needed for development`);
231
+ }
232
+ if (peerPaths.length > 0) {
233
+ suggestions.push(`This is a peer dependency - ensure all consumers satisfy the peer requirement`);
234
+ }
235
+
236
+ const directDeps = paths.filter(p => p.chain.length === 1 && p.packageJsonPath === rootPackageJson);
237
+ if (directDeps.length > 0 && paths.length > directDeps.length) {
238
+ suggestions.push(`Can be removed from direct dependencies - it's installed transitively`);
239
+ }
240
+ }
241
+
242
+ const sourceFiles = findFilesUsingPackage(packageName, cwd);
243
+
244
+ return {
245
+ package: {
246
+ name: packageName,
247
+ version,
248
+ description,
249
+ size,
250
+ paths,
251
+ sourceFiles: sourceFiles.length > 0 ? sourceFiles : undefined
252
+ },
253
+ suggestions
254
+ };
255
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { analyzePackage } from './analyzer';
6
+ import { formatOutput } from './formatter';
7
+ import { detectPackageManager } from './packageManager';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('whyinstall')
13
+ .description('Find why a dependency exists in your JS/TS project')
14
+ .version('0.1.0')
15
+ .argument('<package-name>', 'Package name to analyze')
16
+ .option('-j, --json', 'Output as JSON')
17
+ .option('-c, --cwd <path>', 'Working directory', process.cwd())
18
+ .action((packageName: string, options: { json?: boolean; cwd?: string }) => {
19
+ try {
20
+ const cwd = options.cwd || process.cwd();
21
+ const pm = detectPackageManager(cwd);
22
+
23
+ if (!options.json) {
24
+ console.log(`\n${chalk.gray(`Detected package manager: ${pm}`)}\n`);
25
+ }
26
+
27
+ const result = analyzePackage(packageName, cwd);
28
+ const output = formatOutput(result, options.json);
29
+ console.log(output);
30
+
31
+ process.exit(0);
32
+ } catch (error) {
33
+ const message = error instanceof Error ? error.message : String(error);
34
+ console.error(chalk.red(`Error: ${message}`));
35
+ process.exit(1);
36
+ }
37
+ });
38
+
39
+ program.parse();
@@ -0,0 +1,78 @@
1
+ import { readdirSync, readFileSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+
4
+ const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
5
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];
6
+
7
+ function shouldIgnoreDir(dirName: string): boolean {
8
+ return IGNORE_DIRS.includes(dirName) || dirName.startsWith('.');
9
+ }
10
+
11
+ function isSourceFile(filePath: string): boolean {
12
+ return SOURCE_EXTENSIONS.includes(extname(filePath));
13
+ }
14
+
15
+ function findSourceFiles(dir: string, maxDepth: number = 5, currentDepth: number = 0): string[] {
16
+ if (currentDepth > maxDepth) {
17
+ return [];
18
+ }
19
+
20
+ const files: string[] = [];
21
+
22
+ try {
23
+ const entries = readdirSync(dir);
24
+
25
+ for (const entry of entries) {
26
+ const fullPath = join(dir, entry);
27
+
28
+ try {
29
+ const stats = statSync(fullPath);
30
+
31
+ if (stats.isDirectory()) {
32
+ if (!shouldIgnoreDir(entry)) {
33
+ files.push(...findSourceFiles(fullPath, maxDepth, currentDepth + 1));
34
+ }
35
+ } else if (stats.isFile() && isSourceFile(fullPath)) {
36
+ files.push(fullPath);
37
+ }
38
+ } catch {
39
+ // ignore errors for individual files/dirs
40
+ }
41
+ }
42
+ } catch {
43
+ // ignore errors
44
+ }
45
+
46
+ return files;
47
+ }
48
+
49
+ function fileContainsPackage(filePath: string, packageName: string): boolean {
50
+ try {
51
+ const content = readFileSync(filePath, 'utf-8');
52
+
53
+ const patterns = [
54
+ new RegExp(`require\\(['"]${packageName}(/.*)?['"]\\)`, 'g'),
55
+ new RegExp(`from\\s+['"]${packageName}(/.*)?['"]`, 'g'),
56
+ new RegExp(`import\\s+.*\\s+from\\s+['"]${packageName}(/.*)?['"]`, 'g'),
57
+ new RegExp(`import\\s+['"]${packageName}(/.*)?['"]`, 'g'),
58
+ ];
59
+
60
+ return patterns.some(pattern => pattern.test(content));
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ export function findFilesUsingPackage(packageName: string, cwd: string): string[] {
67
+ const sourceFiles = findSourceFiles(cwd);
68
+ const matchingFiles: string[] = [];
69
+
70
+ for (const file of sourceFiles) {
71
+ if (fileContainsPackage(file, packageName)) {
72
+ const relativePath = file.replace(cwd + '/', '');
73
+ matchingFiles.push(relativePath);
74
+ }
75
+ }
76
+
77
+ return matchingFiles;
78
+ }
@@ -0,0 +1,104 @@
1
+ import chalk from 'chalk';
2
+ import { AnalyzeResult, DependencyPath } from './types';
3
+
4
+ function formatSize(bytes: number | undefined): string {
5
+ if (!bytes) return '';
6
+ if (bytes < 1024) return `${bytes} B`;
7
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
8
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
9
+ }
10
+
11
+ function formatChain(chain: string[], isLast: boolean): string {
12
+ if (chain.length === 0) return '';
13
+
14
+ const parts: string[] = [];
15
+
16
+ for (let i = 0; i < chain.length; i++) {
17
+ const isLastInChain = i === chain.length - 1;
18
+ let prefix: string;
19
+ let indent = '';
20
+
21
+ if (i === 0) {
22
+ prefix = '';
23
+ indent = ' ';
24
+ } else {
25
+ indent = ' ' + ' '.repeat((i - 1) * 3);
26
+ if (isLastInChain) {
27
+ prefix = '└─> ';
28
+ } else {
29
+ prefix = '├─> ';
30
+ }
31
+ }
32
+
33
+ let packageName = chain[i];
34
+ if (packageName.includes('node_modules/')) {
35
+ packageName = chalk.gray(packageName.replace(/.*node_modules\//, 'node_modules/'));
36
+ } else if (packageName.includes('/')) {
37
+ packageName = chalk.white(packageName);
38
+ } else {
39
+ packageName = chalk.cyan(packageName);
40
+ }
41
+
42
+ parts.push(`${indent}${prefix}${packageName}`);
43
+ }
44
+
45
+ return parts.join('\n');
46
+ }
47
+
48
+ function getTypeLabel(type: DependencyPath['type']): string {
49
+ const labels = {
50
+ prod: chalk.green('prod'),
51
+ dev: chalk.yellow('dev'),
52
+ peer: chalk.blue('peer'),
53
+ optional: chalk.gray('optional')
54
+ };
55
+ return labels[type] || type;
56
+ }
57
+
58
+ export function formatOutput(result: AnalyzeResult, json: boolean = false): string {
59
+ if (json) {
60
+ return JSON.stringify(result, null, 2);
61
+ }
62
+
63
+ const { package: pkg, suggestions } = result;
64
+ const sizeStr = pkg.size ? formatSize(pkg.size) : '';
65
+ const sizeDisplay = sizeStr ? ` (${sizeStr})` : '';
66
+
67
+ let output = '';
68
+ output += chalk.bold.cyan(`${pkg.name}`) + chalk.gray(` v${pkg.version}`) + chalk.dim(`${sizeDisplay}`) + '\n';
69
+ if (pkg.description) {
70
+ output += chalk.white(` ${pkg.description}\n`);
71
+ }
72
+ output += '\n' + chalk.dim(` installed via ${pkg.paths.length} path${pkg.paths.length !== 1 ? 's' : ''}\n\n`);
73
+
74
+ if (pkg.paths.length === 0) {
75
+ output += chalk.yellow(' No dependency paths found.\n');
76
+ } else {
77
+ pkg.paths.forEach((path, index) => {
78
+ const pathNum = chalk.gray(`${index + 1}.`);
79
+ const typeLabel = getTypeLabel(path.type);
80
+
81
+ let chainDisplay = path.chain.length > 0 ? path.chain : [pkg.name];
82
+
83
+ output += `${pathNum} ${typeLabel}\n`;
84
+ output += formatChain(chainDisplay, index === pkg.paths.length - 1);
85
+ output += '\n';
86
+ });
87
+ }
88
+
89
+ if (pkg.sourceFiles && pkg.sourceFiles.length > 0) {
90
+ output += '\n' + chalk.bold(`Used in (${pkg.sourceFiles.length}):`) + '\n';
91
+ pkg.sourceFiles.forEach((file) => {
92
+ output += ` ${chalk.blue(file)}\n`;
93
+ });
94
+ }
95
+
96
+ if (suggestions.length > 0) {
97
+ output += '\n' + chalk.bold('Suggested actions:') + '\n';
98
+ suggestions.forEach((suggestion, index) => {
99
+ output += ` ${index + 1}. ${chalk.gray(suggestion)}\n`;
100
+ });
101
+ }
102
+
103
+ return output;
104
+ }
@@ -0,0 +1,25 @@
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { PackageManager } from './types';
4
+
5
+ export function detectPackageManager(cwd: string = process.cwd()): PackageManager {
6
+ if (existsSync(join(cwd, 'pnpm-lock.yaml'))) {
7
+ return 'pnpm';
8
+ }
9
+ if (existsSync(join(cwd, 'yarn.lock'))) {
10
+ return 'yarn';
11
+ }
12
+ if (existsSync(join(cwd, 'package-lock.json'))) {
13
+ return 'npm';
14
+ }
15
+ return 'npm';
16
+ }
17
+
18
+ export function getLockFilePath(cwd: string, pm: PackageManager): string {
19
+ const paths = {
20
+ npm: 'package-lock.json',
21
+ yarn: 'yarn.lock',
22
+ pnpm: 'pnpm-lock.yaml'
23
+ };
24
+ return join(cwd, paths[pm]);
25
+ }
package/src/types.ts ADDED
@@ -0,0 +1,21 @@
1
+ export interface DependencyPath {
2
+ chain: string[];
3
+ type: 'prod' | 'dev' | 'peer' | 'optional';
4
+ packageJsonPath?: string;
5
+ }
6
+
7
+ export interface PackageInfo {
8
+ name: string;
9
+ version: string;
10
+ description?: string;
11
+ size?: number;
12
+ paths: DependencyPath[];
13
+ sourceFiles?: string[];
14
+ }
15
+
16
+ export interface AnalyzeResult {
17
+ package: PackageInfo;
18
+ suggestions: string[];
19
+ }
20
+
21
+ export type PackageManager = 'npm' | 'yarn' | 'pnpm';
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "moduleResolution": "node",
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }