@reliverse/dler 2.0.0 → 2.0.1

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 (135) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +3 -0
  3. package/dist/cmds/build/cmd.d.ts +2 -0
  4. package/dist/cmds/build/cmd.js +564 -0
  5. package/dist/cmds/clean/cmd.d.ts +2 -0
  6. package/dist/cmds/clean/cmd.js +146 -0
  7. package/dist/cmds/clean/impl.d.ts +2 -0
  8. package/dist/cmds/clean/impl.js +627 -0
  9. package/dist/cmds/clean/presets.d.ts +10 -0
  10. package/dist/cmds/clean/presets.js +112 -0
  11. package/dist/cmds/clean/types.d.ts +62 -0
  12. package/dist/cmds/clean/types.js +0 -0
  13. package/dist/cmds/init/cmd.d.ts +3 -0
  14. package/dist/cmds/init/cmd.js +56 -0
  15. package/dist/cmds/init/impl/config.d.ts +45 -0
  16. package/dist/cmds/init/impl/config.js +99 -0
  17. package/dist/cmds/init/impl/generators.d.ts +6 -0
  18. package/dist/cmds/init/impl/generators.js +178 -0
  19. package/dist/cmds/init/impl/prompts.d.ts +2 -0
  20. package/dist/cmds/init/impl/prompts.js +98 -0
  21. package/dist/cmds/init/impl/types.d.ts +22 -0
  22. package/dist/cmds/init/impl/types.js +0 -0
  23. package/dist/cmds/init/impl/utils.d.ts +4 -0
  24. package/dist/cmds/init/impl/utils.js +11 -0
  25. package/dist/cmds/init/impl/validators.d.ts +4 -0
  26. package/dist/cmds/init/impl/validators.js +42 -0
  27. package/dist/cmds/integrate/cmd.d.ts +3 -0
  28. package/dist/cmds/integrate/cmd.js +70 -0
  29. package/dist/cmds/integrate/impl.d.ts +7 -0
  30. package/dist/cmds/integrate/impl.js +127 -0
  31. package/dist/cmds/integrate/integrations/base.d.ts +13 -0
  32. package/dist/cmds/integrate/integrations/base.js +41 -0
  33. package/dist/cmds/integrate/integrations/nextjs.d.ts +16 -0
  34. package/dist/cmds/integrate/integrations/nextjs.js +167 -0
  35. package/dist/cmds/integrate/integrations/registry.d.ts +7 -0
  36. package/dist/cmds/integrate/integrations/registry.js +31 -0
  37. package/dist/cmds/integrate/integrations/ultracite.d.ts +11 -0
  38. package/dist/cmds/integrate/integrations/ultracite.js +40 -0
  39. package/dist/cmds/integrate/types.d.ts +39 -0
  40. package/dist/cmds/integrate/types.js +0 -0
  41. package/dist/cmds/integrate/utils/biome.d.ts +4 -0
  42. package/dist/cmds/integrate/utils/biome.js +140 -0
  43. package/dist/cmds/integrate/utils/context.d.ts +3 -0
  44. package/dist/cmds/integrate/utils/context.js +111 -0
  45. package/dist/cmds/integrate/utils/temp.d.ts +3 -0
  46. package/dist/cmds/integrate/utils/temp.js +36 -0
  47. package/dist/cmds/perf/analysis/bundle.d.ts +20 -0
  48. package/dist/cmds/perf/analysis/bundle.js +225 -0
  49. package/dist/cmds/perf/analysis/filesystem.d.ts +27 -0
  50. package/dist/cmds/perf/analysis/filesystem.js +246 -0
  51. package/dist/cmds/perf/analysis/monorepo.d.ts +29 -0
  52. package/dist/cmds/perf/analysis/monorepo.js +307 -0
  53. package/dist/cmds/perf/benchmarks/command.d.ts +21 -0
  54. package/dist/cmds/perf/benchmarks/command.js +162 -0
  55. package/dist/cmds/perf/benchmarks/memory.d.ts +41 -0
  56. package/dist/cmds/perf/benchmarks/memory.js +169 -0
  57. package/dist/cmds/perf/benchmarks/runner.d.ts +22 -0
  58. package/dist/cmds/perf/benchmarks/runner.js +157 -0
  59. package/dist/cmds/perf/cmd.d.ts +2 -0
  60. package/dist/cmds/perf/cmd.js +238 -0
  61. package/dist/cmds/perf/impl.d.ts +24 -0
  62. package/dist/cmds/perf/impl.js +304 -0
  63. package/dist/cmds/perf/reporters/console.d.ts +12 -0
  64. package/dist/cmds/perf/reporters/console.js +257 -0
  65. package/dist/cmds/perf/reporters/html.d.ts +27 -0
  66. package/dist/cmds/perf/reporters/html.js +881 -0
  67. package/dist/cmds/perf/reporters/json.d.ts +9 -0
  68. package/dist/cmds/perf/reporters/json.js +32 -0
  69. package/dist/cmds/perf/types.d.ts +184 -0
  70. package/dist/cmds/perf/types.js +0 -0
  71. package/dist/cmds/perf/utils/cache.d.ts +23 -0
  72. package/dist/cmds/perf/utils/cache.js +171 -0
  73. package/dist/cmds/perf/utils/formatter.d.ts +17 -0
  74. package/dist/cmds/perf/utils/formatter.js +134 -0
  75. package/dist/cmds/perf/utils/stats.d.ts +15 -0
  76. package/dist/cmds/perf/utils/stats.js +101 -0
  77. package/dist/cmds/publish/cmd.d.ts +3 -0
  78. package/dist/cmds/publish/cmd.js +189 -0
  79. package/dist/cmds/shell/cmd.d.ts +3 -0
  80. package/dist/cmds/shell/cmd.js +50 -0
  81. package/dist/cmds/tsc/cache.d.ts +27 -0
  82. package/dist/cmds/tsc/cache.js +160 -0
  83. package/dist/cmds/tsc/cmd.d.ts +2 -0
  84. package/dist/cmds/tsc/cmd.js +111 -0
  85. package/dist/cmds/tsc/impl.d.ts +41 -0
  86. package/dist/cmds/tsc/impl.js +572 -0
  87. package/dist/cmds/tsc/types.d.ts +57 -0
  88. package/dist/cmds/tsc/types.js +0 -0
  89. package/package.json +4 -11
  90. package/src/cli.ts +8 -0
  91. package/src/cmds/build/cmd.ts +582 -0
  92. package/src/cmds/clean/cmd.ts +166 -0
  93. package/src/cmds/clean/impl.ts +900 -0
  94. package/src/cmds/clean/presets.ts +158 -0
  95. package/src/cmds/clean/types.ts +71 -0
  96. package/src/cmds/init/cmd.ts +68 -0
  97. package/src/cmds/init/impl/config.ts +105 -0
  98. package/src/cmds/init/impl/generators.ts +220 -0
  99. package/src/cmds/init/impl/prompts.ts +137 -0
  100. package/src/cmds/init/impl/types.ts +25 -0
  101. package/src/cmds/init/impl/utils.ts +17 -0
  102. package/src/cmds/init/impl/validators.ts +55 -0
  103. package/src/cmds/integrate/cmd.ts +82 -0
  104. package/src/cmds/integrate/impl.ts +204 -0
  105. package/src/cmds/integrate/integrations/base.ts +69 -0
  106. package/src/cmds/integrate/integrations/nextjs.ts +227 -0
  107. package/src/cmds/integrate/integrations/registry.ts +45 -0
  108. package/src/cmds/integrate/integrations/ultracite.ts +53 -0
  109. package/src/cmds/integrate/types.ts +48 -0
  110. package/src/cmds/integrate/utils/biome.ts +173 -0
  111. package/src/cmds/integrate/utils/context.ts +148 -0
  112. package/src/cmds/integrate/utils/temp.ts +47 -0
  113. package/src/cmds/perf/analysis/bundle.ts +311 -0
  114. package/src/cmds/perf/analysis/filesystem.ts +324 -0
  115. package/src/cmds/perf/analysis/monorepo.ts +439 -0
  116. package/src/cmds/perf/benchmarks/command.ts +230 -0
  117. package/src/cmds/perf/benchmarks/memory.ts +249 -0
  118. package/src/cmds/perf/benchmarks/runner.ts +220 -0
  119. package/src/cmds/perf/cmd.ts +285 -0
  120. package/src/cmds/perf/impl.ts +411 -0
  121. package/src/cmds/perf/reporters/console.ts +331 -0
  122. package/src/cmds/perf/reporters/html.ts +984 -0
  123. package/src/cmds/perf/reporters/json.ts +42 -0
  124. package/src/cmds/perf/types.ts +220 -0
  125. package/src/cmds/perf/utils/cache.ts +234 -0
  126. package/src/cmds/perf/utils/formatter.ts +190 -0
  127. package/src/cmds/perf/utils/stats.ts +153 -0
  128. package/src/cmds/publish/cmd.ts +215 -0
  129. package/src/cmds/shell/cmd.ts +61 -0
  130. package/src/cmds/tsc/cache.ts +237 -0
  131. package/src/cmds/tsc/cmd.ts +139 -0
  132. package/src/cmds/tsc/impl.ts +855 -0
  133. package/src/cmds/tsc/types.ts +66 -0
  134. package/tsconfig.json +9 -0
  135. package/cli.js +0 -1316
@@ -0,0 +1,47 @@
1
+ // apps/dler/src/cmds/integrate/utils/temp.ts
2
+
3
+ import { existsSync, rmSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { logger } from "@reliverse/dler-logger";
7
+ import { ensureDir } from "../../init/impl/utils";
8
+ import type { TempDirectory } from "../types";
9
+
10
+ export const createTempDirectory = async (): Promise<TempDirectory> => {
11
+ const timestamp = Date.now();
12
+ const tempPath = join(
13
+ homedir(),
14
+ ".reliverse",
15
+ "dler",
16
+ "temp",
17
+ "integrate",
18
+ timestamp.toString(),
19
+ );
20
+
21
+ await ensureDir(tempPath);
22
+
23
+ logger.debug(`📁 Created temp directory: ${tempPath}`);
24
+
25
+ return {
26
+ path: tempPath,
27
+ cleanup: async () => {
28
+ try {
29
+ if (existsSync(tempPath)) {
30
+ rmSync(tempPath, { recursive: true, force: true });
31
+ logger.debug(`🧹 Cleaned up temp directory: ${tempPath}`);
32
+ }
33
+ } catch (error) {
34
+ logger.warn(`⚠️ Failed to clean up temp directory: ${error}`);
35
+ }
36
+ },
37
+ };
38
+ };
39
+
40
+ export const createIntegrationTempDir = async (
41
+ tempDir: TempDirectory,
42
+ integrationName: string,
43
+ ): Promise<string> => {
44
+ const integrationPath = join(tempDir.path, `${integrationName}-temp`);
45
+ await ensureDir(integrationPath);
46
+ return integrationPath;
47
+ };
@@ -0,0 +1,311 @@
1
+ // apps/dler/src/cmds/perf/analysis/bundle.ts
2
+
3
+ import { existsSync, readFileSync, statSync } from "node:fs";
4
+ import { extname, join, resolve } from "node:path";
5
+ import { logger } from "@reliverse/dler-logger";
6
+ import type {
7
+ BundleAnalysisResult,
8
+ DuplicateInfo,
9
+ FileSize,
10
+ ModuleInfo,
11
+ } from "../types";
12
+
13
+ export interface BundleAnalysisOptions {
14
+ target: string;
15
+ verbose?: boolean;
16
+ includeSourceMaps?: boolean;
17
+ analyzeDependencies?: boolean;
18
+ }
19
+
20
+ export class BundleAnalyzer {
21
+ private options: BundleAnalysisOptions;
22
+
23
+ constructor(options: BundleAnalysisOptions) {
24
+ this.options = options;
25
+ }
26
+
27
+ async analyze(): Promise<BundleAnalysisResult> {
28
+ const startTime = Date.now();
29
+ const { target, verbose } = this.options;
30
+
31
+ if (verbose) {
32
+ logger.info(`🔍 Analyzing bundle: ${target}`);
33
+ }
34
+
35
+ // Check if target is a file or directory
36
+ const targetPath = resolve(target);
37
+ if (!existsSync(targetPath)) {
38
+ throw new Error(`Target not found: ${target}`);
39
+ }
40
+
41
+ const stat = statSync(targetPath);
42
+ let files: string[] = [];
43
+
44
+ if (stat.isDirectory()) {
45
+ files = await this.findBundleFiles(targetPath);
46
+ } else {
47
+ files = [targetPath];
48
+ }
49
+
50
+ if (files.length === 0) {
51
+ throw new Error(`No bundle files found in: ${target}`);
52
+ }
53
+
54
+ if (verbose) {
55
+ logger.info(` Found ${files.length} bundle files`);
56
+ }
57
+
58
+ // Analyze each file
59
+ const fileSizes: FileSize[] = [];
60
+ const modules: ModuleInfo[] = [];
61
+ const duplicates: DuplicateInfo[] = [];
62
+
63
+ for (const file of files) {
64
+ const fileSize = await this.analyzeFile(file);
65
+ fileSizes.push(fileSize);
66
+
67
+ if (this.options.analyzeDependencies) {
68
+ const fileModules = await this.extractModules(file);
69
+ modules.push(...fileModules);
70
+ }
71
+ }
72
+
73
+ // Sort by size
74
+ fileSizes.sort((a, b) => b.size - a.size);
75
+
76
+ // Find duplicates
77
+ if (this.options.analyzeDependencies) {
78
+ const duplicateMap = this.findDuplicates(modules);
79
+ duplicates.push(...duplicateMap);
80
+ }
81
+
82
+ // Calculate totals
83
+ const totalSize = fileSizes.reduce((sum, file) => sum + file.size, 0);
84
+ const fileCount = fileSizes.length;
85
+
86
+ // Calculate compression potential
87
+ const compressionPotential = this.calculateCompressionPotential(files);
88
+
89
+ // Update percentages
90
+ fileSizes.forEach((file) => {
91
+ file.percentage = (file.size / totalSize) * 100;
92
+ });
93
+
94
+ const analysisTime = Date.now() - startTime;
95
+
96
+ if (verbose) {
97
+ logger.info(` Analysis completed in ${analysisTime}ms`);
98
+ }
99
+
100
+ return {
101
+ target,
102
+ totalSize,
103
+ fileCount,
104
+ largestFiles: fileSizes.slice(0, 10), // Top 10
105
+ modules: modules.slice(0, 20), // Top 20 modules
106
+ duplicates: duplicates.slice(0, 10), // Top 10 duplicates
107
+ compressionPotential,
108
+ analysisTime,
109
+ };
110
+ }
111
+
112
+ private async findBundleFiles(dir: string): Promise<string[]> {
113
+ const bundleExtensions = [".js", ".mjs", ".cjs", ".ts", ".jsx", ".tsx"];
114
+ const files: string[] = [];
115
+
116
+ try {
117
+ const glob = new Bun.Glob("**/*");
118
+ const matches = glob.scanSync({ cwd: dir, onlyFiles: true });
119
+
120
+ for (const match of matches) {
121
+ const fullPath = join(dir, match);
122
+ const ext = extname(match);
123
+
124
+ if (bundleExtensions.includes(ext)) {
125
+ files.push(fullPath);
126
+ }
127
+ }
128
+ } catch (error) {
129
+ logger.warn(`Error scanning directory ${dir}:`, error);
130
+ }
131
+
132
+ return files;
133
+ }
134
+
135
+ private async analyzeFile(filePath: string): Promise<FileSize> {
136
+ const stat = statSync(filePath);
137
+ const ext = extname(filePath);
138
+
139
+ return {
140
+ path: filePath,
141
+ size: stat.size,
142
+ percentage: 0, // Will be calculated later
143
+ type: this.getFileType(ext),
144
+ };
145
+ }
146
+
147
+ private getFileType(extension: string): string {
148
+ const typeMap: Record<string, string> = {
149
+ ".js": "JavaScript",
150
+ ".mjs": "ES Module",
151
+ ".cjs": "CommonJS",
152
+ ".ts": "TypeScript",
153
+ ".jsx": "React JSX",
154
+ ".tsx": "React TSX",
155
+ ".json": "JSON",
156
+ ".css": "CSS",
157
+ ".scss": "SCSS",
158
+ ".sass": "Sass",
159
+ ".less": "Less",
160
+ ".html": "HTML",
161
+ ".svg": "SVG",
162
+ ".png": "PNG",
163
+ ".jpg": "JPEG",
164
+ ".jpeg": "JPEG",
165
+ ".gif": "GIF",
166
+ ".webp": "WebP",
167
+ ".woff": "WOFF",
168
+ ".woff2": "WOFF2",
169
+ ".ttf": "TrueType",
170
+ ".eot": "EOT",
171
+ ".map": "Source Map",
172
+ };
173
+
174
+ return typeMap[extension] ?? "Unknown";
175
+ }
176
+
177
+ private async extractModules(filePath: string): Promise<ModuleInfo[]> {
178
+ const modules: ModuleInfo[] = [];
179
+
180
+ try {
181
+ const content = readFileSync(filePath, "utf-8");
182
+
183
+ // Simple regex-based module extraction
184
+ // This is a basic implementation - in practice, you'd want to use a proper AST parser
185
+ const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
186
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
187
+ const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
188
+
189
+ const dependencies = new Set<string>();
190
+
191
+ // Extract ES6 imports
192
+ let match;
193
+ while ((match = importRegex.exec(content)) !== null) {
194
+ dependencies.add(match[1]!);
195
+ }
196
+
197
+ // Extract CommonJS requires
198
+ while ((match = requireRegex.exec(content)) !== null) {
199
+ dependencies.add(match[1]!);
200
+ }
201
+
202
+ // Extract dynamic imports
203
+ while ((match = dynamicImportRegex.exec(content)) !== null) {
204
+ dependencies.add(match[1]!);
205
+ }
206
+
207
+ // Convert to ModuleInfo
208
+ for (const dep of dependencies) {
209
+ const isExternal = !dep.startsWith(".") && !dep.startsWith("/");
210
+
211
+ modules.push({
212
+ name: dep,
213
+ size: 0, // Would need to resolve actual size
214
+ percentage: 0,
215
+ dependencies: [],
216
+ isExternal,
217
+ });
218
+ }
219
+ } catch (error) {
220
+ logger.warn(`Error extracting modules from ${filePath}:`, error);
221
+ }
222
+
223
+ return modules;
224
+ }
225
+
226
+ private findDuplicates(modules: ModuleInfo[]): DuplicateInfo[] {
227
+ const moduleMap = new Map<
228
+ string,
229
+ { count: number; totalSize: number; locations: string[] }
230
+ >();
231
+
232
+ for (const module of modules) {
233
+ const key = module.name;
234
+ const existing = moduleMap.get(key);
235
+
236
+ if (existing) {
237
+ existing.count++;
238
+ existing.totalSize += module.size;
239
+ existing.locations.push(module.name);
240
+ } else {
241
+ moduleMap.set(key, {
242
+ count: 1,
243
+ totalSize: module.size,
244
+ locations: [module.name],
245
+ });
246
+ }
247
+ }
248
+
249
+ const duplicates: DuplicateInfo[] = [];
250
+
251
+ for (const [name, info] of moduleMap) {
252
+ if (info.count > 1) {
253
+ duplicates.push({
254
+ name,
255
+ count: info.count,
256
+ totalSize: info.totalSize,
257
+ locations: info.locations,
258
+ });
259
+ }
260
+ }
261
+
262
+ return duplicates.sort((a, b) => b.totalSize - a.totalSize);
263
+ }
264
+
265
+ private calculateCompressionPotential(files: string[]): number {
266
+ // Simple heuristic based on file types
267
+ let totalSize = 0;
268
+ let compressibleSize = 0;
269
+
270
+ for (const file of files) {
271
+ const stat = statSync(file);
272
+ const ext = extname(file);
273
+
274
+ totalSize += stat.size;
275
+
276
+ // Files that typically compress well
277
+ const compressibleExtensions = [
278
+ ".js",
279
+ ".ts",
280
+ ".jsx",
281
+ ".tsx",
282
+ ".json",
283
+ ".css",
284
+ ".html",
285
+ ".svg",
286
+ ];
287
+ if (compressibleExtensions.includes(ext)) {
288
+ compressibleSize += stat.size;
289
+ }
290
+ }
291
+
292
+ if (totalSize === 0) return 0;
293
+
294
+ // Estimate 60-80% compression for text files
295
+ const estimatedCompression = compressibleSize * 0.7;
296
+ return (estimatedCompression / totalSize) * 100;
297
+ }
298
+ }
299
+
300
+ export const analyzeBundle = async (
301
+ options: BundleAnalysisOptions,
302
+ ): Promise<BundleAnalysisResult> => {
303
+ const analyzer = new BundleAnalyzer(options);
304
+ return analyzer.analyze();
305
+ };
306
+
307
+ export const createBundleAnalyzer = (
308
+ options: BundleAnalysisOptions,
309
+ ): BundleAnalyzer => {
310
+ return new BundleAnalyzer(options);
311
+ };
@@ -0,0 +1,324 @@
1
+ // apps/dler/src/cmds/perf/analysis/filesystem.ts
2
+
3
+ import { existsSync, statSync } from "node:fs";
4
+ import { extname, join, resolve } from "node:path";
5
+ import { logger } from "@reliverse/dler-logger";
6
+ import type {
7
+ DirectorySize,
8
+ FileSize,
9
+ FileSystemAnalysisResult,
10
+ FileTypeDistribution,
11
+ } from "../types";
12
+ import { formatBytes } from "../utils/formatter";
13
+
14
+ export interface FileSystemAnalysisOptions {
15
+ target: string;
16
+ verbose?: boolean;
17
+ maxDepth?: number;
18
+ includeHidden?: boolean;
19
+ excludePatterns?: string[];
20
+ }
21
+
22
+ export class FileSystemAnalyzer {
23
+ private options: FileSystemAnalysisOptions;
24
+ private fileCount = 0;
25
+ private totalSize = 0;
26
+ private directoryCount = 0;
27
+ private maxDepth = 0;
28
+ private files: FileSize[] = [];
29
+ private directories: DirectorySize[] = [];
30
+ private fileTypes = new Map<string, { count: number; totalSize: number }>();
31
+
32
+ constructor(options: FileSystemAnalysisOptions) {
33
+ this.options = options;
34
+ }
35
+
36
+ async analyze(): Promise<FileSystemAnalysisResult> {
37
+ const startTime = Date.now();
38
+ const { target, verbose } = this.options;
39
+
40
+ if (verbose) {
41
+ logger.info(`🔍 Analyzing filesystem: ${target}`);
42
+ }
43
+
44
+ // Check if target exists
45
+ const targetPath = resolve(target);
46
+ if (!existsSync(targetPath)) {
47
+ throw new Error(`Target not found: ${target}`);
48
+ }
49
+
50
+ const stat = statSync(targetPath);
51
+
52
+ if (stat.isFile()) {
53
+ await this.analyzeFile(targetPath, 0);
54
+ } else {
55
+ await this.analyzeDirectory(targetPath, 0);
56
+ }
57
+
58
+ // Sort results
59
+ this.files.sort((a, b) => b.size - a.size);
60
+ this.directories.sort((a, b) => b.size - a.size);
61
+
62
+ // Calculate file type distribution
63
+ const fileTypeDistribution: FileTypeDistribution[] = Array.from(
64
+ this.fileTypes.entries(),
65
+ )
66
+ .map(([extension, info]) => ({
67
+ extension,
68
+ count: info.count,
69
+ totalSize: info.totalSize,
70
+ percentage: (info.totalSize / this.totalSize) * 100,
71
+ }))
72
+ .sort((a, b) => b.totalSize - a.totalSize);
73
+
74
+ // Calculate compression potential
75
+ const compressionPotential = this.calculateCompressionPotential();
76
+
77
+ const analysisTime = Date.now() - startTime;
78
+
79
+ if (verbose) {
80
+ logger.info(` Analysis completed in ${analysisTime}ms`);
81
+ logger.info(
82
+ ` Files: ${this.fileCount}, Directories: ${this.directoryCount}, Size: ${formatBytes(this.totalSize)}`,
83
+ );
84
+ }
85
+
86
+ return {
87
+ target,
88
+ totalFiles: this.fileCount,
89
+ totalSize: this.totalSize,
90
+ directoryCount: this.directoryCount,
91
+ maxDepth: this.maxDepth,
92
+ largestFiles: this.files.slice(0, 20), // Top 20
93
+ largestDirectories: this.directories.slice(0, 20), // Top 20
94
+ fileTypes: fileTypeDistribution,
95
+ compressionPotential,
96
+ analysisTime,
97
+ };
98
+ }
99
+
100
+ private async analyzeFile(filePath: string, depth: number): Promise<void> {
101
+ try {
102
+ const stat = statSync(filePath);
103
+ const ext = extname(filePath);
104
+
105
+ // Check exclude patterns
106
+ if (this.shouldExclude(filePath)) {
107
+ return;
108
+ }
109
+
110
+ this.fileCount++;
111
+ this.totalSize += stat.size;
112
+ this.maxDepth = Math.max(this.maxDepth, depth);
113
+
114
+ // Add to files list
115
+ this.files.push({
116
+ path: filePath,
117
+ size: stat.size,
118
+ percentage: 0, // Will be calculated later
119
+ type: this.getFileType(ext),
120
+ });
121
+
122
+ // Update file type statistics
123
+ const extension = ext || "no-extension";
124
+ const existing = this.fileTypes.get(extension);
125
+ if (existing) {
126
+ existing.count++;
127
+ existing.totalSize += stat.size;
128
+ } else {
129
+ this.fileTypes.set(extension, {
130
+ count: 1,
131
+ totalSize: stat.size,
132
+ });
133
+ }
134
+ } catch (error) {
135
+ // Skip files we can't access
136
+ if (this.options.verbose) {
137
+ logger.warn(`Cannot access file ${filePath}:`, error);
138
+ }
139
+ }
140
+ }
141
+
142
+ private async analyzeDirectory(
143
+ dirPath: string,
144
+ depth: number,
145
+ ): Promise<void> {
146
+ try {
147
+ this.directoryCount++;
148
+ this.maxDepth = Math.max(this.maxDepth, depth);
149
+
150
+ // Check exclude patterns
151
+ if (this.shouldExclude(dirPath)) {
152
+ return;
153
+ }
154
+
155
+ let dirSize = 0;
156
+ let dirFileCount = 0;
157
+
158
+ // Scan directory contents
159
+ const glob = new Bun.Glob("**/*");
160
+ const matches = glob.scanSync({
161
+ cwd: dirPath,
162
+ onlyFiles: false,
163
+ dot: this.options.includeHidden ?? false,
164
+ });
165
+
166
+ for (const match of matches) {
167
+ const fullPath = join(dirPath, match);
168
+
169
+ try {
170
+ const stat = statSync(fullPath);
171
+
172
+ if (stat.isFile()) {
173
+ await this.analyzeFile(fullPath, depth + 1);
174
+ dirSize += stat.size;
175
+ dirFileCount++;
176
+ } else if (stat.isDirectory()) {
177
+ await this.analyzeDirectory(fullPath, depth + 1);
178
+ }
179
+ } catch (error) {
180
+ // Skip files/directories we can't access
181
+ if (this.options.verbose) {
182
+ logger.warn(`Cannot access ${fullPath}:`, error);
183
+ }
184
+ }
185
+ }
186
+
187
+ // Add directory to list
188
+ this.directories.push({
189
+ path: dirPath,
190
+ size: dirSize,
191
+ fileCount: dirFileCount,
192
+ depth,
193
+ });
194
+ } catch (error) {
195
+ // Skip directories we can't access
196
+ if (this.options.verbose) {
197
+ logger.warn(`Cannot access directory ${dirPath}:`, error);
198
+ }
199
+ }
200
+ }
201
+
202
+ private shouldExclude(path: string): boolean {
203
+ const { excludePatterns = [] } = this.options;
204
+
205
+ for (const pattern of excludePatterns) {
206
+ if (path.includes(pattern)) {
207
+ return true;
208
+ }
209
+ }
210
+
211
+ // Default exclusions
212
+ const defaultExclusions = [
213
+ "node_modules",
214
+ ".git",
215
+ ".next",
216
+ ".nuxt",
217
+ ".expo",
218
+ "dist",
219
+ "build",
220
+ "coverage",
221
+ ".cache",
222
+ ".turbo",
223
+ ];
224
+
225
+ for (const exclusion of defaultExclusions) {
226
+ if (path.includes(exclusion)) {
227
+ return true;
228
+ }
229
+ }
230
+
231
+ return false;
232
+ }
233
+
234
+ private getFileType(extension: string): string {
235
+ const typeMap: Record<string, string> = {
236
+ ".js": "JavaScript",
237
+ ".mjs": "ES Module",
238
+ ".cjs": "CommonJS",
239
+ ".ts": "TypeScript",
240
+ ".jsx": "React JSX",
241
+ ".tsx": "React TSX",
242
+ ".json": "JSON",
243
+ ".css": "CSS",
244
+ ".scss": "SCSS",
245
+ ".sass": "Sass",
246
+ ".less": "Less",
247
+ ".html": "HTML",
248
+ ".svg": "SVG",
249
+ ".png": "PNG",
250
+ ".jpg": "JPEG",
251
+ ".jpeg": "JPEG",
252
+ ".gif": "GIF",
253
+ ".webp": "WebP",
254
+ ".woff": "WOFF",
255
+ ".woff2": "WOFF2",
256
+ ".ttf": "TrueType",
257
+ ".eot": "EOT",
258
+ ".map": "Source Map",
259
+ ".d.ts": "TypeScript Declarations",
260
+ ".md": "Markdown",
261
+ ".txt": "Text",
262
+ ".yml": "YAML",
263
+ ".yaml": "YAML",
264
+ ".xml": "XML",
265
+ ".pdf": "PDF",
266
+ ".zip": "ZIP",
267
+ ".tar": "TAR",
268
+ ".gz": "GZIP",
269
+ };
270
+
271
+ return typeMap[extension] ?? "Unknown";
272
+ }
273
+
274
+ private calculateCompressionPotential(): number {
275
+ if (this.totalSize === 0) return 0;
276
+
277
+ let compressibleSize = 0;
278
+
279
+ for (const [extension, info] of this.fileTypes) {
280
+ // Files that typically compress well
281
+ const compressibleExtensions = [
282
+ ".js",
283
+ ".mjs",
284
+ ".cjs",
285
+ ".ts",
286
+ ".jsx",
287
+ ".tsx",
288
+ ".json",
289
+ ".css",
290
+ ".scss",
291
+ ".sass",
292
+ ".less",
293
+ ".html",
294
+ ".svg",
295
+ ".md",
296
+ ".txt",
297
+ ".yml",
298
+ ".yaml",
299
+ ".xml",
300
+ ];
301
+
302
+ if (compressibleExtensions.includes(extension)) {
303
+ compressibleSize += info.totalSize;
304
+ }
305
+ }
306
+
307
+ // Estimate 60-80% compression for text files
308
+ const estimatedCompression = compressibleSize * 0.7;
309
+ return (estimatedCompression / this.totalSize) * 100;
310
+ }
311
+ }
312
+
313
+ export const analyzeFileSystem = async (
314
+ options: FileSystemAnalysisOptions,
315
+ ): Promise<FileSystemAnalysisResult> => {
316
+ const analyzer = new FileSystemAnalyzer(options);
317
+ return analyzer.analyze();
318
+ };
319
+
320
+ export const createFileSystemAnalyzer = (
321
+ options: FileSystemAnalysisOptions,
322
+ ): FileSystemAnalyzer => {
323
+ return new FileSystemAnalyzer(options);
324
+ };