shakapacker 9.3.0.beta.5 → 9.3.0.beta.7
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -3
- data/ESLINT_TECHNICAL_DEBT.md +8 -2
- data/Gemfile.lock +1 -1
- data/docs/early_hints.md +7 -21
- data/docs/{early_hints_new_api.md → early_hints_manual_api.md} +53 -299
- data/lib/shakapacker/doctor.rb +125 -68
- data/lib/shakapacker/runner.rb +12 -8
- data/lib/shakapacker/swc_migrator.rb +7 -7
- data/lib/shakapacker/version.rb +1 -1
- data/package/configExporter/cli.ts +256 -82
- data/package/configExporter/fileWriter.ts +18 -9
- data/package/configExporter/types.ts +6 -1
- data/package/configExporter/yamlSerializer.ts +36 -0
- data/package/plugins/webpack.ts +2 -1
- data/package/utils/errorCodes.ts +1 -0
- data/package/utils/errorHelpers.ts +16 -12
- data/package.json +2 -2
- data/test/configExporter/integration.test.js +8 -8
- data/test/package/configExporter.test.js +48 -3
- metadata +3 -4
- data/package-lock.json +0 -13047
|
@@ -351,7 +351,7 @@ QUICK START (for troubleshooting):
|
|
|
351
351
|
|
|
352
352
|
# Advanced output options
|
|
353
353
|
bin/shakapacker-config --build=dev --stdout # View in terminal
|
|
354
|
-
bin/shakapacker-config --build=dev --output=config.
|
|
354
|
+
bin/shakapacker-config --build=dev --output=config.yml # Save to specific file`
|
|
355
355
|
)
|
|
356
356
|
.strict()
|
|
357
357
|
.parseSync()
|
|
@@ -566,7 +566,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
-
console.log(
|
|
569
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
570
570
|
console.log("🔍 Validating Builds")
|
|
571
571
|
console.log("=".repeat(80))
|
|
572
572
|
console.log(`\nValidating ${buildsToValidate.length} build(s)...\n`)
|
|
@@ -577,7 +577,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
577
577
|
" This includes all webpack/rspack compilation logs, warnings, and progress messages"
|
|
578
578
|
)
|
|
579
579
|
console.log(" Use without --verbose to see only errors and summaries\n")
|
|
580
|
-
console.log("=".repeat(80)
|
|
580
|
+
console.log(`${"=".repeat(80)}\n`)
|
|
581
581
|
}
|
|
582
582
|
|
|
583
583
|
const results = []
|
|
@@ -585,7 +585,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
585
585
|
// Validate each build
|
|
586
586
|
for (const buildName of buildsToValidate) {
|
|
587
587
|
if (options.verbose) {
|
|
588
|
-
console.log(
|
|
588
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
589
589
|
console.log(`📦 VALIDATING BUILD: ${buildName}`)
|
|
590
590
|
console.log("=".repeat(80))
|
|
591
591
|
} else {
|
|
@@ -596,6 +596,9 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
596
596
|
clearBuildEnvironmentVariables()
|
|
597
597
|
restoreBuildEnvironmentVariables(savedEnv)
|
|
598
598
|
|
|
599
|
+
// Clear shakapacker config cache between builds
|
|
600
|
+
shakapackerConfigCache = null
|
|
601
|
+
|
|
599
602
|
// Get the build's environment to use for auto-detection
|
|
600
603
|
const buildConfig = config.builds[buildName]
|
|
601
604
|
const buildEnv =
|
|
@@ -608,7 +611,11 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
608
611
|
"development"
|
|
609
612
|
|
|
610
613
|
// Auto-detect bundler using the build's environment
|
|
611
|
-
const defaultBundler = await autoDetectBundler(
|
|
614
|
+
const defaultBundler = await autoDetectBundler(
|
|
615
|
+
buildEnv,
|
|
616
|
+
appRoot,
|
|
617
|
+
options.verbose
|
|
618
|
+
)
|
|
612
619
|
|
|
613
620
|
// Resolve build config with the correct default bundler
|
|
614
621
|
const resolvedBuild = loader.resolveBuild(
|
|
@@ -692,6 +699,9 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
|
|
|
692
699
|
clearBuildEnvironmentVariables()
|
|
693
700
|
restoreBuildEnvironmentVariables(savedEnv)
|
|
694
701
|
|
|
702
|
+
// Clear shakapacker config cache between builds
|
|
703
|
+
shakapackerConfigCache = null
|
|
704
|
+
|
|
695
705
|
// Create a modified options object for this build
|
|
696
706
|
const buildOptions = { ...resolvedOptions, build: buildName }
|
|
697
707
|
const configs = await loadConfigsForEnv(undefined, buildOptions, appRoot)
|
|
@@ -713,7 +723,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
|
|
|
713
723
|
}
|
|
714
724
|
|
|
715
725
|
// Print summary
|
|
716
|
-
console.log(
|
|
726
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
717
727
|
console.log("✅ All Builds Exported!")
|
|
718
728
|
console.log("=".repeat(80))
|
|
719
729
|
console.log(`\nCreated ${createdFiles.length} configuration file(s) in:`)
|
|
@@ -722,7 +732,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
|
|
|
722
732
|
createdFiles.forEach((file) => {
|
|
723
733
|
console.log(` ✓ ${basename(file)}`)
|
|
724
734
|
})
|
|
725
|
-
console.log(
|
|
735
|
+
console.log(`\n${"=".repeat(80)}\n`)
|
|
726
736
|
|
|
727
737
|
return 0
|
|
728
738
|
} catch (error: unknown) {
|
|
@@ -743,7 +753,7 @@ async function runDoctorMode(
|
|
|
743
753
|
const savedEnv = saveBuildEnvironmentVariables()
|
|
744
754
|
|
|
745
755
|
try {
|
|
746
|
-
console.log(
|
|
756
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
747
757
|
console.log("🔍 Config Exporter - Doctor Mode")
|
|
748
758
|
console.log("=".repeat(80))
|
|
749
759
|
|
|
@@ -770,6 +780,9 @@ async function runDoctorMode(
|
|
|
770
780
|
clearBuildEnvironmentVariables()
|
|
771
781
|
restoreBuildEnvironmentVariables(savedEnv)
|
|
772
782
|
|
|
783
|
+
// Clear shakapacker config cache between builds
|
|
784
|
+
shakapackerConfigCache = null
|
|
785
|
+
|
|
773
786
|
const configs = await loadConfigsForEnv(
|
|
774
787
|
undefined,
|
|
775
788
|
{ ...options, build: buildName },
|
|
@@ -798,9 +811,13 @@ async function runDoctorMode(
|
|
|
798
811
|
// If config file exists but is invalid, show error and exit
|
|
799
812
|
const errorMessage =
|
|
800
813
|
error instanceof Error ? error.message : String(error)
|
|
801
|
-
console.error(`\n❌
|
|
814
|
+
console.error(`\n❌ Error loading build configuration:`)
|
|
815
|
+
console.error(`\n${errorMessage}`)
|
|
816
|
+
console.error(
|
|
817
|
+
`\n💡 To fix this issue, check your build config in ${configFilePath}`
|
|
818
|
+
)
|
|
802
819
|
console.error(
|
|
803
|
-
`
|
|
820
|
+
` or run: bin/shakapacker-config --init to regenerate it.\n`
|
|
804
821
|
)
|
|
805
822
|
throw error
|
|
806
823
|
}
|
|
@@ -825,6 +842,9 @@ async function runDoctorMode(
|
|
|
825
842
|
clearBuildEnvironmentVariables()
|
|
826
843
|
restoreBuildEnvironmentVariables(savedEnv)
|
|
827
844
|
|
|
845
|
+
// Clear shakapacker config cache between builds
|
|
846
|
+
shakapackerConfigCache = null
|
|
847
|
+
|
|
828
848
|
// Set WEBPACK_SERVE for HMR config
|
|
829
849
|
if (hmr) {
|
|
830
850
|
process.env.WEBPACK_SERVE = "true"
|
|
@@ -883,7 +903,7 @@ async function runDoctorMode(
|
|
|
883
903
|
|
|
884
904
|
function printDoctorSummary(createdFiles: string[], targetDir: string): void {
|
|
885
905
|
// Print summary
|
|
886
|
-
console.log(
|
|
906
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
887
907
|
console.log("✅ Export Complete!")
|
|
888
908
|
console.log("=".repeat(80))
|
|
889
909
|
console.log(`\nCreated ${createdFiles.length} configuration file(s) in:`)
|
|
@@ -906,14 +926,14 @@ function printDoctorSummary(createdFiles: string[], targetDir: string): void {
|
|
|
906
926
|
}
|
|
907
927
|
|
|
908
928
|
if (shouldSuggestGitignore) {
|
|
909
|
-
console.log(
|
|
929
|
+
console.log(`\n${"─".repeat(80)}`)
|
|
910
930
|
console.log(
|
|
911
931
|
"💡 Tip: Add the export directory to .gitignore to avoid committing config files:"
|
|
912
932
|
)
|
|
913
933
|
console.log(`\n echo "${dirName}/" >> .gitignore\n`)
|
|
914
934
|
}
|
|
915
935
|
|
|
916
|
-
console.log(
|
|
936
|
+
console.log(`\n${"=".repeat(80)}\n`)
|
|
917
937
|
}
|
|
918
938
|
|
|
919
939
|
async function runSaveMode(
|
|
@@ -930,7 +950,7 @@ async function runSaveMode(
|
|
|
930
950
|
if (options.output) {
|
|
931
951
|
// Single file output
|
|
932
952
|
const combined = configs.map((c) => c.config)
|
|
933
|
-
const metadata = configs[0]
|
|
953
|
+
const { metadata } = configs[0]
|
|
934
954
|
metadata.configCount = combined.length
|
|
935
955
|
|
|
936
956
|
const output = formatConfig(
|
|
@@ -970,15 +990,15 @@ async function runStdoutMode(
|
|
|
970
990
|
options: ExportOptions,
|
|
971
991
|
appRoot: string
|
|
972
992
|
): Promise<void> {
|
|
973
|
-
const configs = await loadConfigsForEnv(options.env
|
|
993
|
+
const configs = await loadConfigsForEnv(options.env, options, appRoot)
|
|
974
994
|
const combined = configs.map((c) => c.config)
|
|
975
|
-
const metadata = configs[0]
|
|
995
|
+
const { metadata } = configs[0]
|
|
976
996
|
metadata.configCount = combined.length
|
|
977
997
|
|
|
978
998
|
const config = combined.length === 1 ? combined[0] : combined
|
|
979
999
|
const output = formatConfig(config, metadata, options, appRoot)
|
|
980
1000
|
|
|
981
|
-
console.log(
|
|
1001
|
+
console.log(`\n${"=".repeat(80)}\n`)
|
|
982
1002
|
console.log(output)
|
|
983
1003
|
}
|
|
984
1004
|
|
|
@@ -986,9 +1006,9 @@ async function runSingleFileMode(
|
|
|
986
1006
|
options: ExportOptions,
|
|
987
1007
|
appRoot: string
|
|
988
1008
|
): Promise<void> {
|
|
989
|
-
const configs = await loadConfigsForEnv(options.env
|
|
1009
|
+
const configs = await loadConfigsForEnv(options.env, options, appRoot)
|
|
990
1010
|
const combined = configs.map((c) => c.config)
|
|
991
|
-
const metadata = configs[0]
|
|
1011
|
+
const { metadata } = configs[0]
|
|
992
1012
|
metadata.configCount = combined.length
|
|
993
1013
|
|
|
994
1014
|
const config = combined.length === 1 ? combined[0] : combined
|
|
@@ -1015,7 +1035,11 @@ async function loadConfigsForEnv(
|
|
|
1015
1035
|
// Use a temporary env for auto-detection, will be overridden by build config
|
|
1016
1036
|
const tempEnv = env || "development"
|
|
1017
1037
|
const loader = new ConfigFileLoader(options.configFile)
|
|
1018
|
-
const defaultBundler = await autoDetectBundler(
|
|
1038
|
+
const defaultBundler = await autoDetectBundler(
|
|
1039
|
+
tempEnv,
|
|
1040
|
+
appRoot,
|
|
1041
|
+
options.verbose
|
|
1042
|
+
)
|
|
1019
1043
|
const resolvedBuild = loader.resolveBuild(
|
|
1020
1044
|
options.build,
|
|
1021
1045
|
options,
|
|
@@ -1039,6 +1063,12 @@ async function loadConfigsForEnv(
|
|
|
1039
1063
|
"DYLD_INSERT_LIBRARIES"
|
|
1040
1064
|
]
|
|
1041
1065
|
|
|
1066
|
+
if (options.verbose) {
|
|
1067
|
+
console.log(
|
|
1068
|
+
`[Config Exporter] Setting environment variables from build config...`
|
|
1069
|
+
)
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1042
1072
|
for (const [key, value] of Object.entries(resolvedBuild.environment)) {
|
|
1043
1073
|
if (DANGEROUS_ENV_VARS.includes(key)) {
|
|
1044
1074
|
console.warn(
|
|
@@ -1053,6 +1083,9 @@ async function loadConfigsForEnv(
|
|
|
1053
1083
|
)
|
|
1054
1084
|
continue
|
|
1055
1085
|
}
|
|
1086
|
+
if (options.verbose) {
|
|
1087
|
+
console.log(`[Config Exporter] ${key}=${value}`)
|
|
1088
|
+
}
|
|
1056
1089
|
process.env[key] = value
|
|
1057
1090
|
}
|
|
1058
1091
|
|
|
@@ -1080,18 +1113,36 @@ async function loadConfigsForEnv(
|
|
|
1080
1113
|
const railsEnv =
|
|
1081
1114
|
options.env || resolvedBuild.environment.RAILS_ENV || finalEnv
|
|
1082
1115
|
process.env.RAILS_ENV = railsEnv
|
|
1116
|
+
|
|
1117
|
+
// Auto-set CLIENT_BUNDLE_ONLY/SERVER_BUNDLE_ONLY from outputs if not already in environment
|
|
1118
|
+
// This allows webpack configs to return the correct number of bundles
|
|
1119
|
+
if (
|
|
1120
|
+
!resolvedBuild.environment.CLIENT_BUNDLE_ONLY &&
|
|
1121
|
+
!resolvedBuild.environment.SERVER_BUNDLE_ONLY
|
|
1122
|
+
) {
|
|
1123
|
+
if (buildOutputs.length === 1) {
|
|
1124
|
+
if (buildOutputs[0] === "client") {
|
|
1125
|
+
process.env.CLIENT_BUNDLE_ONLY = "yes"
|
|
1126
|
+
} else if (buildOutputs[0] === "server") {
|
|
1127
|
+
process.env.SERVER_BUNDLE_ONLY = "yes"
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1083
1131
|
} else {
|
|
1084
1132
|
// No build config - use CLI env or default
|
|
1085
1133
|
finalEnv = env || "development"
|
|
1086
1134
|
|
|
1087
1135
|
// Auto-detect bundler if not specified
|
|
1088
|
-
bundler =
|
|
1136
|
+
bundler =
|
|
1137
|
+
options.bundler ||
|
|
1138
|
+
(await autoDetectBundler(finalEnv, appRoot, options.verbose))
|
|
1089
1139
|
|
|
1090
1140
|
// Set environment variables
|
|
1091
1141
|
process.env.NODE_ENV = finalEnv
|
|
1092
1142
|
process.env.RAILS_ENV = finalEnv
|
|
1093
1143
|
}
|
|
1094
1144
|
|
|
1145
|
+
// Handle CLI flags for client/server only
|
|
1095
1146
|
if (options.clientOnly) {
|
|
1096
1147
|
process.env.CLIENT_BUNDLE_ONLY = "yes"
|
|
1097
1148
|
} else if (options.serverOnly) {
|
|
@@ -1100,9 +1151,10 @@ async function loadConfigsForEnv(
|
|
|
1100
1151
|
|
|
1101
1152
|
// Find and load config file
|
|
1102
1153
|
const configFile =
|
|
1103
|
-
customConfigFile ||
|
|
1154
|
+
customConfigFile ||
|
|
1155
|
+
findConfigFile(bundler, appRoot, finalEnv, options.verbose)
|
|
1104
1156
|
// Quiet mode for cleaner output - only show if verbose or errors
|
|
1105
|
-
if (
|
|
1157
|
+
if (options.verbose) {
|
|
1106
1158
|
console.log(`[Config Exporter] Loading config: ${configFile}`)
|
|
1107
1159
|
console.log(`[Config Exporter] Environment: ${finalEnv}`)
|
|
1108
1160
|
console.log(`[Config Exporter] Bundler: ${bundler}`)
|
|
@@ -1115,7 +1167,6 @@ async function loadConfigsForEnv(
|
|
|
1115
1167
|
// Register ts-node for TypeScript config files
|
|
1116
1168
|
if (configFile.endsWith(".ts")) {
|
|
1117
1169
|
try {
|
|
1118
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1119
1170
|
require("ts-node/register/transpile-only")
|
|
1120
1171
|
} catch (error) {
|
|
1121
1172
|
throw new Error(
|
|
@@ -1164,8 +1215,19 @@ async function loadConfigsForEnv(
|
|
|
1164
1215
|
}
|
|
1165
1216
|
})
|
|
1166
1217
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1218
|
+
let loadedConfig: any
|
|
1219
|
+
try {
|
|
1220
|
+
loadedConfig = require(configFile)
|
|
1221
|
+
} catch (error: unknown) {
|
|
1222
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
1223
|
+
throw new Error(
|
|
1224
|
+
`Failed to load webpack/rspack config file.\n\n` +
|
|
1225
|
+
`Config file: ${configFile}\n` +
|
|
1226
|
+
`Build: ${buildName || "default"}\n` +
|
|
1227
|
+
`Error: ${errorMessage}\n\n` +
|
|
1228
|
+
`Tip: Check that the config file is valid and doesn't have syntax errors.`
|
|
1229
|
+
)
|
|
1230
|
+
}
|
|
1169
1231
|
|
|
1170
1232
|
// Handle ES module default export
|
|
1171
1233
|
if (typeof loadedConfig === "object" && "default" in loadedConfig) {
|
|
@@ -1198,10 +1260,41 @@ async function loadConfigsForEnv(
|
|
|
1198
1260
|
} catch (error: unknown) {
|
|
1199
1261
|
const errorMessage =
|
|
1200
1262
|
error instanceof Error ? error.message : String(error)
|
|
1263
|
+
|
|
1264
|
+
// Build detailed environment information for debugging
|
|
1265
|
+
const envDetails = [
|
|
1266
|
+
`Config file: ${configFile}`,
|
|
1267
|
+
`Build: ${buildName || "default"}`,
|
|
1268
|
+
``,
|
|
1269
|
+
`Current Environment Variables:`,
|
|
1270
|
+
` NODE_ENV: ${process.env.NODE_ENV || "(not set)"}`,
|
|
1271
|
+
` RAILS_ENV: ${process.env.RAILS_ENV || "(not set)"}`,
|
|
1272
|
+
` CLIENT_BUNDLE_ONLY: ${process.env.CLIENT_BUNDLE_ONLY || "(not set)"}`,
|
|
1273
|
+
` SERVER_BUNDLE_ONLY: ${process.env.SERVER_BUNDLE_ONLY || "(not set)"}`,
|
|
1274
|
+
` WEBPACK_SERVE: ${process.env.WEBPACK_SERVE || "(not set)"}`,
|
|
1275
|
+
``,
|
|
1276
|
+
`Bundler env args: ${JSON.stringify(envObject)}`,
|
|
1277
|
+
`Mode: ${finalEnv}`,
|
|
1278
|
+
``,
|
|
1279
|
+
`Error: ${errorMessage}`,
|
|
1280
|
+
``
|
|
1281
|
+
]
|
|
1282
|
+
|
|
1283
|
+
// Add suggestion based on common error patterns
|
|
1284
|
+
let suggestion = `Check your webpack/rspack config for errors. The config function threw an exception when called.`
|
|
1285
|
+
if (errorMessage.includes("NODE_ENV") && !process.env.NODE_ENV) {
|
|
1286
|
+
suggestion =
|
|
1287
|
+
`NODE_ENV is not set. ` +
|
|
1288
|
+
`Your build config should set NODE_ENV in the 'environment' section.\n` +
|
|
1289
|
+
`Example:\n` +
|
|
1290
|
+
` environment:\n` +
|
|
1291
|
+
` NODE_ENV: "development"`
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1201
1294
|
throw new Error(
|
|
1202
|
-
`Failed to execute config function: ${errorMessage}\n
|
|
1203
|
-
|
|
1204
|
-
|
|
1295
|
+
`Failed to execute config function: ${errorMessage}\n${envDetails.join(
|
|
1296
|
+
"\n"
|
|
1297
|
+
)}\nTip: ${suggestion}`
|
|
1205
1298
|
)
|
|
1206
1299
|
}
|
|
1207
1300
|
}
|
|
@@ -1212,29 +1305,81 @@ async function loadConfigsForEnv(
|
|
|
1212
1305
|
: [loadedConfig]
|
|
1213
1306
|
const results: Array<{ config: any; metadata: ConfigMetadata }> = []
|
|
1214
1307
|
|
|
1308
|
+
// Validate config count matches expected outputs
|
|
1309
|
+
if (buildOutputs.length > 0 && configs.length !== buildOutputs.length) {
|
|
1310
|
+
const errorLines = [
|
|
1311
|
+
`Webpack config returned ${configs.length} config(s) but outputs array specifies ${buildOutputs.length}.`,
|
|
1312
|
+
``,
|
|
1313
|
+
`Build: ${buildName || "default"}`,
|
|
1314
|
+
`Config file: ${configFile}`,
|
|
1315
|
+
`Expected outputs: [${buildOutputs.join(", ")}]`,
|
|
1316
|
+
`Actual configs returned: ${configs.length}`,
|
|
1317
|
+
``,
|
|
1318
|
+
`This mismatch means:`
|
|
1319
|
+
]
|
|
1320
|
+
|
|
1321
|
+
if (configs.length < buildOutputs.length) {
|
|
1322
|
+
errorLines.push(
|
|
1323
|
+
` - Your webpack config is returning FEWER configs than expected.`,
|
|
1324
|
+
` - Either update your webpack config to return ${buildOutputs.length} config(s),`,
|
|
1325
|
+
` - Or update the 'outputs' array in your build config to match what webpack returns.`
|
|
1326
|
+
)
|
|
1327
|
+
} else {
|
|
1328
|
+
errorLines.push(
|
|
1329
|
+
` - Your webpack config is returning MORE configs than expected.`,
|
|
1330
|
+
` - Either update the 'outputs' array to include all ${configs.length} outputs,`,
|
|
1331
|
+
` - Or update your webpack config to return only ${buildOutputs.length} config(s).`
|
|
1332
|
+
)
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
errorLines.push(
|
|
1336
|
+
``,
|
|
1337
|
+
`Example fix in build config:`,
|
|
1338
|
+
` outputs:`,
|
|
1339
|
+
...Array.from({ length: configs.length }, (_, i) =>
|
|
1340
|
+
i < buildOutputs.length
|
|
1341
|
+
? ` - ${buildOutputs[i]}`
|
|
1342
|
+
: ` - config-${i + 1} # Add a name for this config`
|
|
1343
|
+
)
|
|
1344
|
+
)
|
|
1345
|
+
|
|
1346
|
+
throw new Error(errorLines.join("\n"))
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Debug logging
|
|
1350
|
+
if (options.verbose || buildOutputs.length > 0) {
|
|
1351
|
+
console.log(
|
|
1352
|
+
`[Config Exporter] Webpack returned ${configs.length} config(s), buildOutputs: [${buildOutputs.join(", ")}]`
|
|
1353
|
+
)
|
|
1354
|
+
if (buildOutputs.length > 0 && configs.length === buildOutputs.length) {
|
|
1355
|
+
console.log(
|
|
1356
|
+
`[Config Exporter] ✓ Config count matches outputs array (${configs.length})`
|
|
1357
|
+
)
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1215
1361
|
configs.forEach((cfg, index) => {
|
|
1216
|
-
let configType:
|
|
1362
|
+
let configType: string = "all"
|
|
1217
1363
|
|
|
1218
1364
|
// Use outputs from build config if available
|
|
1219
|
-
if (
|
|
1220
|
-
|
|
1221
|
-
index
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
const outputValue = buildOutputs[index]
|
|
1225
|
-
// Validate the output value is a valid config type
|
|
1226
|
-
if (
|
|
1227
|
-
outputValue === "client" ||
|
|
1228
|
-
outputValue === "server" ||
|
|
1229
|
-
outputValue === "all"
|
|
1230
|
-
) {
|
|
1231
|
-
configType = outputValue
|
|
1232
|
-
} else {
|
|
1233
|
-
throw new Error(
|
|
1234
|
-
`Invalid output type '${outputValue}' at index ${index} in build '${buildName}'. ` +
|
|
1235
|
-
`Allowed values are: client, server, all`
|
|
1365
|
+
if (buildOutputs.length > 0) {
|
|
1366
|
+
// If outputs are specified, skip configs beyond the outputs array
|
|
1367
|
+
if (index >= buildOutputs.length) {
|
|
1368
|
+
console.log(
|
|
1369
|
+
`[Config Exporter] Skipping config[${index}] - beyond outputs array`
|
|
1236
1370
|
)
|
|
1371
|
+
return // Skip this config
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
const outputValue = buildOutputs[index]
|
|
1375
|
+
if (!outputValue || outputValue.trim() === "") {
|
|
1376
|
+
return // Skip null/undefined/empty string entries
|
|
1237
1377
|
}
|
|
1378
|
+
|
|
1379
|
+
// Accept any string as a valid output name
|
|
1380
|
+
// Built-in types: client, server, all, client-hmr
|
|
1381
|
+
// Custom types: client-modern, client-legacy, server-bundle, etc.
|
|
1382
|
+
configType = outputValue
|
|
1238
1383
|
} else if (configs.length === 2) {
|
|
1239
1384
|
// Likely client and server configs
|
|
1240
1385
|
configType = index === 0 ? "client" : "server"
|
|
@@ -1306,39 +1451,37 @@ function formatConfig(
|
|
|
1306
1451
|
return value
|
|
1307
1452
|
}
|
|
1308
1453
|
return JSON.stringify({ metadata, config }, jsonReplacer, 2)
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
let output =
|
|
1321
|
-
"=== METADATA ===\n\n" + inspect(metadata, inspectOptions) + "\n\n"
|
|
1322
|
-
output += "=== CONFIG ===\n\n"
|
|
1454
|
+
}
|
|
1455
|
+
// inspect format
|
|
1456
|
+
const inspectOptions = {
|
|
1457
|
+
depth: options.depth,
|
|
1458
|
+
colors: false,
|
|
1459
|
+
maxArrayLength: null,
|
|
1460
|
+
maxStringLength: null,
|
|
1461
|
+
breakLength: 120,
|
|
1462
|
+
compact: false
|
|
1463
|
+
}
|
|
1323
1464
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
config.forEach((cfg, index) => {
|
|
1327
|
-
output += `--- Config [${index}] ---\n\n`
|
|
1328
|
-
output += inspect(cfg, inspectOptions) + "\n\n"
|
|
1329
|
-
})
|
|
1330
|
-
} else {
|
|
1331
|
-
output += inspect(config, inspectOptions) + "\n"
|
|
1332
|
-
}
|
|
1465
|
+
let output = `=== METADATA ===\n\n${inspect(metadata, inspectOptions)}\n\n`
|
|
1466
|
+
output += "=== CONFIG ===\n\n"
|
|
1333
1467
|
|
|
1334
|
-
|
|
1468
|
+
if (Array.isArray(config)) {
|
|
1469
|
+
output += `Total configs: ${config.length}\n\n`
|
|
1470
|
+
config.forEach((cfg, index) => {
|
|
1471
|
+
output += `--- Config [${index}] ---\n\n`
|
|
1472
|
+
output += `${inspect(cfg, inspectOptions)}\n\n`
|
|
1473
|
+
})
|
|
1474
|
+
} else {
|
|
1475
|
+
output += `${inspect(config, inspectOptions)}\n`
|
|
1335
1476
|
}
|
|
1477
|
+
|
|
1478
|
+
return output
|
|
1336
1479
|
}
|
|
1337
1480
|
|
|
1338
1481
|
function cleanConfig(obj: any, rootPath: string): any {
|
|
1339
1482
|
const makePathRelative = (str: string): string => {
|
|
1340
1483
|
if (typeof str === "string" && str.startsWith(rootPath)) {
|
|
1341
|
-
return
|
|
1484
|
+
return `./${str.substring(rootPath.length + 1)}`
|
|
1342
1485
|
}
|
|
1343
1486
|
return str
|
|
1344
1487
|
}
|
|
@@ -1398,10 +1541,31 @@ function cleanConfig(obj: any, rootPath: string): any {
|
|
|
1398
1541
|
/**
|
|
1399
1542
|
* Loads and returns shakapacker.yml configuration
|
|
1400
1543
|
*/
|
|
1544
|
+
// Cache to avoid duplicate loading and logging
|
|
1545
|
+
let shakapackerConfigCache: {
|
|
1546
|
+
env: string
|
|
1547
|
+
result: { bundler: "webpack" | "rspack"; configPath: string }
|
|
1548
|
+
} | null = null
|
|
1549
|
+
|
|
1401
1550
|
function loadShakapackerConfig(
|
|
1402
1551
|
env: string,
|
|
1403
|
-
appRoot: string
|
|
1552
|
+
appRoot: string,
|
|
1553
|
+
verbose = false
|
|
1404
1554
|
): { bundler: "webpack" | "rspack"; configPath: string } {
|
|
1555
|
+
// Return cached result if same environment
|
|
1556
|
+
if (shakapackerConfigCache && shakapackerConfigCache.env === env) {
|
|
1557
|
+
if (verbose) {
|
|
1558
|
+
console.log(
|
|
1559
|
+
`[Config Exporter] Using cached bundler config for env: ${env}`
|
|
1560
|
+
)
|
|
1561
|
+
}
|
|
1562
|
+
return shakapackerConfigCache.result
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (verbose) {
|
|
1566
|
+
console.log(`[Config Exporter] Loading shakapacker config for env: ${env}`)
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1405
1569
|
try {
|
|
1406
1570
|
const configFilePath =
|
|
1407
1571
|
process.env.SHAKAPACKER_CONFIG ||
|
|
@@ -1417,10 +1581,12 @@ function loadShakapackerConfig(
|
|
|
1417
1581
|
console.warn(
|
|
1418
1582
|
`[Config Exporter] Invalid bundler '${bundler}' in shakapacker.yml, defaulting to webpack`
|
|
1419
1583
|
)
|
|
1420
|
-
|
|
1421
|
-
bundler: "webpack",
|
|
1584
|
+
const result = {
|
|
1585
|
+
bundler: "webpack" as const,
|
|
1422
1586
|
configPath: bundler === "rspack" ? "config/rspack" : "config/webpack"
|
|
1423
1587
|
}
|
|
1588
|
+
shakapackerConfigCache = { env, result }
|
|
1589
|
+
return result
|
|
1424
1590
|
}
|
|
1425
1591
|
|
|
1426
1592
|
// Get config path
|
|
@@ -1429,10 +1595,15 @@ function loadShakapackerConfig(
|
|
|
1429
1595
|
customConfigPath ||
|
|
1430
1596
|
(bundler === "rspack" ? "config/rspack" : "config/webpack")
|
|
1431
1597
|
|
|
1598
|
+
const result = { bundler, configPath }
|
|
1599
|
+
shakapackerConfigCache = { env, result }
|
|
1600
|
+
|
|
1601
|
+
// Only log on first call (when cache was empty)
|
|
1432
1602
|
console.log(
|
|
1433
1603
|
`[Config Exporter] Auto-detected bundler: ${bundler}, config path: ${configPath}`
|
|
1434
1604
|
)
|
|
1435
|
-
|
|
1605
|
+
|
|
1606
|
+
return result
|
|
1436
1607
|
}
|
|
1437
1608
|
} catch (error: unknown) {
|
|
1438
1609
|
console.warn(
|
|
@@ -1440,7 +1611,9 @@ function loadShakapackerConfig(
|
|
|
1440
1611
|
)
|
|
1441
1612
|
}
|
|
1442
1613
|
|
|
1443
|
-
|
|
1614
|
+
const result = { bundler: "webpack" as const, configPath: "config/webpack" }
|
|
1615
|
+
shakapackerConfigCache = { env, result }
|
|
1616
|
+
return result
|
|
1444
1617
|
}
|
|
1445
1618
|
|
|
1446
1619
|
/**
|
|
@@ -1458,18 +1631,20 @@ function loadShakapackerConfig(
|
|
|
1458
1631
|
*/
|
|
1459
1632
|
async function autoDetectBundler(
|
|
1460
1633
|
env: string,
|
|
1461
|
-
appRoot: string
|
|
1634
|
+
appRoot: string,
|
|
1635
|
+
verbose = false
|
|
1462
1636
|
): Promise<"webpack" | "rspack"> {
|
|
1463
|
-
const { bundler } = loadShakapackerConfig(env, appRoot)
|
|
1637
|
+
const { bundler } = loadShakapackerConfig(env, appRoot, verbose)
|
|
1464
1638
|
return bundler
|
|
1465
1639
|
}
|
|
1466
1640
|
|
|
1467
1641
|
function findConfigFile(
|
|
1468
1642
|
bundler: "webpack" | "rspack",
|
|
1469
1643
|
appRoot: string,
|
|
1470
|
-
env: string
|
|
1644
|
+
env: string,
|
|
1645
|
+
verbose = false
|
|
1471
1646
|
): string {
|
|
1472
|
-
const { configPath } = loadShakapackerConfig(env, appRoot)
|
|
1647
|
+
const { configPath } = loadShakapackerConfig(env, appRoot, verbose)
|
|
1473
1648
|
const extensions = ["ts", "js"]
|
|
1474
1649
|
|
|
1475
1650
|
if (bundler === "rspack") {
|
|
@@ -1527,7 +1702,6 @@ function setupNodePath(appRoot: string): void {
|
|
|
1527
1702
|
? `${nodePaths.join(delimiter)}${delimiter}${existingNodePath}`
|
|
1528
1703
|
: nodePaths.join(delimiter)
|
|
1529
1704
|
|
|
1530
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1531
1705
|
require("module").Module._initPaths()
|
|
1532
1706
|
}
|
|
1533
1707
|
}
|
|
@@ -46,24 +46,33 @@ export class FileWriter {
|
|
|
46
46
|
* Generate filename for a config export
|
|
47
47
|
* Format without build: {bundler}-{env}-{type}.{ext}
|
|
48
48
|
* Format with build: {bundler}-{build}-{type}.{ext}
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
49
|
+
*
|
|
50
|
+
* @param bundler - The bundler type (webpack, rspack)
|
|
51
|
+
* @param env - The environment (development, production, test)
|
|
52
|
+
* @param configType - Type of config. Built-in: "client", "server", "all", "client-hmr". Custom: any string from outputs array
|
|
53
|
+
* @param format - Output format (yaml, json, inspect)
|
|
54
|
+
* @param buildName - Optional build name that overrides env in filename
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Built-in types
|
|
58
|
+
* generateFilename("webpack", "development", "client", "yaml")
|
|
59
|
+
* // => "webpack-development-client.yml"
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Custom output names
|
|
63
|
+
* generateFilename("webpack", "development", "client-modern", "yaml", "dev-hmr")
|
|
64
|
+
* // => "webpack-dev-hmr-client-modern.yml"
|
|
56
65
|
*/
|
|
57
66
|
static generateFilename(
|
|
58
67
|
bundler: string,
|
|
59
68
|
env: string,
|
|
60
|
-
configType:
|
|
69
|
+
configType: string,
|
|
61
70
|
format: "yaml" | "json" | "inspect",
|
|
62
71
|
buildName?: string
|
|
63
72
|
): string {
|
|
64
73
|
let ext: string
|
|
65
74
|
if (format === "yaml") {
|
|
66
|
-
ext = "
|
|
75
|
+
ext = "yml"
|
|
67
76
|
} else if (format === "json") {
|
|
68
77
|
ext = "json"
|
|
69
78
|
} else {
|
|
@@ -29,7 +29,12 @@ export interface ConfigMetadata {
|
|
|
29
29
|
bundler: string
|
|
30
30
|
environment: string
|
|
31
31
|
configFile: string
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Type of webpack/rspack config output.
|
|
34
|
+
* Built-in types: "client", "server", "all", "client-hmr"
|
|
35
|
+
* Custom types: Any string matching your outputs array (e.g., "client-modern", "client-legacy", "server-bundle")
|
|
36
|
+
*/
|
|
37
|
+
configType: string
|
|
33
38
|
configCount: number
|
|
34
39
|
buildName?: string // New: name of the build from config file
|
|
35
40
|
environmentVariables: {
|