packlyze 3.0.9 → 4.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/README.md +74 -3
- package/dist/analyzer/packlyze.d.ts +27 -0
- package/dist/analyzer/packlyze.d.ts.map +1 -1
- package/dist/analyzer/packlyze.js +535 -34
- package/dist/analyzer/packlyze.js.map +1 -1
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -1
- package/dist/visualization/reports.js +88 -54
- package/dist/visualization/reports.js.map +1 -1
- package/package.json +3 -3
|
@@ -53,14 +53,14 @@ class Packlyze {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
// Priority order for entry point detection
|
|
56
|
+
// main.* files are typically the actual entry points, so check them first
|
|
56
57
|
const entryPointPatterns = [
|
|
57
|
-
//
|
|
58
|
+
// Main entry points (highest priority - these are typically the actual entry points)
|
|
59
|
+
'main.tsx', 'main.ts', 'main.jsx', 'main.js',
|
|
60
|
+
// React App files (common but not always the entry point)
|
|
58
61
|
'App.tsx', 'App.jsx', 'app.tsx', 'app.jsx',
|
|
59
|
-
//
|
|
60
|
-
'index.tsx', 'index.jsx',
|
|
61
|
-
// Generic entry points
|
|
62
|
-
'main.ts', 'main.js', 'main.tsx', 'main.jsx',
|
|
63
|
-
'index.ts', 'index.js',
|
|
62
|
+
// Index files (fallback entry points)
|
|
63
|
+
'index.tsx', 'index.ts', 'index.jsx', 'index.js',
|
|
64
64
|
// Vue/Angular
|
|
65
65
|
'app.ts', 'app.js',
|
|
66
66
|
];
|
|
@@ -291,6 +291,203 @@ class Packlyze {
|
|
|
291
291
|
}
|
|
292
292
|
return possibleRoots;
|
|
293
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Detect missing file extensions in resolve.extensions
|
|
296
|
+
* Example: Error resolving './App' when App.tsx exists but .tsx not in extensions
|
|
297
|
+
*/
|
|
298
|
+
detectMissingExtensions(errors) {
|
|
299
|
+
const missingExtensions = new Set();
|
|
300
|
+
for (const error of errors) {
|
|
301
|
+
const message = (error.message || '') + ' ' + (error.details || '');
|
|
302
|
+
// Pattern: Can't resolve './App' or './Component'
|
|
303
|
+
// Check if file exists with common extensions
|
|
304
|
+
const resolveError = message.match(/Can't resolve ['"]([^'"]+)['"]/);
|
|
305
|
+
if (resolveError) {
|
|
306
|
+
const importPath = resolveError[1];
|
|
307
|
+
// Skip if it's a package (starts with letter, no ./ or ../)
|
|
308
|
+
if (importPath.startsWith('./') || importPath.startsWith('../')) {
|
|
309
|
+
const projectRoot = this.baseDir;
|
|
310
|
+
const possibleExtensions = ['.tsx', '.ts', '.jsx', '.js', '.json', '.css', '.scss', '.svg', '.png'];
|
|
311
|
+
for (const ext of possibleExtensions) {
|
|
312
|
+
const possibleFile = path_1.default.resolve(projectRoot, importPath + ext);
|
|
313
|
+
if (fs_1.default.existsSync(possibleFile)) {
|
|
314
|
+
// File exists with this extension, but webpack couldn't resolve it
|
|
315
|
+
// This suggests the extension is missing from resolve.extensions
|
|
316
|
+
missingExtensions.add(ext);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return Array.from(missingExtensions);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Detect missing CSS/image loaders
|
|
326
|
+
*/
|
|
327
|
+
detectMissingAssetLoaders(errors) {
|
|
328
|
+
let css = false;
|
|
329
|
+
let images = false;
|
|
330
|
+
for (const error of errors) {
|
|
331
|
+
const message = (error.message || '') + ' ' + (error.details || '');
|
|
332
|
+
// CSS loader errors
|
|
333
|
+
if (message.includes('.css') &&
|
|
334
|
+
(message.includes("You may need an appropriate loader") ||
|
|
335
|
+
message.includes("Can't resolve") && message.match(/\.css['"]/))) {
|
|
336
|
+
css = true;
|
|
337
|
+
}
|
|
338
|
+
// Image loader errors
|
|
339
|
+
if ((message.includes('.svg') || message.includes('.png') || message.includes('.jpg') || message.includes('.gif')) &&
|
|
340
|
+
(message.includes("You may need an appropriate loader") ||
|
|
341
|
+
message.includes("Can't resolve") && message.match(/\.(svg|png|jpg|jpeg|gif)['"]/))) {
|
|
342
|
+
images = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return { css, images };
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Detect baseUrl usage without proper webpack configuration
|
|
349
|
+
*/
|
|
350
|
+
detectBaseUrlIssues(errors) {
|
|
351
|
+
for (const error of errors) {
|
|
352
|
+
const message = (error.message || '') + ' ' + (error.details || '');
|
|
353
|
+
// Pattern: Can't resolve 'src/utils' or 'app/components' (absolute imports without ./ or ../)
|
|
354
|
+
// This suggests baseUrl is set in tsconfig but not configured in webpack
|
|
355
|
+
const absoluteImportError = message.match(/Can't resolve ['"]([^./][^'"]+)['"]/);
|
|
356
|
+
if (absoluteImportError) {
|
|
357
|
+
const importPath = absoluteImportError[1];
|
|
358
|
+
// Check if it's not a node_modules package (doesn't start with @ or common package names)
|
|
359
|
+
if (!importPath.startsWith('@') &&
|
|
360
|
+
!importPath.match(/^(react|vue|angular|@babel|@types)/)) {
|
|
361
|
+
// Check if this path exists in the project
|
|
362
|
+
const projectRoot = this.baseDir;
|
|
363
|
+
const possiblePath = path_1.default.join(projectRoot, importPath);
|
|
364
|
+
if (fs_1.default.existsSync(possiblePath) || fs_1.default.existsSync(possiblePath + '.ts') || fs_1.default.existsSync(possiblePath + '.tsx')) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Detect case-sensitivity issues
|
|
374
|
+
*/
|
|
375
|
+
detectCaseSensitivityIssues(errors) {
|
|
376
|
+
const issues = [];
|
|
377
|
+
for (const error of errors) {
|
|
378
|
+
const message = (error.message || '') + ' ' + (error.details || '');
|
|
379
|
+
// Pattern: Can't resolve './components/Button' but './Components/button.tsx' exists
|
|
380
|
+
const resolveError = message.match(/Can't resolve ['"]([^'"]+)['"]/);
|
|
381
|
+
if (resolveError) {
|
|
382
|
+
const importPath = resolveError[1];
|
|
383
|
+
if (importPath.startsWith('./') || importPath.startsWith('../')) {
|
|
384
|
+
const projectRoot = this.baseDir;
|
|
385
|
+
const fullPath = path_1.default.resolve(projectRoot, importPath);
|
|
386
|
+
const dir = path_1.default.dirname(fullPath);
|
|
387
|
+
const basename = path_1.default.basename(fullPath);
|
|
388
|
+
if (fs_1.default.existsSync(dir)) {
|
|
389
|
+
// Check for case-insensitive matches
|
|
390
|
+
const files = fs_1.default.readdirSync(dir);
|
|
391
|
+
const lowerBasename = basename.toLowerCase();
|
|
392
|
+
for (const file of files) {
|
|
393
|
+
if (file.toLowerCase() === lowerBasename && file !== basename) {
|
|
394
|
+
issues.push({ expected: importPath, actual: path_1.default.join(path_1.default.dirname(importPath), file) });
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return issues;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Extract alias patterns from error messages
|
|
406
|
+
* Example: "Can't resolve '@/hooks/useAuth'" -> { alias: '@', path: '@/hooks/useAuth' }
|
|
407
|
+
*/
|
|
408
|
+
extractAliasFromErrors(errors) {
|
|
409
|
+
const aliases = new Map();
|
|
410
|
+
for (const error of errors) {
|
|
411
|
+
const message = (error.message || '') + ' ' + (error.details || '');
|
|
412
|
+
// Match patterns like '@/hooks/useAuth', '@\\hooks\\useAuth', or "'@/hooks/useAuth'"
|
|
413
|
+
const aliasPatterns = [
|
|
414
|
+
/['"`]?(@[^/\\'"`\s]+)[/\\]/g, // @/something or @\something
|
|
415
|
+
/Can't resolve ['"`]?(@[^/\\'"`\s]+)[/\\]/g,
|
|
416
|
+
/Cannot resolve ['"`]?(@[^/\\'"`\s]+)[/\\]/g,
|
|
417
|
+
];
|
|
418
|
+
for (const pattern of aliasPatterns) {
|
|
419
|
+
let match;
|
|
420
|
+
while ((match = pattern.exec(message)) !== null) {
|
|
421
|
+
const alias = match[1];
|
|
422
|
+
if (alias && !aliases.has(alias)) {
|
|
423
|
+
// Extract the example path
|
|
424
|
+
const escapedAlias = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
425
|
+
const fullPathMatch = message.match(new RegExp(`['"]?${escapedAlias}[/\\\\][^'"\\s]+`));
|
|
426
|
+
aliases.set(alias, fullPathMatch ? fullPathMatch[0].replace(/['"`]/g, '') : `${alias}/...`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return Array.from(aliases.entries()).map(([alias, examplePath]) => ({ alias, examplePath }));
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Infer alias mapping by analyzing project structure
|
|
435
|
+
* Example: '@' -> 'src' (most common convention)
|
|
436
|
+
*/
|
|
437
|
+
inferAliasMapping(alias, examplePath) {
|
|
438
|
+
// Common alias mappings
|
|
439
|
+
const commonMappings = {
|
|
440
|
+
'@': ['src', 'source', 'app', 'lib'],
|
|
441
|
+
'~': ['src', 'source'],
|
|
442
|
+
'@app': ['src/app', 'app'],
|
|
443
|
+
'@components': ['src/components', 'components'],
|
|
444
|
+
'@utils': ['src/utils', 'utils'],
|
|
445
|
+
'@lib': ['src/lib', 'lib'],
|
|
446
|
+
};
|
|
447
|
+
// Check if we have a known mapping
|
|
448
|
+
if (commonMappings[alias]) {
|
|
449
|
+
const projectRoot = this.baseDir;
|
|
450
|
+
for (const possiblePath of commonMappings[alias]) {
|
|
451
|
+
const fullPath = path_1.default.join(projectRoot, possiblePath);
|
|
452
|
+
if (fs_1.default.existsSync(fullPath) && fs_1.default.statSync(fullPath).isDirectory()) {
|
|
453
|
+
return possiblePath;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// Try to infer from the example path
|
|
458
|
+
// If error is '@/hooks/useAuth', try to find where 'hooks' directory exists
|
|
459
|
+
const pathParts = examplePath.split(/[/\\]/);
|
|
460
|
+
if (pathParts.length >= 2) {
|
|
461
|
+
const firstPart = pathParts[1]; // e.g., 'hooks' from '@/hooks/useAuth'
|
|
462
|
+
const projectRoot = this.baseDir;
|
|
463
|
+
// Check common locations
|
|
464
|
+
const possibleLocations = [
|
|
465
|
+
path_1.default.join(projectRoot, 'src', firstPart),
|
|
466
|
+
path_1.default.join(projectRoot, firstPart),
|
|
467
|
+
path_1.default.join(path_1.default.dirname(projectRoot), 'src', firstPart),
|
|
468
|
+
];
|
|
469
|
+
for (const location of possibleLocations) {
|
|
470
|
+
if (fs_1.default.existsSync(location) && fs_1.default.statSync(location).isDirectory()) {
|
|
471
|
+
// Found the directory, infer the base path
|
|
472
|
+
const relativePath = path_1.default.relative(projectRoot, path_1.default.dirname(location));
|
|
473
|
+
return relativePath || 'src'; // Default to 'src' if at root
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// If we found 'hooks' in 'src/hooks', infer '@' -> 'src'
|
|
477
|
+
const srcHooks = path_1.default.join(projectRoot, 'src', firstPart);
|
|
478
|
+
if (fs_1.default.existsSync(srcHooks)) {
|
|
479
|
+
return 'src';
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Default inference: '@' usually maps to 'src'
|
|
483
|
+
if (alias === '@') {
|
|
484
|
+
const srcPath = path_1.default.join(this.baseDir, 'src');
|
|
485
|
+
if (fs_1.default.existsSync(srcPath) && fs_1.default.statSync(srcPath).isDirectory()) {
|
|
486
|
+
return 'src';
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
294
491
|
/**
|
|
295
492
|
* Extract TypeScript path aliases from tsconfig.json
|
|
296
493
|
* Note: Does not handle "extends" - only reads the direct tsconfig.json file
|
|
@@ -321,6 +518,12 @@ class Packlyze {
|
|
|
321
518
|
this.log(`Warning: tsconfig.json found but compilerOptions is missing`);
|
|
322
519
|
continue;
|
|
323
520
|
}
|
|
521
|
+
// Check for moduleResolution: "bundler" which doesn't work well with webpack
|
|
522
|
+
const moduleResolution = tsconfig.compilerOptions.moduleResolution;
|
|
523
|
+
if (moduleResolution === 'bundler') {
|
|
524
|
+
this.log(`Warning: moduleResolution: "bundler" is set in tsconfig.json. This works with Vite/esbuild but not webpack.`);
|
|
525
|
+
this.log(`Consider using "node" or "node16" for webpack compatibility.`);
|
|
526
|
+
}
|
|
324
527
|
const paths = tsconfig.compilerOptions.paths;
|
|
325
528
|
if (paths && typeof paths === 'object') {
|
|
326
529
|
this.log(`Found compilerOptions.paths in tsconfig.json`);
|
|
@@ -513,6 +716,9 @@ class Packlyze {
|
|
|
513
716
|
})
|
|
514
717
|
.join(',\n');
|
|
515
718
|
aliasConfig = `,\n alias: {\n${aliasEntries}\n }`;
|
|
719
|
+
// Add note about tsconfig-paths-webpack-plugin as alternative
|
|
720
|
+
this.log(`Note: Using webpack resolve.alias for path aliases.`);
|
|
721
|
+
this.log(`Alternative: You can use tsconfig-paths-webpack-plugin for automatic sync with tsconfig.json`);
|
|
516
722
|
}
|
|
517
723
|
let config = '';
|
|
518
724
|
if (isESModule) {
|
|
@@ -528,21 +734,31 @@ module.exports = {
|
|
|
528
734
|
},
|
|
529
735
|
|
|
530
736
|
resolve: {
|
|
531
|
-
extensions: ['.js', '.jsx'${useTypeScript ? ", '.ts', '.tsx'" : ''}]${aliasConfig},
|
|
737
|
+
extensions: ['.js', '.jsx'${useTypeScript ? ", '.ts', '.tsx'" : ''}, '.json']${aliasConfig},
|
|
738
|
+
modules: ['node_modules', path.resolve(__dirname, 'src')],
|
|
532
739
|
},
|
|
533
740
|
|
|
534
741
|
module: {
|
|
535
742
|
rules: [
|
|
536
743
|
`;
|
|
537
744
|
if (useTypeScript) {
|
|
745
|
+
// Configure ts-loader to work better with path aliases
|
|
746
|
+
// Note: transpileOnly: true speeds up builds but webpack still needs resolve.alias for path resolution
|
|
747
|
+
const tsLoaderOptions = hasAliases
|
|
748
|
+
? `{
|
|
749
|
+
transpileOnly: true,
|
|
750
|
+
// Path aliases are handled by webpack resolve.alias above
|
|
751
|
+
// TypeScript type checking happens separately (e.g., via tsc --noEmit)
|
|
752
|
+
}`
|
|
753
|
+
: `{
|
|
754
|
+
transpileOnly: true
|
|
755
|
+
}`;
|
|
538
756
|
config += ` {
|
|
539
757
|
test: /\\.(ts|tsx)$/,
|
|
540
758
|
exclude: /node_modules/,
|
|
541
759
|
use: {
|
|
542
760
|
loader: 'ts-loader',
|
|
543
|
-
options: {
|
|
544
|
-
transpileOnly: true
|
|
545
|
-
}
|
|
761
|
+
options: ${tsLoaderOptions}
|
|
546
762
|
}
|
|
547
763
|
},
|
|
548
764
|
`;
|
|
@@ -563,6 +779,10 @@ module.exports = {
|
|
|
563
779
|
config += ` {
|
|
564
780
|
test: /\\.(png|jpg|jpeg|gif|svg)$/,
|
|
565
781
|
type: 'asset/resource'
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
test: /\\.css$/,
|
|
785
|
+
use: ['style-loader', 'css-loader']
|
|
566
786
|
}
|
|
567
787
|
]
|
|
568
788
|
},
|
|
@@ -593,21 +813,31 @@ module.exports = {
|
|
|
593
813
|
},
|
|
594
814
|
|
|
595
815
|
resolve: {
|
|
596
|
-
extensions: ['.js', '.jsx'${useTypeScript ? ", '.ts', '.tsx'" : ''}]${aliasConfig},
|
|
816
|
+
extensions: ['.js', '.jsx'${useTypeScript ? ", '.ts', '.tsx'" : ''}, '.json']${aliasConfig},
|
|
817
|
+
modules: ['node_modules', path.resolve(__dirname, 'src')],
|
|
597
818
|
},
|
|
598
819
|
|
|
599
820
|
module: {
|
|
600
821
|
rules: [
|
|
601
822
|
`;
|
|
602
823
|
if (useTypeScript) {
|
|
824
|
+
// Configure ts-loader to work better with path aliases
|
|
825
|
+
// Note: transpileOnly: true speeds up builds but webpack still needs resolve.alias for path resolution
|
|
826
|
+
const tsLoaderOptions = hasAliases
|
|
827
|
+
? `{
|
|
828
|
+
transpileOnly: true,
|
|
829
|
+
// Path aliases are handled by webpack resolve.alias above
|
|
830
|
+
// TypeScript type checking happens separately (e.g., via tsc --noEmit)
|
|
831
|
+
}`
|
|
832
|
+
: `{
|
|
833
|
+
transpileOnly: true
|
|
834
|
+
}`;
|
|
603
835
|
config += ` {
|
|
604
836
|
test: /\\.(ts|tsx)$/,
|
|
605
837
|
exclude: /node_modules/,
|
|
606
838
|
use: {
|
|
607
839
|
loader: 'ts-loader',
|
|
608
|
-
options: {
|
|
609
|
-
transpileOnly: true
|
|
610
|
-
}
|
|
840
|
+
options: ${tsLoaderOptions}
|
|
611
841
|
}
|
|
612
842
|
},
|
|
613
843
|
`;
|
|
@@ -628,6 +858,10 @@ module.exports = {
|
|
|
628
858
|
config += ` {
|
|
629
859
|
test: /\\.(png|jpg|jpeg|gif|svg)$/,
|
|
630
860
|
type: 'asset/resource'
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
test: /\\.css$/,
|
|
864
|
+
use: ['style-loader', 'css-loader']
|
|
631
865
|
}
|
|
632
866
|
]
|
|
633
867
|
},
|
|
@@ -1039,25 +1273,162 @@ module.exports = {
|
|
|
1039
1273
|
}
|
|
1040
1274
|
}
|
|
1041
1275
|
else {
|
|
1042
|
-
//
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
`
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1276
|
+
// No aliases in tsconfig.json, but we have path alias errors
|
|
1277
|
+
// Try to infer aliases from error messages and auto-fix
|
|
1278
|
+
const inferredAliases = this.extractAliasFromErrors(pathAliasErrors);
|
|
1279
|
+
if (inferredAliases.length > 0) {
|
|
1280
|
+
this.log(`Detected ${inferredAliases.length} alias pattern(s) from error messages: ${inferredAliases.map(a => a.alias).join(', ')}`);
|
|
1281
|
+
// Try to infer mappings and add them to webpack config
|
|
1282
|
+
const aliasMappings = {};
|
|
1283
|
+
for (const { alias, examplePath } of inferredAliases) {
|
|
1284
|
+
const inferredPath = this.inferAliasMapping(alias, examplePath);
|
|
1285
|
+
if (inferredPath) {
|
|
1286
|
+
const projectRoot = this.baseDir;
|
|
1287
|
+
const absolutePath = path_1.default.isAbsolute(inferredPath)
|
|
1288
|
+
? inferredPath
|
|
1289
|
+
: path_1.default.resolve(projectRoot, inferredPath);
|
|
1290
|
+
aliasMappings[alias] = absolutePath;
|
|
1291
|
+
this.log(`Inferred mapping: ${alias} -> ${inferredPath}`);
|
|
1292
|
+
}
|
|
1293
|
+
else {
|
|
1294
|
+
this.log(`Could not infer mapping for alias: ${alias}`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
// If we found mappings, try to add them to webpack config
|
|
1298
|
+
if (Object.keys(aliasMappings).length > 0 && configCheck.exists) {
|
|
1299
|
+
try {
|
|
1300
|
+
// Read existing config
|
|
1301
|
+
const configPath = configCheck.path;
|
|
1302
|
+
let configContent = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
1303
|
+
// Check if alias section exists
|
|
1304
|
+
const hasAliasSection = configContent.includes('resolve:') &&
|
|
1305
|
+
configContent.includes('alias:');
|
|
1306
|
+
if (!hasAliasSection) {
|
|
1307
|
+
// Add alias section to resolve
|
|
1308
|
+
const aliasEntries = Object.entries(aliasMappings)
|
|
1309
|
+
.map(([key, absolutePath]) => {
|
|
1310
|
+
const relativePath = path_1.default.relative(this.baseDir, absolutePath);
|
|
1311
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
1312
|
+
const escapedKey = key.replace(/'/g, "\\'");
|
|
1313
|
+
return ` '${escapedKey}': path.resolve(__dirname, '${normalizedPath}')`;
|
|
1314
|
+
})
|
|
1315
|
+
.join(',\n');
|
|
1316
|
+
// Try to insert alias into resolve section
|
|
1317
|
+
if (configContent.includes('resolve:')) {
|
|
1318
|
+
// Find resolve section and add alias
|
|
1319
|
+
const resolveMatch = configContent.match(/resolve:\s*\{[^}]*\}/s);
|
|
1320
|
+
if (resolveMatch) {
|
|
1321
|
+
const resolveSection = resolveMatch[0];
|
|
1322
|
+
if (!resolveSection.includes('alias:')) {
|
|
1323
|
+
// Add alias before closing brace
|
|
1324
|
+
const newResolveSection = resolveSection.replace(/\}\s*$/, `,\n alias: {\n${aliasEntries}\n }\n }`);
|
|
1325
|
+
configContent = configContent.replace(resolveSection, newResolveSection);
|
|
1326
|
+
fs_1.default.writeFileSync(configPath, configContent, 'utf-8');
|
|
1327
|
+
configRegenerated = true;
|
|
1328
|
+
pathAliasSuggestion = `\n\n✅ Auto-detected and added path aliases to ${configCheck.correctFileName}!\n` +
|
|
1329
|
+
` Detected aliases from error messages:\n` +
|
|
1330
|
+
Object.entries(aliasMappings).map(([key, mappedPath]) => {
|
|
1331
|
+
const relativePath = path_1.default.relative(this.baseDir, mappedPath);
|
|
1332
|
+
return ` - ${key} -> ${relativePath}`;
|
|
1333
|
+
}).join('\n') +
|
|
1334
|
+
`\n\n 💡 Next steps:\n` +
|
|
1335
|
+
` 1. Regenerate stats.json: npx webpack --profile --json stats.json\n` +
|
|
1336
|
+
` 2. Run packlyze analyze stats.json again\n`;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
catch (error) {
|
|
1343
|
+
this.log(`Could not auto-add aliases to config: ${error}`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
// If we couldn't auto-fix, provide helpful suggestions
|
|
1347
|
+
if (!configRegenerated) {
|
|
1348
|
+
const searchPaths = this.findProjectRoot();
|
|
1349
|
+
const searchedLocations = searchPaths.map(p => path_1.default.join(p, 'tsconfig.json')).join('\n - ');
|
|
1350
|
+
const aliasExamples = inferredAliases.map(a => ` - ${a.alias} (from: ${a.examplePath})`).join('\n');
|
|
1351
|
+
const suggestedMappings = Object.entries(aliasMappings).length > 0
|
|
1352
|
+
? `\n Inferred mappings:\n` +
|
|
1353
|
+
Object.entries(aliasMappings).map(([key, mappedPath]) => {
|
|
1354
|
+
const relativePath = path_1.default.relative(this.baseDir, mappedPath);
|
|
1355
|
+
return ` - ${key} -> ${relativePath}`;
|
|
1356
|
+
}).join('\n')
|
|
1357
|
+
: '';
|
|
1358
|
+
pathAliasSuggestion = `\n\n⚠️ Path alias errors detected, but no path aliases found in tsconfig.json.\n\n` +
|
|
1359
|
+
` Detected alias patterns from errors:\n${aliasExamples}\n` +
|
|
1360
|
+
(suggestedMappings || '') +
|
|
1361
|
+
`\n Searched for tsconfig.json in:\n` +
|
|
1362
|
+
` - ${searchedLocations}\n\n` +
|
|
1363
|
+
(Object.keys(aliasMappings).length > 0 ?
|
|
1364
|
+
` 💡 Quick fix - Add to your ${configCheck.correctFileName}:\n` +
|
|
1365
|
+
` resolve: {\n` +
|
|
1366
|
+
` alias: {\n` +
|
|
1367
|
+
Object.entries(aliasMappings).map(([key, mappedPath]) => {
|
|
1368
|
+
const relativePath = path_1.default.relative(this.baseDir, mappedPath);
|
|
1369
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
1370
|
+
return ` '${key}': path.resolve(__dirname, '${normalizedPath}')`;
|
|
1371
|
+
}).join(',\n') +
|
|
1372
|
+
`\n }\n` +
|
|
1373
|
+
` }\n\n` +
|
|
1374
|
+
` Or add to tsconfig.json:\n` +
|
|
1375
|
+
` {\n` +
|
|
1376
|
+
` "compilerOptions": {\n` +
|
|
1377
|
+
` "baseUrl": ".",\n` +
|
|
1378
|
+
` "moduleResolution": "node",\n` +
|
|
1379
|
+
` "paths": {\n` +
|
|
1380
|
+
Object.entries(aliasMappings).map(([key, mappedPath]) => {
|
|
1381
|
+
const relativePath = path_1.default.relative(this.baseDir, mappedPath);
|
|
1382
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
1383
|
+
return ` "${key}/*": ["${normalizedPath}/*"]`;
|
|
1384
|
+
}).join(',\n') +
|
|
1385
|
+
`\n }\n` +
|
|
1386
|
+
` }\n` +
|
|
1387
|
+
` }\n` :
|
|
1388
|
+
` Make sure:\n` +
|
|
1389
|
+
` 1. tsconfig.json exists in one of the locations above\n` +
|
|
1390
|
+
` 2. tsconfig.json has compilerOptions.paths configured:\n` +
|
|
1391
|
+
` {\n` +
|
|
1392
|
+
` "compilerOptions": {\n` +
|
|
1393
|
+
` "baseUrl": ".",\n` +
|
|
1394
|
+
` "moduleResolution": "node",\n` +
|
|
1395
|
+
` "paths": {\n` +
|
|
1396
|
+
` "@/*": ["src/*"]\n` +
|
|
1397
|
+
` }\n` +
|
|
1398
|
+
` }\n` +
|
|
1399
|
+
` }\n` +
|
|
1400
|
+
` 3. If using "extends", path aliases must be in the main tsconfig.json (not just in extended config)\n` +
|
|
1401
|
+
` 4. If using moduleResolution: "bundler", change it to "node" or "node16" for webpack compatibility\n` +
|
|
1402
|
+
` 5. Run with --verbose to see detailed search logs\n\n` +
|
|
1403
|
+
` 💡 Alternative: Install tsconfig-paths-webpack-plugin for automatic path alias resolution:\n` +
|
|
1404
|
+
` npm install --save-dev tsconfig-paths-webpack-plugin\n`);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
else {
|
|
1408
|
+
// Could not extract aliases from errors
|
|
1409
|
+
const searchPaths = this.findProjectRoot();
|
|
1410
|
+
const searchedLocations = searchPaths.map(p => path_1.default.join(p, 'tsconfig.json')).join('\n - ');
|
|
1411
|
+
pathAliasSuggestion = `\n\n⚠️ Path alias errors detected, but no path aliases found in tsconfig.json.\n\n` +
|
|
1412
|
+
` Searched for tsconfig.json in:\n` +
|
|
1413
|
+
` - ${searchedLocations}\n\n` +
|
|
1414
|
+
` Make sure:\n` +
|
|
1415
|
+
` 1. tsconfig.json exists in one of the locations above\n` +
|
|
1416
|
+
` 2. tsconfig.json has compilerOptions.paths configured:\n` +
|
|
1417
|
+
` {\n` +
|
|
1418
|
+
` "compilerOptions": {\n` +
|
|
1419
|
+
` "baseUrl": ".",\n` +
|
|
1420
|
+
` "moduleResolution": "node",\n` +
|
|
1421
|
+
` "paths": {\n` +
|
|
1422
|
+
` "@/*": ["src/*"]\n` +
|
|
1423
|
+
` }\n` +
|
|
1424
|
+
` }\n` +
|
|
1425
|
+
` }\n` +
|
|
1426
|
+
` 3. If using "extends", path aliases must be in the main tsconfig.json (not just in extended config)\n` +
|
|
1427
|
+
` 4. If using moduleResolution: "bundler", change it to "node" or "node16" for webpack compatibility\n` +
|
|
1428
|
+
` 5. Run with --verbose to see detailed search logs\n\n` +
|
|
1429
|
+
` 💡 Alternative: Install tsconfig-paths-webpack-plugin for automatic path alias resolution:\n` +
|
|
1430
|
+
` npm install --save-dev tsconfig-paths-webpack-plugin\n`;
|
|
1431
|
+
}
|
|
1061
1432
|
}
|
|
1062
1433
|
}
|
|
1063
1434
|
// Build the error message
|
|
@@ -1079,8 +1450,138 @@ module.exports = {
|
|
|
1079
1450
|
if (!configRegenerated && pathAliasSuggestion) {
|
|
1080
1451
|
errorMessage += pathAliasSuggestion;
|
|
1081
1452
|
}
|
|
1453
|
+
// Detect and handle other common issues
|
|
1454
|
+
let otherIssues = '';
|
|
1455
|
+
// 1. Missing extensions in resolve.extensions
|
|
1456
|
+
const missingExtensions = this.detectMissingExtensions(errors);
|
|
1457
|
+
if (missingExtensions.length > 0) {
|
|
1458
|
+
const configCheck = this.checkWebpackConfig();
|
|
1459
|
+
if (configCheck.exists && configCheck.path) {
|
|
1460
|
+
try {
|
|
1461
|
+
let configContent = fs_1.default.readFileSync(configCheck.path, 'utf-8');
|
|
1462
|
+
// Check if extensions are already in config
|
|
1463
|
+
const hasExtensions = configContent.includes('extensions:');
|
|
1464
|
+
if (hasExtensions) {
|
|
1465
|
+
// Try to add missing extensions
|
|
1466
|
+
const extensionsMatch = configContent.match(/extensions:\s*\[([^\]]+)\]/);
|
|
1467
|
+
if (extensionsMatch) {
|
|
1468
|
+
const existingExtensions = extensionsMatch[1];
|
|
1469
|
+
const newExtensions = missingExtensions
|
|
1470
|
+
.filter(ext => !existingExtensions.includes(ext))
|
|
1471
|
+
.map(ext => `'${ext}'`)
|
|
1472
|
+
.join(', ');
|
|
1473
|
+
if (newExtensions) {
|
|
1474
|
+
const newExtensionsList = existingExtensions.trim()
|
|
1475
|
+
? `${existingExtensions}, ${newExtensions}`
|
|
1476
|
+
: newExtensions;
|
|
1477
|
+
configContent = configContent.replace(/extensions:\s*\[([^\]]+)\]/, `extensions: [${newExtensionsList}]`);
|
|
1478
|
+
fs_1.default.writeFileSync(configCheck.path, configContent, 'utf-8');
|
|
1479
|
+
otherIssues += `\n\n✅ Auto-added missing extensions to resolve.extensions: ${missingExtensions.join(', ')}\n`;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
else {
|
|
1484
|
+
// Add extensions section
|
|
1485
|
+
if (configContent.includes('resolve:')) {
|
|
1486
|
+
const resolveMatch = configContent.match(/resolve:\s*\{[^}]*\}/s);
|
|
1487
|
+
if (resolveMatch) {
|
|
1488
|
+
const extensionsList = missingExtensions.map(ext => `'${ext}'`).join(', ');
|
|
1489
|
+
const newResolveSection = resolveMatch[0].replace(/\}\s*$/, `,\n extensions: ['.js', '.jsx', '.ts', '.tsx', ${extensionsList}]\n }`);
|
|
1490
|
+
configContent = configContent.replace(resolveMatch[0], newResolveSection);
|
|
1491
|
+
fs_1.default.writeFileSync(configCheck.path, configContent, 'utf-8');
|
|
1492
|
+
otherIssues += `\n\n✅ Auto-added resolve.extensions: ${missingExtensions.join(', ')}\n`;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
catch (error) {
|
|
1498
|
+
otherIssues += `\n\n⚠️ Missing file extensions in resolve.extensions: ${missingExtensions.join(', ')}\n` +
|
|
1499
|
+
` Add to your webpack config:\n` +
|
|
1500
|
+
` resolve: {\n` +
|
|
1501
|
+
` extensions: ['.js', '.jsx', '.ts', '.tsx', ${missingExtensions.map(e => `'${e}'`).join(', ')}]\n` +
|
|
1502
|
+
` }\n`;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
else {
|
|
1506
|
+
otherIssues += `\n\n⚠️ Missing file extensions in resolve.extensions: ${missingExtensions.join(', ')}\n` +
|
|
1507
|
+
` Add these extensions to your webpack config's resolve.extensions array.\n`;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
// 2. Missing CSS/image loaders
|
|
1511
|
+
const missingLoaders = this.detectMissingAssetLoaders(errors);
|
|
1512
|
+
if (missingLoaders.css || missingLoaders.images) {
|
|
1513
|
+
const missingPackages = [];
|
|
1514
|
+
if (missingLoaders.css) {
|
|
1515
|
+
missingPackages.push('css-loader', 'style-loader');
|
|
1516
|
+
}
|
|
1517
|
+
if (missingLoaders.images) {
|
|
1518
|
+
// Webpack 5 uses asset/resource, no loader needed, but we should suggest it
|
|
1519
|
+
}
|
|
1520
|
+
if (missingPackages.length > 0) {
|
|
1521
|
+
const dependencyCheck = this.checkPackagesInstalled(missingPackages);
|
|
1522
|
+
if (dependencyCheck && dependencyCheck.missing.length > 0) {
|
|
1523
|
+
const installCommand = this.generateInstallCommand(dependencyCheck.missing);
|
|
1524
|
+
otherIssues += `\n\n⚠️ Missing CSS/Asset loaders detected!\n` +
|
|
1525
|
+
` The following packages are required but not installed:\n` +
|
|
1526
|
+
` ${dependencyCheck.missing.map(p => ` - ${p}`).join('\n')}\n\n` +
|
|
1527
|
+
` 💡 Install them with:\n` +
|
|
1528
|
+
` ${installCommand}\n\n` +
|
|
1529
|
+
` Then add to your webpack config:\n` +
|
|
1530
|
+
(missingLoaders.css ?
|
|
1531
|
+
` module: {\n` +
|
|
1532
|
+
` rules: [\n` +
|
|
1533
|
+
` { test: /\\.css$/, use: ['style-loader', 'css-loader'] }\n` +
|
|
1534
|
+
` ]\n` +
|
|
1535
|
+
` }\n` : '');
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
// 3. baseUrl issues
|
|
1540
|
+
const hasBaseUrlIssue = this.detectBaseUrlIssues(errors);
|
|
1541
|
+
if (hasBaseUrlIssue) {
|
|
1542
|
+
const tsconfigPath = this.findProjectRoot().map(p => path_1.default.join(p, 'tsconfig.json')).find(p => fs_1.default.existsSync(p));
|
|
1543
|
+
if (tsconfigPath) {
|
|
1544
|
+
try {
|
|
1545
|
+
const tsconfigContent = fs_1.default.readFileSync(tsconfigPath, 'utf-8');
|
|
1546
|
+
const cleanedContent = tsconfigContent.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
|
|
1547
|
+
const tsconfig = JSON.parse(cleanedContent);
|
|
1548
|
+
if (tsconfig.compilerOptions?.baseUrl && !tsconfig.compilerOptions?.paths) {
|
|
1549
|
+
otherIssues += `\n\n⚠️ baseUrl is set in tsconfig.json but paths are not configured.\n` +
|
|
1550
|
+
` TypeScript can resolve imports like 'src/utils', but webpack cannot.\n\n` +
|
|
1551
|
+
` 💡 Solution 1: Add resolve.modules to webpack config:\n` +
|
|
1552
|
+
` resolve: {\n` +
|
|
1553
|
+
` modules: ['node_modules', '${tsconfig.compilerOptions.baseUrl}']\n` +
|
|
1554
|
+
` }\n\n` +
|
|
1555
|
+
` 💡 Solution 2: Add paths to tsconfig.json:\n` +
|
|
1556
|
+
` "compilerOptions": {\n` +
|
|
1557
|
+
` "baseUrl": ".",\n` +
|
|
1558
|
+
` "paths": {\n` +
|
|
1559
|
+
` "src/*": ["src/*"],\n` +
|
|
1560
|
+
` "app/*": ["app/*"]\n` +
|
|
1561
|
+
` }\n` +
|
|
1562
|
+
` }\n`;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
catch (error) {
|
|
1566
|
+
// Ignore parse errors
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
// 4. Case-sensitivity issues
|
|
1571
|
+
const caseIssues = this.detectCaseSensitivityIssues(errors);
|
|
1572
|
+
if (caseIssues.length > 0) {
|
|
1573
|
+
otherIssues += `\n\n⚠️ Case-sensitivity issues detected!\n` +
|
|
1574
|
+
` Your imports use different casing than the actual files:\n` +
|
|
1575
|
+
caseIssues.map(issue => ` - Imported: ${issue.expected}\n Actual: ${issue.actual}`).join('\n') +
|
|
1576
|
+
`\n\n 💡 Fix: Update your imports to match the exact file casing.\n` +
|
|
1577
|
+
` This is especially important for Linux/CI environments where file systems are case-sensitive.\n`;
|
|
1578
|
+
}
|
|
1579
|
+
// Add other issues to error message
|
|
1580
|
+
if (otherIssues) {
|
|
1581
|
+
errorMessage += otherIssues;
|
|
1582
|
+
}
|
|
1082
1583
|
// Add default suggestion if no other suggestions
|
|
1083
|
-
if (!suggestion && !pathAliasSuggestion && !dependencySuggestion) {
|
|
1584
|
+
if (!suggestion && !pathAliasSuggestion && !dependencySuggestion && !otherIssues) {
|
|
1084
1585
|
errorMessage += `\n💡 Fix the webpack build errors first, then regenerate stats.json:\n` +
|
|
1085
1586
|
` npx webpack --profile --json stats.json`;
|
|
1086
1587
|
}
|