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.
@@ -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;IA8BjB,OAAO,CAAC,aAAa;IAmGf,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"}
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
- const content = fs_1.default.readFileSync(statsPath, 'utf-8');
94
- this.log(`Stats file size: ${(content.length / 1024).toFixed(2)} KB`);
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
- throw new Error(`Invalid JSON in stats file: ${e}`);
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 (entryPointError) {
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 (detectedEntry) {
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
- ` npx webpack --profile --json stats.json`);
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);