@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.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/cmds/build/cmd.d.ts +2 -0
- package/dist/cmds/build/cmd.js +564 -0
- package/dist/cmds/clean/cmd.d.ts +2 -0
- package/dist/cmds/clean/cmd.js +146 -0
- package/dist/cmds/clean/impl.d.ts +2 -0
- package/dist/cmds/clean/impl.js +627 -0
- package/dist/cmds/clean/presets.d.ts +10 -0
- package/dist/cmds/clean/presets.js +112 -0
- package/dist/cmds/clean/types.d.ts +62 -0
- package/dist/cmds/clean/types.js +0 -0
- package/dist/cmds/init/cmd.d.ts +3 -0
- package/dist/cmds/init/cmd.js +56 -0
- package/dist/cmds/init/impl/config.d.ts +45 -0
- package/dist/cmds/init/impl/config.js +99 -0
- package/dist/cmds/init/impl/generators.d.ts +6 -0
- package/dist/cmds/init/impl/generators.js +178 -0
- package/dist/cmds/init/impl/prompts.d.ts +2 -0
- package/dist/cmds/init/impl/prompts.js +98 -0
- package/dist/cmds/init/impl/types.d.ts +22 -0
- package/dist/cmds/init/impl/types.js +0 -0
- package/dist/cmds/init/impl/utils.d.ts +4 -0
- package/dist/cmds/init/impl/utils.js +11 -0
- package/dist/cmds/init/impl/validators.d.ts +4 -0
- package/dist/cmds/init/impl/validators.js +42 -0
- package/dist/cmds/integrate/cmd.d.ts +3 -0
- package/dist/cmds/integrate/cmd.js +70 -0
- package/dist/cmds/integrate/impl.d.ts +7 -0
- package/dist/cmds/integrate/impl.js +127 -0
- package/dist/cmds/integrate/integrations/base.d.ts +13 -0
- package/dist/cmds/integrate/integrations/base.js +41 -0
- package/dist/cmds/integrate/integrations/nextjs.d.ts +16 -0
- package/dist/cmds/integrate/integrations/nextjs.js +167 -0
- package/dist/cmds/integrate/integrations/registry.d.ts +7 -0
- package/dist/cmds/integrate/integrations/registry.js +31 -0
- package/dist/cmds/integrate/integrations/ultracite.d.ts +11 -0
- package/dist/cmds/integrate/integrations/ultracite.js +40 -0
- package/dist/cmds/integrate/types.d.ts +39 -0
- package/dist/cmds/integrate/types.js +0 -0
- package/dist/cmds/integrate/utils/biome.d.ts +4 -0
- package/dist/cmds/integrate/utils/biome.js +140 -0
- package/dist/cmds/integrate/utils/context.d.ts +3 -0
- package/dist/cmds/integrate/utils/context.js +111 -0
- package/dist/cmds/integrate/utils/temp.d.ts +3 -0
- package/dist/cmds/integrate/utils/temp.js +36 -0
- package/dist/cmds/perf/analysis/bundle.d.ts +20 -0
- package/dist/cmds/perf/analysis/bundle.js +225 -0
- package/dist/cmds/perf/analysis/filesystem.d.ts +27 -0
- package/dist/cmds/perf/analysis/filesystem.js +246 -0
- package/dist/cmds/perf/analysis/monorepo.d.ts +29 -0
- package/dist/cmds/perf/analysis/monorepo.js +307 -0
- package/dist/cmds/perf/benchmarks/command.d.ts +21 -0
- package/dist/cmds/perf/benchmarks/command.js +162 -0
- package/dist/cmds/perf/benchmarks/memory.d.ts +41 -0
- package/dist/cmds/perf/benchmarks/memory.js +169 -0
- package/dist/cmds/perf/benchmarks/runner.d.ts +22 -0
- package/dist/cmds/perf/benchmarks/runner.js +157 -0
- package/dist/cmds/perf/cmd.d.ts +2 -0
- package/dist/cmds/perf/cmd.js +238 -0
- package/dist/cmds/perf/impl.d.ts +24 -0
- package/dist/cmds/perf/impl.js +304 -0
- package/dist/cmds/perf/reporters/console.d.ts +12 -0
- package/dist/cmds/perf/reporters/console.js +257 -0
- package/dist/cmds/perf/reporters/html.d.ts +27 -0
- package/dist/cmds/perf/reporters/html.js +881 -0
- package/dist/cmds/perf/reporters/json.d.ts +9 -0
- package/dist/cmds/perf/reporters/json.js +32 -0
- package/dist/cmds/perf/types.d.ts +184 -0
- package/dist/cmds/perf/types.js +0 -0
- package/dist/cmds/perf/utils/cache.d.ts +23 -0
- package/dist/cmds/perf/utils/cache.js +171 -0
- package/dist/cmds/perf/utils/formatter.d.ts +17 -0
- package/dist/cmds/perf/utils/formatter.js +134 -0
- package/dist/cmds/perf/utils/stats.d.ts +15 -0
- package/dist/cmds/perf/utils/stats.js +101 -0
- package/dist/cmds/publish/cmd.d.ts +3 -0
- package/dist/cmds/publish/cmd.js +189 -0
- package/dist/cmds/shell/cmd.d.ts +3 -0
- package/dist/cmds/shell/cmd.js +50 -0
- package/dist/cmds/tsc/cache.d.ts +27 -0
- package/dist/cmds/tsc/cache.js +160 -0
- package/dist/cmds/tsc/cmd.d.ts +2 -0
- package/dist/cmds/tsc/cmd.js +111 -0
- package/dist/cmds/tsc/impl.d.ts +41 -0
- package/dist/cmds/tsc/impl.js +572 -0
- package/dist/cmds/tsc/types.d.ts +57 -0
- package/dist/cmds/tsc/types.js +0 -0
- package/package.json +4 -11
- package/src/cli.ts +8 -0
- package/src/cmds/build/cmd.ts +582 -0
- package/src/cmds/clean/cmd.ts +166 -0
- package/src/cmds/clean/impl.ts +900 -0
- package/src/cmds/clean/presets.ts +158 -0
- package/src/cmds/clean/types.ts +71 -0
- package/src/cmds/init/cmd.ts +68 -0
- package/src/cmds/init/impl/config.ts +105 -0
- package/src/cmds/init/impl/generators.ts +220 -0
- package/src/cmds/init/impl/prompts.ts +137 -0
- package/src/cmds/init/impl/types.ts +25 -0
- package/src/cmds/init/impl/utils.ts +17 -0
- package/src/cmds/init/impl/validators.ts +55 -0
- package/src/cmds/integrate/cmd.ts +82 -0
- package/src/cmds/integrate/impl.ts +204 -0
- package/src/cmds/integrate/integrations/base.ts +69 -0
- package/src/cmds/integrate/integrations/nextjs.ts +227 -0
- package/src/cmds/integrate/integrations/registry.ts +45 -0
- package/src/cmds/integrate/integrations/ultracite.ts +53 -0
- package/src/cmds/integrate/types.ts +48 -0
- package/src/cmds/integrate/utils/biome.ts +173 -0
- package/src/cmds/integrate/utils/context.ts +148 -0
- package/src/cmds/integrate/utils/temp.ts +47 -0
- package/src/cmds/perf/analysis/bundle.ts +311 -0
- package/src/cmds/perf/analysis/filesystem.ts +324 -0
- package/src/cmds/perf/analysis/monorepo.ts +439 -0
- package/src/cmds/perf/benchmarks/command.ts +230 -0
- package/src/cmds/perf/benchmarks/memory.ts +249 -0
- package/src/cmds/perf/benchmarks/runner.ts +220 -0
- package/src/cmds/perf/cmd.ts +285 -0
- package/src/cmds/perf/impl.ts +411 -0
- package/src/cmds/perf/reporters/console.ts +331 -0
- package/src/cmds/perf/reporters/html.ts +984 -0
- package/src/cmds/perf/reporters/json.ts +42 -0
- package/src/cmds/perf/types.ts +220 -0
- package/src/cmds/perf/utils/cache.ts +234 -0
- package/src/cmds/perf/utils/formatter.ts +190 -0
- package/src/cmds/perf/utils/stats.ts +153 -0
- package/src/cmds/publish/cmd.ts +215 -0
- package/src/cmds/shell/cmd.ts +61 -0
- package/src/cmds/tsc/cache.ts +237 -0
- package/src/cmds/tsc/cmd.ts +139 -0
- package/src/cmds/tsc/impl.ts +855 -0
- package/src/cmds/tsc/types.ts +66 -0
- package/tsconfig.json +9 -0
- 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
|
+
};
|