importy 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,171 @@
1
1
  # Importy
2
+
3
+ A powerful CLI tool for analyzing JavaScript/TypeScript imports from libraries.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/importy.svg)](https://www.npmjs.com/package/importy)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://github.com/yourusername/importy)
8
+
9
+ ## Overview
10
+
11
+ Importy scans your codebase to identify and analyze imports from specific libraries. It helps you:
12
+
13
+ - Identify which components from a library are being used in your codebase
14
+ - Find all occurrences of specific imported components
15
+ - Analyze library usage patterns across your project
16
+ - Generate detailed reports for dependency management
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # Using npm
22
+ npm install -g importy
23
+
24
+ # Using yarn
25
+ yarn global add importy
26
+
27
+ # Using pnpm
28
+ pnpm add -g importy
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ importy --dir <directory> --lib <library-name> [options]
35
+ ```
36
+
37
+ ### Required Options
38
+
39
+ - `-d, --dir <directory>`: Directory to scan (required)
40
+ - `-l, --lib <library>`: Library name to match (required)
41
+
42
+ ### Additional Options
43
+
44
+ - `-o, --output <file>`: Output results to a JSON file instead of stdout
45
+ - `-v, --verbose`: Enable verbose logging
46
+ - `-i, --include <pattern>`: Only include files matching pattern (glob)
47
+ - `-e, --exclude <pattern>`: Exclude files matching pattern (glob)
48
+ - `-c, --concurrency <number>`: Number of worker threads (defaults to CPU count - 1)
49
+ - `--version`: Show version number
50
+ - `--help`: Show help
51
+
52
+ ## Examples
53
+
54
+ ### Basic Usage
55
+
56
+ ```bash
57
+ # Find all React imports in src directory
58
+ importy --dir ./src --lib react
59
+
60
+ # Find all MUI components used in your project
61
+ importy --dir ./src --lib @mui/material
62
+ ```
63
+
64
+ ### Advanced Usage
65
+
66
+ ```bash
67
+ # Export results to a JSON file
68
+ importy --dir ./src --lib lodash --output imports.json
69
+
70
+ # Only scan TypeScript files
71
+ importy --dir ./src --lib axios --include "**/*.ts"
72
+
73
+ # Exclude test files
74
+ importy --dir ./src --lib react --exclude "**/*.test.{ts,tsx}"
75
+
76
+ # Limit concurrency
77
+ importy --dir ./src --lib react --concurrency 4
78
+ ```
79
+
80
+ ## Output Format
81
+
82
+ The tool outputs JSON in the following format:
83
+
84
+ ```json
85
+ {
86
+ "summary": {
87
+ "library": "react",
88
+ "componentsFound": 5,
89
+ "totalImports": 12,
90
+ "filesScanned": 42
91
+ },
92
+ "components": {
93
+ "useState": [
94
+ "src/components/Counter.tsx",
95
+ "src/components/Form.tsx"
96
+ ],
97
+ "useEffect": [
98
+ "src/components/Dashboard.tsx"
99
+ ],
100
+ "Component": [
101
+ "src/components/BaseComponent.tsx"
102
+ ]
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## Performance
108
+
109
+ Importy uses parallel processing with promises, making it efficient even for large codebases. You can adjust the concurrency level to match your system's capabilities using the `--concurrency` option.
110
+
111
+ ## Development
112
+
113
+ ### Prerequisites
114
+
115
+ - Node.js 16+
116
+ - npm, yarn, or pnpm
117
+
118
+ ### Setup
119
+
120
+ ```bash
121
+ # Clone the repository
122
+ git clone https://github.com/yourusername/importy.git
123
+ cd importy
124
+
125
+ # Install dependencies
126
+ npm install
127
+
128
+ # Build the project
129
+ npm run build
130
+
131
+ # Run tests
132
+ npm test
133
+ ```
134
+
135
+ ### Testing
136
+
137
+ Importy uses Vitest for testing. There are two types of tests:
138
+
139
+ 1. **Programmatic tests**: Test the core functionality through the JavaScript API
140
+ 2. **CLI tests**: Test the command-line interface
141
+
142
+ Run tests with:
143
+
144
+ ```bash
145
+ # Run all tests
146
+ npm test
147
+
148
+ # Run specific tests
149
+ npx vitest run tests/programmatic.test.ts
150
+ ```
151
+
152
+ ## Contributing
153
+
154
+ Contributions are welcome! Please feel free to submit a Pull Request.
155
+
156
+ 1. Fork the repository
157
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
158
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
159
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
160
+ 5. Open a Pull Request
161
+
162
+ ## Troubleshooting
163
+
164
+ ### Common Issues
165
+
166
+ - **ES Module Compatibility**: If you encounter issues with ES modules, ensure your Node.js version is compatible (16+) and you're using the correct import syntax.
167
+ - **Parsing Errors**: Complex TypeScript/JSX syntax may occasionally cause parsing errors. These files are skipped with a warning.
168
+
169
+ ## License
170
+
171
+ This project is licensed under the MIT License - see the LICENSE file for details.
package/dist/index.js CHANGED
@@ -1,74 +1,292 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import fs2 from "fs";
5
+ import path2 from "path";
6
+ import { program } from "commander";
7
+ import { fileURLToPath } from "url";
8
+ import { dirname } from "path";
9
+
10
+ // src/cli.ts
4
11
  import fs from "fs";
5
12
  import path from "path";
6
- import { program } from "commander";
7
13
  import * as parser from "@babel/parser";
8
14
  import _traverse from "@babel/traverse";
9
- var traverse = _traverse.default ?? _traverse;
10
- program.requiredOption("-d, --dir <directory>", "Directory to scan").requiredOption("-l, --lib <library>", "Library name to match").parse(process.argv);
11
- var { dir, lib } = program.opts();
15
+ import os from "os";
16
+ var traverse = _traverse.default;
12
17
  function isJavaScriptFile(file) {
13
18
  return /\.(js|ts|jsx|tsx)$/.test(file);
14
19
  }
15
- function getAllFiles(dirPath, arrayOfFiles = []) {
16
- const files = fs.readdirSync(dirPath);
17
- for (const file of files) {
18
- const fullPath = path.join(dirPath, file);
19
- const stat = fs.statSync(fullPath);
20
- if (stat.isDirectory()) {
21
- getAllFiles(fullPath, arrayOfFiles);
22
- } else if (isJavaScriptFile(fullPath)) {
23
- arrayOfFiles.push(fullPath);
20
+ function getAllFiles(dirPath, arrayOfFiles = [], includePattern, excludePattern, verbose = false) {
21
+ try {
22
+ const files = fs.readdirSync(dirPath);
23
+ for (const file of files) {
24
+ const fullPath = path.join(dirPath, file);
25
+ try {
26
+ const stat = fs.statSync(fullPath);
27
+ if (excludePattern && stat.isDirectory() && minimatch(fullPath, excludePattern)) {
28
+ if (verbose) console.log(`Skipping excluded directory: ${fullPath}`);
29
+ continue;
30
+ }
31
+ if (stat.isDirectory()) {
32
+ getAllFiles(fullPath, arrayOfFiles, includePattern, excludePattern, verbose);
33
+ } else if (isJavaScriptFile(fullPath)) {
34
+ const shouldInclude = !includePattern || minimatch(fullPath, includePattern);
35
+ const shouldExclude = excludePattern && minimatch(fullPath, excludePattern);
36
+ if (shouldInclude && !shouldExclude) {
37
+ arrayOfFiles.push(fullPath);
38
+ } else if (verbose) {
39
+ console.log(`Skipping file due to patterns: ${fullPath}`);
40
+ }
41
+ }
42
+ } catch (error) {
43
+ if (verbose) console.warn(`Error accessing file ${fullPath}: ${error instanceof Error ? error.message : String(error)}`);
44
+ }
24
45
  }
46
+ } catch (error) {
47
+ if (verbose) console.error(`Error reading directory ${dirPath}: ${error instanceof Error ? error.message : String(error)}`);
25
48
  }
26
49
  return arrayOfFiles;
27
50
  }
51
+ function minimatch(filePath, pattern) {
52
+ try {
53
+ const regExpPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/{{GLOBSTAR}}/g, ".*");
54
+ const regex = new RegExp(`^${regExpPattern}$`, "i");
55
+ const result = regex.test(filePath);
56
+ return result;
57
+ } catch (error) {
58
+ console.warn(`Invalid pattern: ${pattern}`);
59
+ return false;
60
+ }
61
+ }
62
+ function chunkArray(array, chunkSize) {
63
+ const result = [];
64
+ for (let i = 0; i < array.length; i += chunkSize) {
65
+ result.push(array.slice(i, i + chunkSize));
66
+ }
67
+ return result;
68
+ }
69
+ async function processFilesInBatches(items, batchSize, processor, onProgress) {
70
+ const results = [];
71
+ const batches = chunkArray(items, batchSize);
72
+ let completed = 0;
73
+ const total = items.length;
74
+ for (const batch of batches) {
75
+ const batchResults = await Promise.all(
76
+ batch.map(async (item) => {
77
+ try {
78
+ const result = await processor(item);
79
+ completed++;
80
+ if (onProgress) {
81
+ onProgress(completed, total);
82
+ }
83
+ return result;
84
+ } catch (error) {
85
+ console.warn(`Error processing item: ${error instanceof Error ? error.message : String(error)}`);
86
+ completed++;
87
+ if (onProgress) {
88
+ onProgress(completed, total);
89
+ }
90
+ return [];
91
+ }
92
+ })
93
+ );
94
+ results.push(...batchResults);
95
+ }
96
+ return results;
97
+ }
28
98
  function extractImportsFromFile(filePath, targetLib) {
29
- const code = fs.readFileSync(filePath, "utf-8");
30
- let ast;
31
99
  try {
32
- ast = parser.parse(code, {
33
- sourceType: "module",
34
- plugins: ["typescript", "jsx"]
100
+ const code = fs.readFileSync(filePath, "utf-8");
101
+ let ast;
102
+ try {
103
+ ast = parser.parse(code, {
104
+ sourceType: "module",
105
+ plugins: ["typescript", "jsx"],
106
+ errorRecovery: true
107
+ });
108
+ } catch (err) {
109
+ try {
110
+ ast = parser.parse(code, {
111
+ sourceType: "module",
112
+ plugins: ["typescript", "jsx", "decorators-legacy", "classProperties"],
113
+ errorRecovery: true
114
+ });
115
+ } catch (secondErr) {
116
+ console.warn(`Skipping ${filePath}: Failed to parse: ${secondErr}`);
117
+ return [];
118
+ }
119
+ }
120
+ const matches = [];
121
+ const lines = code.split("\n");
122
+ traverse(ast, {
123
+ ImportDeclaration(path3) {
124
+ const node = path3.node;
125
+ if (node.source.value === targetLib || // Handle subpath imports like 'library/subpath'
126
+ node.source.value.startsWith(`${targetLib}/`)) {
127
+ const lineNumber = node.loc?.start.line;
128
+ for (const specifier of node.specifiers) {
129
+ let importedName;
130
+ if (specifier.type === "ImportDefaultSpecifier") {
131
+ importedName = "default";
132
+ } else if (specifier.type === "ImportNamespaceSpecifier") {
133
+ importedName = "*";
134
+ } else if ("imported" in specifier && specifier.imported) {
135
+ importedName = specifier.imported.type === "Identifier" ? specifier.imported.name : String(specifier.imported.value);
136
+ } else {
137
+ importedName = "unknown";
138
+ }
139
+ const localName = specifier.local.name;
140
+ matches.push({
141
+ importedName,
142
+ localName,
143
+ file: filePath,
144
+ line: lineNumber
145
+ });
146
+ }
147
+ }
148
+ }
35
149
  });
36
- } catch (err) {
37
- console.warn(`Skipping ${filePath}: Failed to parse`);
150
+ return matches;
151
+ } catch (error) {
152
+ console.warn(`Error reading file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
38
153
  return [];
39
154
  }
40
- const matches = [];
41
- traverse(ast, {
42
- ImportDeclaration(path2) {
43
- const node = path2.node;
44
- if (node.source.value === targetLib) {
45
- for (const specifier of node.specifiers) {
46
- const importedName = "imported" in specifier && specifier.imported ? specifier.imported.name : "default";
47
- const localName = specifier.local.name;
48
- matches.push({
49
- importedName,
50
- localName,
51
- file: filePath
52
- });
155
+ }
156
+ async function analyzeImports(options2) {
157
+ const { dir, lib, include, exclude, verbose = false } = options2;
158
+ const concurrency = options2.concurrency || Math.max(1, Math.min(4, os.cpus().length - 1));
159
+ if (!fs.existsSync(dir)) {
160
+ throw new Error(`Directory '${dir}' does not exist`);
161
+ }
162
+ if (!fs.statSync(dir).isDirectory()) {
163
+ throw new Error(`'${dir}' is not a directory`);
164
+ }
165
+ const allFiles = getAllFiles(dir, [], include, exclude, verbose);
166
+ if (verbose) console.log(`Found ${allFiles.length} files to process`);
167
+ if (allFiles.length === 0) {
168
+ return {
169
+ summary: {
170
+ library: lib,
171
+ componentsFound: 0,
172
+ totalImports: 0,
173
+ filesScanned: 0
174
+ },
175
+ components: {}
176
+ };
177
+ }
178
+ const batchSize = Math.max(1, Math.ceil(allFiles.length / concurrency));
179
+ if (verbose) console.log(`Processing ${allFiles.length} files with ${concurrency} concurrent processes`);
180
+ let processedCount = 0;
181
+ const results = await processFilesInBatches(
182
+ allFiles,
183
+ batchSize,
184
+ (file) => extractImportsFromFile(file, lib),
185
+ (completed, total) => {
186
+ if (verbose) {
187
+ const percentComplete = Math.floor(completed / total * 100);
188
+ if (percentComplete % 10 === 0 && processedCount !== percentComplete) {
189
+ processedCount = percentComplete;
190
+ console.log(`Progress: ${percentComplete}% (${completed}/${total} files)`);
53
191
  }
54
192
  }
55
193
  }
56
- });
57
- return matches;
58
- }
59
- var allFiles = getAllFiles(dir);
60
- var componentMap = {};
61
- for (const file of allFiles) {
62
- const imports = extractImportsFromFile(file, lib);
63
- for (const { importedName, file: filePath } of imports) {
64
- if (!componentMap[importedName]) {
65
- componentMap[importedName] = /* @__PURE__ */ new Set();
194
+ );
195
+ const componentMap = {};
196
+ for (const imports of results) {
197
+ if (!imports) {
198
+ continue;
66
199
  }
67
- componentMap[importedName].add(filePath);
200
+ for (const { importedName, file: filePath } of imports) {
201
+ if (!componentMap[importedName]) {
202
+ componentMap[importedName] = /* @__PURE__ */ new Set();
203
+ }
204
+ componentMap[importedName].add(filePath);
205
+ }
206
+ }
207
+ const output = {};
208
+ for (const [component, files] of Object.entries(componentMap)) {
209
+ output[component] = [...files];
210
+ }
211
+ const totalImports = Object.values(output).reduce((sum, files) => sum + files.length, 0);
212
+ const totalComponents = Object.keys(output).length;
213
+ if (verbose) {
214
+ console.log(`Analysis complete - Found ${totalComponents} components with ${totalImports} total imports across ${allFiles.length} files`);
68
215
  }
216
+ return {
217
+ summary: {
218
+ library: lib,
219
+ componentsFound: totalComponents,
220
+ totalImports,
221
+ filesScanned: allFiles.length
222
+ },
223
+ components: output
224
+ };
69
225
  }
70
- var output = {};
71
- for (const [component, files] of Object.entries(componentMap)) {
72
- output[component] = [...files];
226
+
227
+ // src/index.ts
228
+ var __filename = fileURLToPath(import.meta.url);
229
+ var __dirname = dirname(__filename);
230
+ var packageJson;
231
+ try {
232
+ const packagePath = path2.resolve(__dirname, "../package.json");
233
+ const packageData = fs2.readFileSync(packagePath, "utf8");
234
+ packageJson = JSON.parse(packageData);
235
+ } catch (error) {
236
+ packageJson = { version: "0.0.0", name: "importy" };
237
+ console.warn("Could not load package.json, using default version");
238
+ }
239
+ program.version(packageJson.version).description("Analyze JavaScript/TypeScript imports from a specific library").requiredOption("-d, --dir <directory>", "Directory to scan").requiredOption("-l, --lib <library>", "Library name to match").option(
240
+ "-o, --output <file>",
241
+ "Output results to a JSON file instead of stdout"
242
+ ).option("-v, --verbose", "Enable verbose logging").option(
243
+ "-i, --include <pattern>",
244
+ "Only include files matching pattern (glob)"
245
+ ).option("-e, --exclude <pattern>", "Exclude files matching pattern (glob)").option(
246
+ "-c, --concurrency <number>",
247
+ "Number of worker threads (defaults to CPU count - 1)"
248
+ ).parse(process.argv);
249
+ var options = program.opts();
250
+ async function main() {
251
+ try {
252
+ const result = await analyzeImports({
253
+ dir: options.dir,
254
+ lib: options.lib,
255
+ include: options.include,
256
+ exclude: options.exclude,
257
+ verbose: options.verbose || false,
258
+ concurrency: options.concurrency ? parseInt(options.concurrency, 10) : void 0
259
+ });
260
+ if (options.output) {
261
+ try {
262
+ fs2.writeFileSync(options.output, JSON.stringify(result, null, 2));
263
+ console.log(`Results written to ${options.output}`);
264
+ } catch (error) {
265
+ console.error(
266
+ `Error writing to output file: ${error instanceof Error ? error.message : String(error)}`
267
+ );
268
+ console.log(JSON.stringify(result, null, 2));
269
+ }
270
+ } else {
271
+ console.log(JSON.stringify(result, null, 2));
272
+ }
273
+ if (Object.keys(result.components).length === 0) {
274
+ console.warn(
275
+ `No imports from '${options.lib}' were found in the specified directory.`
276
+ );
277
+ process.exit(0);
278
+ }
279
+ return result;
280
+ } catch (error) {
281
+ console.error(
282
+ `Error during processing: ${error instanceof Error ? error.message : String(error)}`
283
+ );
284
+ process.exit(1);
285
+ }
73
286
  }
74
- console.log(JSON.stringify(output, null, 2));
287
+ main().catch((error) => {
288
+ console.error(
289
+ `Unhandled error: ${error instanceof Error ? error.message : String(error)}`
290
+ );
291
+ process.exit(1);
292
+ });
package/package.json CHANGED
@@ -1,23 +1,78 @@
1
1
  {
2
2
  "name": "importy",
3
- "version": "0.0.6",
4
- "description": "",
3
+ "version": "0.0.8",
4
+ "description": "A CLI tool for analyzing JavaScript/TypeScript imports from libraries",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/tvshevchuk/Importy"
8
+ },
5
9
  "type": "module",
6
10
  "main": "./dist/index.js",
7
11
  "bin": {
8
12
  "inspect-imports": "./dist/index.js"
9
13
  },
10
- "keywords": [],
14
+ "keywords": [
15
+ "cli",
16
+ "import",
17
+ "imports",
18
+ "analyze",
19
+ "analysis",
20
+ "javascript",
21
+ "typescript",
22
+ "jsx",
23
+ "tsx",
24
+ "library",
25
+ "dependencies",
26
+ "dependency",
27
+ "scanner",
28
+ "ast",
29
+ "babel",
30
+ "codebase",
31
+ "tool",
32
+ "static-analysis",
33
+ "code-analysis",
34
+ "module",
35
+ "modules",
36
+ "es6",
37
+ "esm",
38
+ "commonjs",
39
+ "react",
40
+ "vue",
41
+ "angular",
42
+ "component",
43
+ "components",
44
+ "usage",
45
+ "report",
46
+ "audit",
47
+ "bundle",
48
+ "bundler",
49
+ "webpack",
50
+ "vite",
51
+ "rollup",
52
+ "tree-shaking",
53
+ "dead-code",
54
+ "refactor",
55
+ "migration"
56
+ ],
11
57
  "author": "Taras Shevchuk",
12
58
  "license": "MIT",
13
59
  "publishConfig": {
14
60
  "registry": "https://registry.npmjs.org/"
15
61
  },
62
+ "files": [
63
+ "dist"
64
+ ],
16
65
  "devDependencies": {
17
66
  "@babel/types": "^7.27.3",
67
+ "@types/babel__traverse": "^7.20.7",
18
68
  "@types/node": "^22.15.29",
69
+ "@typescript-eslint/eslint-plugin": "^7.4.0",
70
+ "@typescript-eslint/parser": "^7.4.0",
71
+ "eslint": "^8.57.0",
72
+ "ts-node": "^10.9.2",
19
73
  "tsup": "^8.5.0",
20
- "typescript": "^5.8.3"
74
+ "typescript": "^5.8.3",
75
+ "vitest": "^1.4.0"
21
76
  },
22
77
  "dependencies": {
23
78
  "@babel/parser": "^7.27.5",
@@ -25,6 +80,11 @@
25
80
  "commander": "^14.0.0"
26
81
  },
27
82
  "scripts": {
28
- "build": "tsup"
83
+ "build": "tsup src/index.ts",
84
+ "start": "node dist/index.js",
85
+ "dev": "ts-node src/index.ts",
86
+ "test": "vitest run",
87
+ "test:watch": "vitest",
88
+ "lint": "eslint src/**/*.ts"
29
89
  }
30
90
  }
@@ -1,36 +0,0 @@
1
- name: Publish to npm (pnpm)
2
-
3
- on:
4
- push:
5
- tags:
6
- - "v*" # triggers on tags like v1.0.0
7
-
8
- jobs:
9
- publish:
10
- name: Publish package
11
- runs-on: ubuntu-latest
12
- environment:
13
- name: releases
14
-
15
- steps:
16
- - uses: actions/checkout@v3
17
-
18
- - name: Setup Node.js
19
- uses: actions/setup-node@v4
20
- with:
21
- node-version: "20"
22
- registry-url: "https://registry.npmjs.org/"
23
-
24
- - name: Install pnpm
25
- run: npm install -g pnpm
26
-
27
- - name: Install dependencies
28
- run: pnpm install
29
-
30
- - name: Build
31
- run: pnpm build
32
-
33
- - name: Publish to npm
34
- run: pnpm publish --no-git-checks
35
- env:
36
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/src/index.ts DELETED
@@ -1,105 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { program } from 'commander';
4
- import * as parser from '@babel/parser';
5
-
6
- import _traverse from '@babel/traverse';
7
- const traverse = (_traverse as any).default ?? _traverse;
8
- import type { ImportDeclaration } from '@babel/types';
9
-
10
- // Define CLI options
11
- program
12
- .requiredOption('-d, --dir <directory>', 'Directory to scan')
13
- .requiredOption('-l, --lib <library>', 'Library name to match')
14
- .parse(process.argv);
15
-
16
- const { dir, lib } = program.opts<{
17
- dir: string;
18
- lib: string;
19
- }>();
20
-
21
- // Helpers
22
- function isJavaScriptFile(file: string): boolean {
23
- return /\.(js|ts|jsx|tsx)$/.test(file);
24
- }
25
-
26
- function getAllFiles(dirPath: string, arrayOfFiles: string[] = []): string[] {
27
- const files = fs.readdirSync(dirPath);
28
- for (const file of files) {
29
- const fullPath = path.join(dirPath, file);
30
- const stat = fs.statSync(fullPath);
31
- if (stat.isDirectory()) {
32
- getAllFiles(fullPath, arrayOfFiles);
33
- } else if (isJavaScriptFile(fullPath)) {
34
- arrayOfFiles.push(fullPath);
35
- }
36
- }
37
- return arrayOfFiles;
38
- }
39
-
40
- type ImportMatch = {
41
- importedName: string;
42
- localName: string;
43
- file: string;
44
- };
45
-
46
- function extractImportsFromFile(filePath: string, targetLib: string): ImportMatch[] {
47
- const code = fs.readFileSync(filePath, 'utf-8');
48
- let ast;
49
- try {
50
- ast = parser.parse(code, {
51
- sourceType: 'module',
52
- plugins: ['typescript', 'jsx'],
53
- });
54
- } catch (err) {
55
- console.warn(`Skipping ${filePath}: Failed to parse`);
56
- return [];
57
- }
58
-
59
- const matches: ImportMatch[] = [];
60
-
61
- traverse(ast, {
62
- ImportDeclaration(path: any) {
63
- const node = path.node as ImportDeclaration;
64
- if (node.source.value === targetLib) {
65
- for (const specifier of node.specifiers) {
66
- const importedName =
67
- 'imported' in specifier && specifier.imported
68
- ? specifier.imported.name as any
69
- : 'default';
70
- const localName = specifier.local.name;
71
-
72
- matches.push({
73
- importedName,
74
- localName,
75
- file: filePath,
76
- });
77
- }
78
- }
79
- },
80
- });
81
-
82
- return matches;
83
- }
84
-
85
- // Process all files and build result
86
- const allFiles = getAllFiles(dir);
87
- const componentMap: Record<string, Set<string>> = {};
88
-
89
- for (const file of allFiles) {
90
- const imports = extractImportsFromFile(file, lib);
91
- for (const { importedName, file: filePath } of imports) {
92
- if (!componentMap[importedName]) {
93
- componentMap[importedName] = new Set();
94
- }
95
- componentMap[importedName].add(filePath);
96
- }
97
- }
98
-
99
- // Final output
100
- const output: Record<string, string[]> = {};
101
- for (const [component, files] of Object.entries(componentMap)) {
102
- output[component] = [...files];
103
- }
104
-
105
- console.log(JSON.stringify(output, null, 2));
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "moduleResolution": "Node",
6
- "esModuleInterop": true,
7
- "forceConsistentCasingInFileNames": true,
8
- "verbatimModuleSyntax": true,
9
- "strict": true,
10
- "skipLibCheck": true,
11
- "outDir": "dist"
12
- },
13
- "include": ["src"]
14
- }
package/tsup.config.ts DELETED
@@ -1,12 +0,0 @@
1
- // tsup.config.ts
2
- import { defineConfig } from "tsup";
3
-
4
- export default defineConfig({
5
- entry: ["src/index.ts"],
6
- format: ["esm"],
7
- outDir: "dist",
8
- banner: {
9
- js: "#!/usr/bin/env node",
10
- },
11
- target: "node18",
12
- });