packlyze 3.0.3 ā 3.0.6
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 +393 -386
- package/dist/analyzer/packlyze.d.ts +17 -0
- package/dist/analyzer/packlyze.d.ts.map +1 -1
- package/dist/analyzer/packlyze.js +535 -9
- package/dist/analyzer/packlyze.js.map +1 -1
- package/dist/cli.js +24 -6
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,6 +9,23 @@ export declare class Packlyze {
|
|
|
9
9
|
* Automatically detect entry point by scanning common locations
|
|
10
10
|
*/
|
|
11
11
|
private detectEntryPoint;
|
|
12
|
+
/**
|
|
13
|
+
* Extract TypeScript path aliases from tsconfig.json
|
|
14
|
+
* Note: Does not handle "extends" - only reads the direct tsconfig.json file
|
|
15
|
+
*/
|
|
16
|
+
private getTypeScriptPathAliases;
|
|
17
|
+
/**
|
|
18
|
+
* Check if webpack config file exists and detect project type
|
|
19
|
+
*/
|
|
20
|
+
private checkWebpackConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Generate webpack config template based on project type
|
|
23
|
+
*/
|
|
24
|
+
private generateWebpackConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Automatically create webpack config file
|
|
27
|
+
*/
|
|
28
|
+
private createWebpackConfig;
|
|
12
29
|
private loadStats;
|
|
13
30
|
private validateStats;
|
|
14
31
|
analyze(): Promise<AnalysisResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packlyze.d.ts","sourceRoot":"","sources":["../../src/analyzer/packlyze.ts"],"names":[],"mappings":"AAEA,OAAO,EAUL,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,qBAAa,QAAQ;IACnB,OAAO,CAAC,SAAS,CAcV;IACP,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAC,CAA4B;gBAEnC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IASrE,OAAO,CAAC,GAAG;IAMX;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA6ExB,OAAO,CAAC,SAAS;
|
|
1
|
+
{"version":3,"file":"packlyze.d.ts","sourceRoot":"","sources":["../../src/analyzer/packlyze.ts"],"names":[],"mappings":"AAEA,OAAO,EAUL,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,qBAAa,QAAQ;IACnB,OAAO,CAAC,SAAS,CAcV;IACP,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAC,CAA4B;gBAEnC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IASrE,OAAO,CAAC,GAAG;IAMX;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA6ExB;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA0GhC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAsE1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA6L7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4D3B,OAAO,CAAC,SAAS;IA6DjB,OAAO,CAAC,aAAa;IA+Pf,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC;IAuCxC,OAAO,CAAC,kBAAkB;IAqG1B,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,gBAAgB;IAuCxB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,uBAAuB;IAkD/B,OAAO,CAAC,uBAAuB;IAmB/B;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,cAAc;IAoCtB,OAAO,CAAC,eAAe;IAqCvB,OAAO,CAAC,mBAAmB;IAkD3B,OAAO,CAAC,aAAa;IAgErB,OAAO,CAAC,gBAAgB;CAuBzB"}
|
|
@@ -88,11 +88,423 @@ class Packlyze {
|
|
|
88
88
|
}
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Extract TypeScript path aliases from tsconfig.json
|
|
93
|
+
* Note: Does not handle "extends" - only reads the direct tsconfig.json file
|
|
94
|
+
*/
|
|
95
|
+
getTypeScriptPathAliases() {
|
|
96
|
+
const possibleRoots = [
|
|
97
|
+
this.baseDir, // stats.json in project root
|
|
98
|
+
path_1.default.dirname(this.baseDir) // stats.json in subdirectory
|
|
99
|
+
];
|
|
100
|
+
for (const projectRoot of possibleRoots) {
|
|
101
|
+
const tsconfigPath = path_1.default.join(projectRoot, 'tsconfig.json');
|
|
102
|
+
if (fs_1.default.existsSync(tsconfigPath)) {
|
|
103
|
+
try {
|
|
104
|
+
const tsconfigContent = fs_1.default.readFileSync(tsconfigPath, 'utf-8');
|
|
105
|
+
// Remove comments (simple regex-based approach)
|
|
106
|
+
// Note: This is a basic implementation and may not handle all edge cases
|
|
107
|
+
// For production, consider using a proper JSONC parser
|
|
108
|
+
const cleanedContent = tsconfigContent.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
|
|
109
|
+
const tsconfig = JSON.parse(cleanedContent);
|
|
110
|
+
// Check if tsconfig uses "extends" - we can't resolve that without additional tooling
|
|
111
|
+
if (tsconfig.extends) {
|
|
112
|
+
this.log(`Note: tsconfig.json uses "extends" - path aliases from extended configs are not automatically resolved`);
|
|
113
|
+
}
|
|
114
|
+
const paths = tsconfig.compilerOptions?.paths;
|
|
115
|
+
if (paths && typeof paths === 'object') {
|
|
116
|
+
const aliases = {};
|
|
117
|
+
for (const [alias, pathArray] of Object.entries(paths)) {
|
|
118
|
+
// Validate alias key
|
|
119
|
+
if (!alias || typeof alias !== 'string') {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (Array.isArray(pathArray) && pathArray.length > 0) {
|
|
123
|
+
// Take the first path mapping
|
|
124
|
+
let mappedPath = pathArray[0];
|
|
125
|
+
// Validate mapped path
|
|
126
|
+
if (!mappedPath || typeof mappedPath !== 'string') {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Remove wildcards from both alias and mapped path
|
|
130
|
+
// TypeScript: "@/*": ["src/*"] -> webpack: "@": "src"
|
|
131
|
+
// TypeScript: "@/components/*": ["src/components/*"] -> webpack: "@/components": "src/components"
|
|
132
|
+
const aliasKey = alias.replace(/\*$/, '');
|
|
133
|
+
// Skip empty alias keys
|
|
134
|
+
if (!aliasKey) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (mappedPath.includes('*')) {
|
|
138
|
+
mappedPath = mappedPath.replace(/\*$/, '');
|
|
139
|
+
}
|
|
140
|
+
// Skip empty mapped paths
|
|
141
|
+
if (!mappedPath) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// Resolve the mapped path relative to tsconfig.json location
|
|
145
|
+
// The path in tsconfig is relative to the tsconfig.json file location
|
|
146
|
+
// If the path doesn't start with . or /, it's relative to baseUrl or tsconfig location
|
|
147
|
+
let resolvedPath;
|
|
148
|
+
if (path_1.default.isAbsolute(mappedPath)) {
|
|
149
|
+
resolvedPath = mappedPath;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Check if there's a baseUrl in tsconfig
|
|
153
|
+
const baseUrl = tsconfig.compilerOptions?.baseUrl;
|
|
154
|
+
if (baseUrl && typeof baseUrl === 'string') {
|
|
155
|
+
const baseUrlPath = path_1.default.isAbsolute(baseUrl)
|
|
156
|
+
? baseUrl
|
|
157
|
+
: path_1.default.resolve(path_1.default.dirname(tsconfigPath), baseUrl);
|
|
158
|
+
resolvedPath = path_1.default.resolve(baseUrlPath, mappedPath);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
resolvedPath = path_1.default.resolve(path_1.default.dirname(tsconfigPath), mappedPath);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Validate that the resolved path exists (or at least the directory)
|
|
165
|
+
const resolvedDir = path_1.default.dirname(resolvedPath);
|
|
166
|
+
if (!fs_1.default.existsSync(resolvedDir)) {
|
|
167
|
+
this.log(`Warning: Path alias "${aliasKey}" resolves to non-existent directory: ${resolvedDir}`);
|
|
168
|
+
// Continue anyway - webpack will handle the error
|
|
169
|
+
}
|
|
170
|
+
// Store the absolute path - we'll make it relative in the webpack config generation
|
|
171
|
+
aliases[aliasKey] = resolvedPath;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (Object.keys(aliases).length > 0) {
|
|
175
|
+
this.log(`Found ${Object.keys(aliases).length} TypeScript path alias(es) in tsconfig.json`);
|
|
176
|
+
return aliases;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
// Silently fail - tsconfig might be invalid or use extends
|
|
182
|
+
this.log(`Could not parse tsconfig.json: ${error}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if webpack config file exists and detect project type
|
|
190
|
+
*/
|
|
191
|
+
checkWebpackConfig() {
|
|
192
|
+
const configNames = ['webpack.config.js', 'webpack.config.cjs', 'webpack.config.ts'];
|
|
193
|
+
// Try project root (same directory as stats.json) or one level up
|
|
194
|
+
const possibleRoots = [
|
|
195
|
+
this.baseDir, // stats.json in project root
|
|
196
|
+
path_1.default.dirname(this.baseDir) // stats.json in subdirectory
|
|
197
|
+
];
|
|
198
|
+
// First, check project type
|
|
199
|
+
const projectRoot = possibleRoots[0];
|
|
200
|
+
const packageJsonPath = path_1.default.join(projectRoot, 'package.json');
|
|
201
|
+
let isESModule = false;
|
|
202
|
+
let hasTypeScript = false;
|
|
203
|
+
let framework = null;
|
|
204
|
+
if (fs_1.default.existsSync(packageJsonPath)) {
|
|
205
|
+
try {
|
|
206
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
207
|
+
isESModule = pkg.type === 'module';
|
|
208
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
209
|
+
if (deps.react || deps['react-dom']) {
|
|
210
|
+
framework = 'react';
|
|
211
|
+
}
|
|
212
|
+
else if (deps.vue || deps['@vue/cli-service']) {
|
|
213
|
+
framework = 'vue';
|
|
214
|
+
}
|
|
215
|
+
else if (deps['@angular/core']) {
|
|
216
|
+
framework = 'angular';
|
|
217
|
+
}
|
|
218
|
+
hasTypeScript = !!deps.typescript || fs_1.default.existsSync(path_1.default.join(projectRoot, 'tsconfig.json'));
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Ignore parse errors
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const correctFileName = isESModule ? 'webpack.config.cjs' : 'webpack.config.js';
|
|
225
|
+
// Now check for existing config files
|
|
226
|
+
for (const projectRoot of possibleRoots) {
|
|
227
|
+
for (const name of configNames) {
|
|
228
|
+
const configPath = path_1.default.join(projectRoot, name);
|
|
229
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
230
|
+
// Check if the config file has the wrong extension for this project type
|
|
231
|
+
const wrongExtension = isESModule && name === 'webpack.config.js';
|
|
232
|
+
return {
|
|
233
|
+
exists: true,
|
|
234
|
+
path: configPath,
|
|
235
|
+
isESModule,
|
|
236
|
+
hasTypeScript,
|
|
237
|
+
framework,
|
|
238
|
+
wrongExtension,
|
|
239
|
+
correctFileName
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Config not found
|
|
245
|
+
return {
|
|
246
|
+
exists: false,
|
|
247
|
+
path: null,
|
|
248
|
+
isESModule,
|
|
249
|
+
hasTypeScript,
|
|
250
|
+
framework,
|
|
251
|
+
wrongExtension: false,
|
|
252
|
+
correctFileName
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Generate webpack config template based on project type
|
|
257
|
+
*/
|
|
258
|
+
generateWebpackConfig(entryPoint, isESModule, hasTypeScript, framework) {
|
|
259
|
+
const useTypeScript = hasTypeScript;
|
|
260
|
+
const isReact = framework === 'react';
|
|
261
|
+
// Get TypeScript path aliases if available
|
|
262
|
+
const pathAliases = hasTypeScript ? this.getTypeScriptPathAliases() : {};
|
|
263
|
+
const hasAliases = Object.keys(pathAliases).length > 0;
|
|
264
|
+
// Generate alias configuration string
|
|
265
|
+
let aliasConfig = '';
|
|
266
|
+
if (hasAliases) {
|
|
267
|
+
// Determine the project root (where webpack.config will be)
|
|
268
|
+
const projectRoot = this.baseDir;
|
|
269
|
+
const aliasEntries = Object.entries(pathAliases)
|
|
270
|
+
.map(([key, absolutePath]) => {
|
|
271
|
+
// Escape single quotes in the key if present
|
|
272
|
+
const escapedKey = key.replace(/'/g, "\\'");
|
|
273
|
+
// Make the path relative to the project root (where webpack.config will be)
|
|
274
|
+
let relativePath = path_1.default.relative(projectRoot, absolutePath);
|
|
275
|
+
// Handle edge case: if paths are the same, use '.'
|
|
276
|
+
if (!relativePath || relativePath === '') {
|
|
277
|
+
relativePath = '.';
|
|
278
|
+
}
|
|
279
|
+
// Handle edge case: if path goes outside project root, use absolute path
|
|
280
|
+
// This can happen with complex baseUrl configurations
|
|
281
|
+
if (relativePath.startsWith('..')) {
|
|
282
|
+
// Use absolute path resolution
|
|
283
|
+
const normalizedAbsolute = absolutePath.replace(/\\/g, '/');
|
|
284
|
+
return ` '${escapedKey}': '${normalizedAbsolute}'`;
|
|
285
|
+
}
|
|
286
|
+
// Normalize path separators for cross-platform compatibility
|
|
287
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
288
|
+
// Escape any single quotes in the path
|
|
289
|
+
const escapedPath = normalizedPath.replace(/'/g, "\\'");
|
|
290
|
+
// Use path.resolve(__dirname, ...) to ensure correct resolution
|
|
291
|
+
// __dirname in webpack config will be the project root
|
|
292
|
+
return ` '${escapedKey}': path.resolve(__dirname, '${escapedPath}')`;
|
|
293
|
+
})
|
|
294
|
+
.join(',\n');
|
|
295
|
+
aliasConfig = `,\n alias: {\n${aliasEntries}\n }`;
|
|
296
|
+
}
|
|
297
|
+
let config = '';
|
|
298
|
+
if (isESModule) {
|
|
299
|
+
// CommonJS config for ES module projects
|
|
300
|
+
config = `const path = require('path');
|
|
301
|
+
|
|
302
|
+
module.exports = {
|
|
303
|
+
entry: '${entryPoint}',
|
|
304
|
+
|
|
305
|
+
output: {
|
|
306
|
+
path: path.resolve(__dirname, 'dist'),
|
|
307
|
+
filename: 'bundle.js'
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
resolve: {
|
|
311
|
+
extensions: ['.js', '.jsx'${useTypeScript ? ", '.ts', '.tsx'" : ''}]${aliasConfig},
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
module: {
|
|
315
|
+
rules: [
|
|
316
|
+
`;
|
|
317
|
+
if (useTypeScript) {
|
|
318
|
+
config += ` {
|
|
319
|
+
test: /\\.(ts|tsx)$/,
|
|
320
|
+
exclude: /node_modules/,
|
|
321
|
+
use: {
|
|
322
|
+
loader: 'ts-loader',
|
|
323
|
+
options: {
|
|
324
|
+
transpileOnly: true
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
`;
|
|
329
|
+
}
|
|
330
|
+
if (isReact && !useTypeScript) {
|
|
331
|
+
config += ` {
|
|
332
|
+
test: /\\.(js|jsx)$/,
|
|
333
|
+
exclude: /node_modules/,
|
|
334
|
+
use: {
|
|
335
|
+
loader: 'babel-loader',
|
|
336
|
+
options: {
|
|
337
|
+
presets: ['@babel/preset-env', '@babel/preset-react']
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
config += ` {
|
|
344
|
+
test: /\\.(png|jpg|jpeg|gif|svg)$/,
|
|
345
|
+
type: 'asset/resource'
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
mode: 'production',
|
|
351
|
+
|
|
352
|
+
stats: {
|
|
353
|
+
modules: true,
|
|
354
|
+
chunks: true,
|
|
355
|
+
chunkModules: true,
|
|
356
|
+
chunkOrigins: true,
|
|
357
|
+
assets: true,
|
|
358
|
+
entrypoints: true
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
`;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
// Standard CommonJS config
|
|
365
|
+
config = `const path = require('path');
|
|
366
|
+
|
|
367
|
+
module.exports = {
|
|
368
|
+
entry: '${entryPoint}',
|
|
369
|
+
|
|
370
|
+
output: {
|
|
371
|
+
path: path.resolve(__dirname, 'dist'),
|
|
372
|
+
filename: 'bundle.js'
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
resolve: {
|
|
376
|
+
extensions: ['.js', '.jsx'${useTypeScript ? ", '.ts', '.tsx'" : ''}]${aliasConfig},
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
module: {
|
|
380
|
+
rules: [
|
|
381
|
+
`;
|
|
382
|
+
if (useTypeScript) {
|
|
383
|
+
config += ` {
|
|
384
|
+
test: /\\.(ts|tsx)$/,
|
|
385
|
+
exclude: /node_modules/,
|
|
386
|
+
use: {
|
|
387
|
+
loader: 'ts-loader',
|
|
388
|
+
options: {
|
|
389
|
+
transpileOnly: true
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
`;
|
|
394
|
+
}
|
|
395
|
+
if (isReact && !useTypeScript) {
|
|
396
|
+
config += ` {
|
|
397
|
+
test: /\\.(js|jsx)$/,
|
|
398
|
+
exclude: /node_modules/,
|
|
399
|
+
use: {
|
|
400
|
+
loader: 'babel-loader',
|
|
401
|
+
options: {
|
|
402
|
+
presets: ['@babel/preset-env', '@babel/preset-react']
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
config += ` {
|
|
409
|
+
test: /\\.(png|jpg|jpeg|gif|svg)$/,
|
|
410
|
+
type: 'asset/resource'
|
|
411
|
+
}
|
|
412
|
+
]
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
mode: 'production',
|
|
416
|
+
|
|
417
|
+
stats: {
|
|
418
|
+
modules: true,
|
|
419
|
+
chunks: true,
|
|
420
|
+
chunkModules: true,
|
|
421
|
+
chunkOrigins: true,
|
|
422
|
+
assets: true,
|
|
423
|
+
entrypoints: true
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
`;
|
|
427
|
+
}
|
|
428
|
+
return config;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Automatically create webpack config file
|
|
432
|
+
*/
|
|
433
|
+
createWebpackConfig(entryPoint, isESModule, hasTypeScript, framework) {
|
|
434
|
+
const projectRoot = this.baseDir; // Use stats.json directory as project root
|
|
435
|
+
// Validate project root exists and is writable
|
|
436
|
+
if (!fs_1.default.existsSync(projectRoot)) {
|
|
437
|
+
throw new Error(`Project root directory does not exist: ${projectRoot}`);
|
|
438
|
+
}
|
|
439
|
+
const stats = fs_1.default.statSync(projectRoot);
|
|
440
|
+
if (!stats.isDirectory()) {
|
|
441
|
+
throw new Error(`Project root is not a directory: ${projectRoot}`);
|
|
442
|
+
}
|
|
443
|
+
const configFileName = isESModule ? 'webpack.config.cjs' : 'webpack.config.js';
|
|
444
|
+
const configPath = path_1.default.join(projectRoot, configFileName);
|
|
445
|
+
// Check if file already exists
|
|
446
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
447
|
+
// Check if it's actually a file (not a directory)
|
|
448
|
+
const existingStats = fs_1.default.statSync(configPath);
|
|
449
|
+
if (!existingStats.isFile()) {
|
|
450
|
+
throw new Error(`Path exists but is not a file: ${configPath}`);
|
|
451
|
+
}
|
|
452
|
+
this.log(`Webpack config already exists at ${configPath}`);
|
|
453
|
+
return configPath; // Return existing path
|
|
454
|
+
}
|
|
455
|
+
// Validate entry point exists
|
|
456
|
+
const entryPointPath = path_1.default.isAbsolute(entryPoint)
|
|
457
|
+
? entryPoint
|
|
458
|
+
: path_1.default.join(projectRoot, entryPoint);
|
|
459
|
+
if (!fs_1.default.existsSync(entryPointPath)) {
|
|
460
|
+
this.log(`Warning: Entry point does not exist: ${entryPointPath}`);
|
|
461
|
+
// Continue anyway - webpack will handle the error
|
|
462
|
+
}
|
|
463
|
+
// Generate config content
|
|
464
|
+
const configContent = this.generateWebpackConfig(entryPoint, isESModule, hasTypeScript, framework);
|
|
465
|
+
// Write config file
|
|
466
|
+
try {
|
|
467
|
+
fs_1.default.writeFileSync(configPath, configContent, 'utf-8');
|
|
468
|
+
this.log(`Created ${configFileName} at ${configPath}`);
|
|
469
|
+
return configPath;
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
473
|
+
this.log(`Failed to create ${configFileName}: ${errorMessage}`);
|
|
474
|
+
// Provide helpful error message
|
|
475
|
+
if (errorMessage.includes('EACCES') || errorMessage.includes('permission')) {
|
|
476
|
+
throw new Error(`Permission denied: Cannot write to ${configPath}\nš” Check file permissions or run with appropriate privileges`);
|
|
477
|
+
}
|
|
478
|
+
if (errorMessage.includes('ENOSPC')) {
|
|
479
|
+
throw new Error(`No space left on device: Cannot write ${configPath}`);
|
|
480
|
+
}
|
|
481
|
+
throw new Error(`Failed to create webpack config: ${errorMessage}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
91
484
|
loadStats(statsPath) {
|
|
92
485
|
this.log('Reading stats file...');
|
|
93
|
-
|
|
94
|
-
|
|
486
|
+
// Check if file exists and is readable
|
|
487
|
+
if (!fs_1.default.existsSync(statsPath)) {
|
|
488
|
+
throw new Error(`Stats file not found: ${statsPath}`);
|
|
489
|
+
}
|
|
490
|
+
// Check if it's a file (not a directory)
|
|
491
|
+
const stats = fs_1.default.statSync(statsPath);
|
|
492
|
+
if (!stats.isFile()) {
|
|
493
|
+
throw new Error(`Path is not a file: ${statsPath}`);
|
|
494
|
+
}
|
|
495
|
+
// Check file size (warn if very large)
|
|
496
|
+
const fileSizeKB = stats.size / 1024;
|
|
497
|
+
const fileSizeMB = fileSizeKB / 1024;
|
|
498
|
+
this.log(`Stats file size: ${fileSizeKB.toFixed(2)} KB`);
|
|
499
|
+
if (fileSizeMB > 100) {
|
|
500
|
+
this.log(`Warning: Stats file is very large (${fileSizeMB.toFixed(2)} MB). Parsing may take a while...`);
|
|
501
|
+
}
|
|
95
502
|
try {
|
|
503
|
+
const content = fs_1.default.readFileSync(statsPath, 'utf-8');
|
|
504
|
+
// Check if file is empty
|
|
505
|
+
if (!content || content.trim().length === 0) {
|
|
506
|
+
throw new Error('Stats file is empty');
|
|
507
|
+
}
|
|
96
508
|
this.log('Parsing JSON...');
|
|
97
509
|
this.statsData = JSON.parse(content);
|
|
98
510
|
this.log('Validating stats structure...');
|
|
@@ -109,10 +521,16 @@ class Packlyze {
|
|
|
109
521
|
this.log(`Found ${topLevelModules} top-level modules, ${chunkModules} modules in chunks, ${chunks.length} chunks`);
|
|
110
522
|
}
|
|
111
523
|
catch (e) {
|
|
524
|
+
if (e instanceof SyntaxError) {
|
|
525
|
+
throw new Error(`Invalid JSON in stats file: ${e.message}\nš” Make sure the stats file was generated correctly (e.g., webpack --profile --json stats.json)`);
|
|
526
|
+
}
|
|
112
527
|
if (e instanceof Error && e.message.includes('Invalid stats')) {
|
|
113
528
|
throw e;
|
|
114
529
|
}
|
|
115
|
-
|
|
530
|
+
if (e instanceof Error && e.message.includes('empty')) {
|
|
531
|
+
throw e;
|
|
532
|
+
}
|
|
533
|
+
throw new Error(`Error reading stats file: ${e instanceof Error ? e.message : String(e)}`);
|
|
116
534
|
}
|
|
117
535
|
}
|
|
118
536
|
validateStats() {
|
|
@@ -131,24 +549,132 @@ class Packlyze {
|
|
|
131
549
|
// Check for entry point resolution errors and auto-detect entry files
|
|
132
550
|
const entryPointError = errors.find(e => e.message?.includes("Can't resolve") &&
|
|
133
551
|
(e.message.includes('./src') || e.message.includes("'./src'") || e.message.includes('"./src"')));
|
|
552
|
+
// Check for ES module config loading errors
|
|
553
|
+
const esModuleConfigError = errors.find(e => e.message?.includes('require is not defined in ES module scope') ||
|
|
554
|
+
e.message?.includes('To treat it as a CommonJS script, rename it to use the \'.cjs\' file extension'));
|
|
134
555
|
let suggestion = '';
|
|
135
|
-
if (
|
|
556
|
+
if (esModuleConfigError) {
|
|
557
|
+
// ES module config loading error - config has wrong extension
|
|
558
|
+
const configCheck = this.checkWebpackConfig();
|
|
559
|
+
const detectedEntry = this.detectEntryPoint();
|
|
560
|
+
const entryPoint = detectedEntry || './src/index.js';
|
|
561
|
+
try {
|
|
562
|
+
// Automatically create the correct config file
|
|
563
|
+
this.createWebpackConfig(entryPoint, configCheck.isESModule, configCheck.hasTypeScript, configCheck.framework);
|
|
564
|
+
const currentFileName = configCheck.path ? path_1.default.basename(configCheck.path) : 'webpack.config.js';
|
|
565
|
+
suggestion = `\n\nā
Created ${configCheck.correctFileName} automatically!\n\n` +
|
|
566
|
+
`Your project uses ES modules, so webpack needs ${configCheck.correctFileName} instead of ${currentFileName}.\n\n` +
|
|
567
|
+
`š Auto-detected entry point: ${entryPoint}\n` +
|
|
568
|
+
`š¦ Project type: ${configCheck.framework || 'Generic'}${configCheck.hasTypeScript ? ' + TypeScript' : ''} (ES Modules)\n\n` +
|
|
569
|
+
`š” Next steps:\n`;
|
|
570
|
+
if (configCheck.hasTypeScript) {
|
|
571
|
+
suggestion += ` 1. Install required loaders: npm install --save-dev ts-loader typescript\n`;
|
|
572
|
+
}
|
|
573
|
+
if (configCheck.framework === 'react' && !configCheck.hasTypeScript) {
|
|
574
|
+
suggestion += ` 1. Install required loaders: npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react\n`;
|
|
575
|
+
}
|
|
576
|
+
suggestion += ` 2. Regenerate stats.json: npx webpack --profile --json stats.json\n`;
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
// If creation fails, fall back to manual instructions
|
|
580
|
+
const configTemplate = this.generateWebpackConfig(entryPoint, configCheck.isESModule, configCheck.hasTypeScript, configCheck.framework);
|
|
581
|
+
const currentFileName = configCheck.path ? path_1.default.basename(configCheck.path) : 'webpack.config.js';
|
|
582
|
+
suggestion = `\n\nā ļø Webpack config file has wrong extension!\n\n` +
|
|
583
|
+
`Your project uses ES modules (package.json has "type": "module"), but you have ${currentFileName}.\n` +
|
|
584
|
+
`Webpack can't load CommonJS config files (.js) in ES module projects.\n\n` +
|
|
585
|
+
`š Auto-detected entry point: ${entryPoint}\n` +
|
|
586
|
+
`š¦ Project type: ${configCheck.framework || 'Generic'}${configCheck.hasTypeScript ? ' + TypeScript' : ''} (ES Modules)\n\n` +
|
|
587
|
+
`Create ${configCheck.correctFileName} with the following content:\n\n` +
|
|
588
|
+
`\`\`\`javascript\n${configTemplate}\`\`\`\n\n` +
|
|
589
|
+
`š” After creating the config file, regenerate stats.json:\n` +
|
|
590
|
+
` npx webpack --profile --json stats.json\n`;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else if (entryPointError) {
|
|
594
|
+
const configCheck = this.checkWebpackConfig();
|
|
136
595
|
const detectedEntry = this.detectEntryPoint();
|
|
137
|
-
if (
|
|
596
|
+
if (!configCheck.exists) {
|
|
597
|
+
// Webpack config doesn't exist - automatically create it
|
|
598
|
+
const entryPoint = detectedEntry || './src/index.js';
|
|
599
|
+
try {
|
|
600
|
+
this.createWebpackConfig(entryPoint, configCheck.isESModule, configCheck.hasTypeScript, configCheck.framework);
|
|
601
|
+
suggestion = `\n\nā
Created ${configCheck.correctFileName} automatically!\n\n` +
|
|
602
|
+
`š Auto-detected entry point: ${entryPoint}\n` +
|
|
603
|
+
`š¦ Project type: ${configCheck.framework || 'Generic'}${configCheck.hasTypeScript ? ' + TypeScript' : ''}${configCheck.isESModule ? ' (ES Modules)' : ''}\n\n` +
|
|
604
|
+
`š” Next steps:\n`;
|
|
605
|
+
if (configCheck.hasTypeScript) {
|
|
606
|
+
suggestion += ` 1. Install required loaders: npm install --save-dev ts-loader typescript\n`;
|
|
607
|
+
}
|
|
608
|
+
if (configCheck.framework === 'react' && !configCheck.hasTypeScript) {
|
|
609
|
+
suggestion += ` 1. Install required loaders: npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react\n`;
|
|
610
|
+
}
|
|
611
|
+
suggestion += ` 2. Regenerate stats.json: npx webpack --profile --json stats.json\n`;
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
// If creation fails, fall back to manual instructions
|
|
615
|
+
const configTemplate = this.generateWebpackConfig(entryPoint, configCheck.isESModule, configCheck.hasTypeScript, configCheck.framework);
|
|
616
|
+
suggestion = `\n\nš Webpack config file not found!\n\n` +
|
|
617
|
+
`š Auto-detected entry point: ${entryPoint}\n` +
|
|
618
|
+
`š¦ Project type: ${configCheck.framework || 'Generic'}${configCheck.hasTypeScript ? ' + TypeScript' : ''}${configCheck.isESModule ? ' (ES Modules)' : ''}\n\n` +
|
|
619
|
+
`Create ${configCheck.correctFileName} with the following content:\n\n` +
|
|
620
|
+
`\`\`\`javascript\n${configTemplate}\`\`\`\n\n` +
|
|
621
|
+
`š” After creating the config file, install required loaders:\n`;
|
|
622
|
+
if (configCheck.hasTypeScript) {
|
|
623
|
+
suggestion += ` npm install --save-dev ts-loader typescript\n`;
|
|
624
|
+
}
|
|
625
|
+
if (configCheck.framework === 'react' && !configCheck.hasTypeScript) {
|
|
626
|
+
suggestion += ` npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react\n`;
|
|
627
|
+
}
|
|
628
|
+
suggestion += `\n Then regenerate stats.json:\n` +
|
|
629
|
+
` npx webpack --profile --json stats.json\n`;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
else if (configCheck.wrongExtension) {
|
|
633
|
+
// Config exists but has wrong extension - create the correct one
|
|
634
|
+
const entryPoint = detectedEntry || './src/index.js';
|
|
635
|
+
try {
|
|
636
|
+
this.createWebpackConfig(entryPoint, configCheck.isESModule, configCheck.hasTypeScript, configCheck.framework);
|
|
637
|
+
const currentFileName = path_1.default.basename(configCheck.path || 'webpack.config.js');
|
|
638
|
+
suggestion = `\n\nā
Created ${configCheck.correctFileName} automatically!\n\n` +
|
|
639
|
+
`Your project uses ES modules, so webpack needs ${configCheck.correctFileName} instead of ${currentFileName}.\n\n` +
|
|
640
|
+
`š Auto-detected entry point: ${entryPoint}\n` +
|
|
641
|
+
`š¦ Project type: ${configCheck.framework || 'Generic'}${configCheck.hasTypeScript ? ' + TypeScript' : ''} (ES Modules)\n\n` +
|
|
642
|
+
`š” Next steps:\n` +
|
|
643
|
+
` 1. You can delete ${currentFileName} if you no longer need it\n` +
|
|
644
|
+
` 2. Regenerate stats.json: npx webpack --profile --json stats.json\n`;
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
// If creation fails, fall back to manual instructions
|
|
648
|
+
const configTemplate = this.generateWebpackConfig(entryPoint, configCheck.isESModule, configCheck.hasTypeScript, configCheck.framework);
|
|
649
|
+
const currentFileName = path_1.default.basename(configCheck.path || 'webpack.config.js');
|
|
650
|
+
suggestion = `\n\nā ļø Webpack config file has wrong extension!\n\n` +
|
|
651
|
+
`Your project uses ES modules (package.json has "type": "module"), but you have ${currentFileName}.\n` +
|
|
652
|
+
`Webpack can't load CommonJS config files (.js) in ES module projects.\n\n` +
|
|
653
|
+
`š Auto-detected entry point: ${entryPoint}\n` +
|
|
654
|
+
`š¦ Project type: ${configCheck.framework || 'Generic'}${configCheck.hasTypeScript ? ' + TypeScript' : ''} (ES Modules)\n\n` +
|
|
655
|
+
`Create ${configCheck.correctFileName} with the following content:\n\n` +
|
|
656
|
+
`\`\`\`javascript\n${configTemplate}\`\`\`\n\n` +
|
|
657
|
+
`š” After creating the config file, regenerate stats.json:\n` +
|
|
658
|
+
` npx webpack --profile --json stats.json\n`;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
else if (detectedEntry) {
|
|
662
|
+
// Config exists and extension is correct, but entry point is wrong
|
|
138
663
|
suggestion = `\n\nš Auto-detected entry point: ${detectedEntry}\n` +
|
|
139
|
-
` Update your webpack.config.js:\n` +
|
|
664
|
+
` Update your ${path_1.default.basename(configCheck.path || 'webpack.config.js')}:\n` +
|
|
140
665
|
` entry: '${detectedEntry}',\n`;
|
|
141
666
|
}
|
|
142
667
|
else {
|
|
143
668
|
suggestion = `\n\nš” Tip: Your entry point should be a FILE, not a directory.\n` +
|
|
144
|
-
` Common entry points: ./src/App.tsx, ./src/index.jsx, ./src/main.js\n
|
|
669
|
+
` Common entry points: ./src/App.tsx, ./src/index.jsx, ./src/main.js\n` +
|
|
670
|
+
` Update the 'entry' field in your webpack config.\n`;
|
|
145
671
|
}
|
|
146
672
|
}
|
|
147
673
|
throw new Error(`Webpack build failed with ${errors.length} error(s). Cannot analyze bundle.\n\n` +
|
|
148
674
|
`Errors:\n ${errorMessages}${errors.length > 3 ? `\n ... and ${errors.length - 3} more error(s)` : ''}` +
|
|
149
675
|
suggestion +
|
|
150
|
-
`\nš” Fix the webpack build errors first, then regenerate stats.json:\n` +
|
|
151
|
-
|
|
676
|
+
(suggestion ? '' : `\nš” Fix the webpack build errors first, then regenerate stats.json:\n` +
|
|
677
|
+
` npx webpack --profile --json stats.json`));
|
|
152
678
|
}
|
|
153
679
|
// Check for required structure (at least one of assets, modules, or chunks should exist)
|
|
154
680
|
const hasAssets = Array.isArray(this.statsData.assets);
|