shakapacker 9.3.0.beta.6 → 9.3.0
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 +46 -105
- data/ESLINT_TECHNICAL_DEBT.md +8 -2
- data/Gemfile.lock +1 -1
- data/README.md +53 -2
- data/docs/configuration.md +28 -0
- data/docs/rspack_migration_guide.md +238 -2
- data/docs/troubleshooting.md +21 -21
- data/eslint.config.fast.js +8 -0
- data/eslint.config.js +47 -10
- data/knip.ts +8 -1
- data/lib/install/config/shakapacker.yml +6 -6
- data/lib/shakapacker/configuration.rb +227 -4
- data/lib/shakapacker/dev_server.rb +88 -1
- data/lib/shakapacker/doctor.rb +129 -72
- data/lib/shakapacker/instance.rb +85 -1
- data/lib/shakapacker/manifest.rb +85 -11
- data/lib/shakapacker/runner.rb +12 -8
- data/lib/shakapacker/swc_migrator.rb +7 -7
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker.rb +143 -3
- data/lib/tasks/shakapacker/doctor.rake +1 -1
- data/lib/tasks/shakapacker/export_bundler_config.rake +4 -4
- data/package/config.ts +0 -1
- data/package/configExporter/buildValidator.ts +53 -29
- data/package/configExporter/cli.ts +152 -118
- data/package/configExporter/configFile.ts +33 -26
- data/package/configExporter/fileWriter.ts +3 -3
- data/package/configExporter/types.ts +64 -0
- data/package/configExporter/yamlSerializer.ts +147 -36
- data/package/dev_server.ts +2 -1
- data/package/env.ts +1 -1
- data/package/environments/base.ts +4 -4
- data/package/environments/development.ts +7 -6
- data/package/environments/production.ts +6 -7
- data/package/environments/test.ts +2 -1
- data/package/index.ts +28 -4
- data/package/loaders.d.ts +2 -2
- data/package/optimization/webpack.ts +29 -31
- data/package/plugins/webpack.ts +2 -1
- data/package/rspack/index.ts +2 -1
- data/package/rules/file.ts +1 -0
- data/package/rules/jscommon.ts +1 -0
- data/package/utils/helpers.ts +0 -1
- data/package/utils/pathValidation.ts +68 -7
- data/package/utils/requireOrError.ts +10 -2
- data/package/utils/typeGuards.ts +43 -46
- data/package/webpack-types.d.ts +2 -2
- data/package/webpackDevServerConfig.ts +1 -0
- data/package.json +2 -3
- data/test/configExporter/integration.test.js +8 -8
- data/test/package/configExporter/cli.test.js +440 -0
- data/test/package/configExporter/types.test.js +163 -0
- data/test/package/configExporter.test.js +271 -7
- data/test/package/yamlSerializer.test.js +204 -0
- data/test/typescript/pathValidation.test.js +44 -0
- data/test/typescript/requireOrError.test.js +49 -0
- data/yarn.lock +0 -32
- metadata +11 -6
- data/.eslintrc.fast.js +0 -40
- data/.eslintrc.js +0 -84
- data/package-lock.json +0 -13047
|
@@ -1,35 +1,40 @@
|
|
|
1
1
|
// This will be a substantial file - the main CLI entry point
|
|
2
|
-
//
|
|
2
|
+
// Originally migrated from bin/export-bundler-config, now bin/shakapacker-config
|
|
3
3
|
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync } from "fs"
|
|
5
5
|
import { resolve, dirname, sep, delimiter, basename } from "path"
|
|
6
6
|
import { inspect } from "util"
|
|
7
7
|
import { load as loadYaml } from "js-yaml"
|
|
8
8
|
import yargs from "yargs"
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ExportOptions,
|
|
11
|
+
ConfigMetadata,
|
|
12
|
+
FileOutput,
|
|
13
|
+
BUILD_ENV_VARS,
|
|
14
|
+
isBuildEnvVar,
|
|
15
|
+
isDangerousEnvVar,
|
|
16
|
+
DEFAULT_EXPORT_DIR,
|
|
17
|
+
DEFAULT_CONFIG_FILE
|
|
18
|
+
} from "./types"
|
|
10
19
|
import { YamlSerializer } from "./yamlSerializer"
|
|
11
20
|
import { FileWriter } from "./fileWriter"
|
|
12
21
|
import { ConfigFileLoader, generateSampleConfigFile } from "./configFile"
|
|
13
22
|
import { BuildValidator } from "./buildValidator"
|
|
23
|
+
import { safeResolvePath } from "../utils/pathValidation"
|
|
14
24
|
|
|
15
25
|
// Read version from package.json
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"BABEL_ENV",
|
|
29
|
-
"WEBPACK_SERVE",
|
|
30
|
-
"CLIENT_BUNDLE_ONLY",
|
|
31
|
-
"SERVER_BUNDLE_ONLY"
|
|
32
|
-
] as const
|
|
26
|
+
let VERSION = "unknown"
|
|
27
|
+
try {
|
|
28
|
+
const packageJson = JSON.parse(
|
|
29
|
+
readFileSync(resolve(__dirname, "../../package.json"), "utf8")
|
|
30
|
+
) as { version?: string }
|
|
31
|
+
VERSION = packageJson.version || "unknown"
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.warn(
|
|
34
|
+
"Could not read version from package.json:",
|
|
35
|
+
error instanceof Error ? error.message : String(error)
|
|
36
|
+
)
|
|
37
|
+
}
|
|
33
38
|
|
|
34
39
|
/**
|
|
35
40
|
* Saves current values of build environment variables for later restoration
|
|
@@ -103,6 +108,15 @@ export async function run(args: string[]): Promise<number> {
|
|
|
103
108
|
// Apply defaults
|
|
104
109
|
const resolvedOptions = applyDefaults(options)
|
|
105
110
|
|
|
111
|
+
// Validate paths for security AFTER defaults are applied
|
|
112
|
+
// Use safeResolvePath which validates and resolves symlinks
|
|
113
|
+
if (resolvedOptions.output) {
|
|
114
|
+
safeResolvePath(appRoot, resolvedOptions.output)
|
|
115
|
+
}
|
|
116
|
+
if (resolvedOptions.saveDir) {
|
|
117
|
+
safeResolvePath(appRoot, resolvedOptions.saveDir)
|
|
118
|
+
}
|
|
119
|
+
|
|
106
120
|
// Validate after defaults are applied
|
|
107
121
|
if (resolvedOptions.annotate && resolvedOptions.format !== "yaml") {
|
|
108
122
|
throw new Error(
|
|
@@ -114,8 +128,7 @@ export async function run(args: string[]): Promise<number> {
|
|
|
114
128
|
if (resolvedOptions.build) {
|
|
115
129
|
const loader = new ConfigFileLoader(resolvedOptions.configFile)
|
|
116
130
|
if (!loader.exists()) {
|
|
117
|
-
const configPath =
|
|
118
|
-
resolvedOptions.configFile || "config/shakapacker-builds.yml"
|
|
131
|
+
const configPath = resolvedOptions.configFile || DEFAULT_CONFIG_FILE
|
|
119
132
|
throw new Error(
|
|
120
133
|
`--build requires a config file but ${configPath} not found. Run --init to create it.`
|
|
121
134
|
)
|
|
@@ -144,7 +157,7 @@ export async function run(args: string[]): Promise<number> {
|
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
|
|
147
|
-
function parseArguments(args: string[]): ExportOptions {
|
|
160
|
+
export function parseArguments(args: string[]): ExportOptions {
|
|
148
161
|
const argv = yargs(args)
|
|
149
162
|
.version(VERSION)
|
|
150
163
|
.usage(
|
|
@@ -164,8 +177,7 @@ QUICK START (for troubleshooting):
|
|
|
164
177
|
.option("init", {
|
|
165
178
|
type: "boolean",
|
|
166
179
|
default: false,
|
|
167
|
-
description:
|
|
168
|
-
"Generate config/shakapacker-builds.yml (use with --ssr for SSR builds)"
|
|
180
|
+
description: `Generate ${DEFAULT_CONFIG_FILE} (use with --ssr for SSR builds)`
|
|
169
181
|
})
|
|
170
182
|
.option("ssr", {
|
|
171
183
|
type: "boolean",
|
|
@@ -188,8 +200,7 @@ QUICK START (for troubleshooting):
|
|
|
188
200
|
})
|
|
189
201
|
.option("config-file", {
|
|
190
202
|
type: "string",
|
|
191
|
-
description:
|
|
192
|
-
"Path to config file (default: config/shakapacker-builds.yml)"
|
|
203
|
+
description: `Path to config file (default: ${DEFAULT_CONFIG_FILE})`
|
|
193
204
|
})
|
|
194
205
|
// Validation Options
|
|
195
206
|
.option("validate", {
|
|
@@ -235,11 +246,26 @@ QUICK START (for troubleshooting):
|
|
|
235
246
|
"Enable inline documentation (YAML only, default with --doctor or file output)"
|
|
236
247
|
})
|
|
237
248
|
.option("depth", {
|
|
238
|
-
|
|
249
|
+
// Note: type omitted to allow string "null" (yargs would reject it).
|
|
250
|
+
// Coerce function handles validation for both numbers and "null".
|
|
239
251
|
default: 20,
|
|
240
252
|
coerce: (value: number | string) => {
|
|
253
|
+
// Handle "null" string for unlimited depth
|
|
241
254
|
if (value === "null" || value === null) return null
|
|
242
|
-
|
|
255
|
+
|
|
256
|
+
// Reject non-numeric types (arrays, objects, etc.)
|
|
257
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`--depth must be a number or 'null', got: ${typeof value}`
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const parsed =
|
|
264
|
+
typeof value === "number" ? value : parseInt(String(value), 10)
|
|
265
|
+
if (Number.isNaN(parsed)) {
|
|
266
|
+
throw new Error(`--depth must be a number or 'null', got: ${value}`)
|
|
267
|
+
}
|
|
268
|
+
return parsed
|
|
243
269
|
},
|
|
244
270
|
description: "Inspection depth (use 'null' for unlimited)"
|
|
245
271
|
})
|
|
@@ -319,6 +345,21 @@ QUICK START (for troubleshooting):
|
|
|
319
345
|
"--validate cannot be used with --build or --all-builds."
|
|
320
346
|
)
|
|
321
347
|
}
|
|
348
|
+
if (argv["all-builds"] && argv.output) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
"--all-builds and --output are mutually exclusive. Use --save-dir instead."
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
if (argv["all-builds"] && argv.stdout) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
"--all-builds and --stdout are mutually exclusive. Use --save-dir instead."
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
if (argv.stdout && argv.output) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
"--stdout and --output are mutually exclusive. Use one or the other."
|
|
361
|
+
)
|
|
362
|
+
}
|
|
322
363
|
if (argv.ssr && !argv.init) {
|
|
323
364
|
throw new Error(
|
|
324
365
|
"--ssr can only be used with --init. Use: bin/shakapacker-config --init --ssr"
|
|
@@ -351,28 +392,25 @@ QUICK START (for troubleshooting):
|
|
|
351
392
|
|
|
352
393
|
# Advanced output options
|
|
353
394
|
bin/shakapacker-config --build=dev --stdout # View in terminal
|
|
354
|
-
bin/shakapacker-config --build=dev --output=config.
|
|
395
|
+
bin/shakapacker-config --build=dev --output=config.yml # Save to specific file`
|
|
355
396
|
)
|
|
356
397
|
.strict()
|
|
357
398
|
.parseSync()
|
|
358
399
|
|
|
359
400
|
// Type assertions are safe here because yargs validates choices at runtime
|
|
360
401
|
// Handle --webpack and --rspack flags
|
|
361
|
-
let bundler
|
|
362
|
-
| "webpack"
|
|
363
|
-
| "rspack"
|
|
364
|
-
| undefined
|
|
402
|
+
let { bundler } = argv
|
|
365
403
|
if (argv.webpack) bundler = "webpack"
|
|
366
404
|
if (argv.rspack) bundler = "rspack"
|
|
367
405
|
|
|
368
406
|
return {
|
|
369
407
|
bundler,
|
|
370
|
-
env: argv.env
|
|
408
|
+
env: argv.env,
|
|
371
409
|
clientOnly: argv["client-only"],
|
|
372
410
|
serverOnly: argv["server-only"],
|
|
373
411
|
output: argv.output,
|
|
374
|
-
depth: argv.depth
|
|
375
|
-
format: argv.format
|
|
412
|
+
depth: argv.depth,
|
|
413
|
+
format: argv.format,
|
|
376
414
|
help: false, // yargs handles help internally
|
|
377
415
|
verbose: argv.verbose,
|
|
378
416
|
doctor: argv.doctor,
|
|
@@ -411,17 +449,14 @@ function applyDefaults(options: ExportOptions): ExportOptions {
|
|
|
411
449
|
!updatedOptions.output &&
|
|
412
450
|
!updatedOptions.saveDir
|
|
413
451
|
) {
|
|
414
|
-
updatedOptions.saveDir = resolve(
|
|
415
|
-
process.cwd(),
|
|
416
|
-
"shakapacker-config-exports"
|
|
417
|
-
)
|
|
452
|
+
updatedOptions.saveDir = resolve(process.cwd(), DEFAULT_EXPORT_DIR)
|
|
418
453
|
}
|
|
419
454
|
|
|
420
455
|
return updatedOptions
|
|
421
456
|
}
|
|
422
457
|
|
|
423
458
|
function runInitCommand(options: ExportOptions): number {
|
|
424
|
-
const configPath = options.configFile ||
|
|
459
|
+
const configPath = options.configFile || DEFAULT_CONFIG_FILE
|
|
425
460
|
const fullPath = resolve(process.cwd(), configPath)
|
|
426
461
|
|
|
427
462
|
// Check if SSR variant is requested via --ssr flag
|
|
@@ -528,7 +563,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
528
563
|
// Validate that config file exists
|
|
529
564
|
const loader = new ConfigFileLoader(options.configFile)
|
|
530
565
|
if (!loader.exists()) {
|
|
531
|
-
const configPath = options.configFile ||
|
|
566
|
+
const configPath = options.configFile || DEFAULT_CONFIG_FILE
|
|
532
567
|
throw new Error(
|
|
533
568
|
`Config file ${configPath} not found. Run --init to create it.`
|
|
534
569
|
)
|
|
@@ -561,12 +596,12 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
561
596
|
// Handle empty builds edge case
|
|
562
597
|
if (buildsToValidate.length === 0) {
|
|
563
598
|
throw new Error(
|
|
564
|
-
`No builds found in config file. Add at least one build to
|
|
599
|
+
`No builds found in config file. Add at least one build to ${DEFAULT_CONFIG_FILE} or run --init to see examples.`
|
|
565
600
|
)
|
|
566
601
|
}
|
|
567
602
|
}
|
|
568
603
|
|
|
569
|
-
console.log(
|
|
604
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
570
605
|
console.log("🔍 Validating Builds")
|
|
571
606
|
console.log("=".repeat(80))
|
|
572
607
|
console.log(`\nValidating ${buildsToValidate.length} build(s)...\n`)
|
|
@@ -577,7 +612,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
577
612
|
" This includes all webpack/rspack compilation logs, warnings, and progress messages"
|
|
578
613
|
)
|
|
579
614
|
console.log(" Use without --verbose to see only errors and summaries\n")
|
|
580
|
-
console.log("=".repeat(80)
|
|
615
|
+
console.log(`${"=".repeat(80)}\n`)
|
|
581
616
|
}
|
|
582
617
|
|
|
583
618
|
const results = []
|
|
@@ -585,7 +620,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
585
620
|
// Validate each build
|
|
586
621
|
for (const buildName of buildsToValidate) {
|
|
587
622
|
if (options.verbose) {
|
|
588
|
-
console.log(
|
|
623
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
589
624
|
console.log(`📦 VALIDATING BUILD: ${buildName}`)
|
|
590
625
|
console.log("=".repeat(80))
|
|
591
626
|
} else {
|
|
@@ -611,7 +646,11 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
|
611
646
|
"development"
|
|
612
647
|
|
|
613
648
|
// Auto-detect bundler using the build's environment
|
|
614
|
-
const defaultBundler = await autoDetectBundler(
|
|
649
|
+
const defaultBundler = await autoDetectBundler(
|
|
650
|
+
buildEnv,
|
|
651
|
+
appRoot,
|
|
652
|
+
options.verbose
|
|
653
|
+
)
|
|
615
654
|
|
|
616
655
|
// Resolve build config with the correct default bundler
|
|
617
656
|
const resolvedBuild = loader.resolveBuild(
|
|
@@ -670,8 +709,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
|
|
|
670
709
|
|
|
671
710
|
const loader = new ConfigFileLoader(resolvedOptions.configFile)
|
|
672
711
|
if (!loader.exists()) {
|
|
673
|
-
const configPath =
|
|
674
|
-
resolvedOptions.configFile || "config/shakapacker-builds.yml"
|
|
712
|
+
const configPath = resolvedOptions.configFile || DEFAULT_CONFIG_FILE
|
|
675
713
|
throw new Error(
|
|
676
714
|
`Config file ${configPath} not found. Run --init to create it.`
|
|
677
715
|
)
|
|
@@ -719,7 +757,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
|
|
|
719
757
|
}
|
|
720
758
|
|
|
721
759
|
// Print summary
|
|
722
|
-
console.log(
|
|
760
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
723
761
|
console.log("✅ All Builds Exported!")
|
|
724
762
|
console.log("=".repeat(80))
|
|
725
763
|
console.log(`\nCreated ${createdFiles.length} configuration file(s) in:`)
|
|
@@ -728,7 +766,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
|
|
|
728
766
|
createdFiles.forEach((file) => {
|
|
729
767
|
console.log(` ✓ ${basename(file)}`)
|
|
730
768
|
})
|
|
731
|
-
console.log(
|
|
769
|
+
console.log(`\n${"=".repeat(80)}\n`)
|
|
732
770
|
|
|
733
771
|
return 0
|
|
734
772
|
} catch (error: unknown) {
|
|
@@ -749,7 +787,7 @@ async function runDoctorMode(
|
|
|
749
787
|
const savedEnv = saveBuildEnvironmentVariables()
|
|
750
788
|
|
|
751
789
|
try {
|
|
752
|
-
console.log(
|
|
790
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
753
791
|
console.log("🔍 Config Exporter - Doctor Mode")
|
|
754
792
|
console.log("=".repeat(80))
|
|
755
793
|
|
|
@@ -758,7 +796,7 @@ async function runDoctorMode(
|
|
|
758
796
|
const createdFiles: string[] = []
|
|
759
797
|
|
|
760
798
|
// Check if config file exists - always use it for doctor mode
|
|
761
|
-
const configFilePath = options.configFile ||
|
|
799
|
+
const configFilePath = options.configFile || DEFAULT_CONFIG_FILE
|
|
762
800
|
const loader = new ConfigFileLoader(configFilePath)
|
|
763
801
|
|
|
764
802
|
if (loader.exists()) {
|
|
@@ -899,7 +937,7 @@ async function runDoctorMode(
|
|
|
899
937
|
|
|
900
938
|
function printDoctorSummary(createdFiles: string[], targetDir: string): void {
|
|
901
939
|
// Print summary
|
|
902
|
-
console.log(
|
|
940
|
+
console.log(`\n${"=".repeat(80)}`)
|
|
903
941
|
console.log("✅ Export Complete!")
|
|
904
942
|
console.log("=".repeat(80))
|
|
905
943
|
console.log(`\nCreated ${createdFiles.length} configuration file(s) in:`)
|
|
@@ -922,14 +960,14 @@ function printDoctorSummary(createdFiles: string[], targetDir: string): void {
|
|
|
922
960
|
}
|
|
923
961
|
|
|
924
962
|
if (shouldSuggestGitignore) {
|
|
925
|
-
console.log(
|
|
963
|
+
console.log(`\n${"─".repeat(80)}`)
|
|
926
964
|
console.log(
|
|
927
965
|
"💡 Tip: Add the export directory to .gitignore to avoid committing config files:"
|
|
928
966
|
)
|
|
929
967
|
console.log(`\n echo "${dirName}/" >> .gitignore\n`)
|
|
930
968
|
}
|
|
931
969
|
|
|
932
|
-
console.log(
|
|
970
|
+
console.log(`\n${"=".repeat(80)}\n`)
|
|
933
971
|
}
|
|
934
972
|
|
|
935
973
|
async function runSaveMode(
|
|
@@ -946,7 +984,7 @@ async function runSaveMode(
|
|
|
946
984
|
if (options.output) {
|
|
947
985
|
// Single file output
|
|
948
986
|
const combined = configs.map((c) => c.config)
|
|
949
|
-
const metadata = configs[0]
|
|
987
|
+
const { metadata } = configs[0]
|
|
950
988
|
metadata.configCount = combined.length
|
|
951
989
|
|
|
952
990
|
const output = formatConfig(
|
|
@@ -986,15 +1024,15 @@ async function runStdoutMode(
|
|
|
986
1024
|
options: ExportOptions,
|
|
987
1025
|
appRoot: string
|
|
988
1026
|
): Promise<void> {
|
|
989
|
-
const configs = await loadConfigsForEnv(options.env
|
|
1027
|
+
const configs = await loadConfigsForEnv(options.env, options, appRoot)
|
|
990
1028
|
const combined = configs.map((c) => c.config)
|
|
991
|
-
const metadata = configs[0]
|
|
1029
|
+
const { metadata } = configs[0]
|
|
992
1030
|
metadata.configCount = combined.length
|
|
993
1031
|
|
|
994
1032
|
const config = combined.length === 1 ? combined[0] : combined
|
|
995
1033
|
const output = formatConfig(config, metadata, options, appRoot)
|
|
996
1034
|
|
|
997
|
-
console.log(
|
|
1035
|
+
console.log(`\n${"=".repeat(80)}\n`)
|
|
998
1036
|
console.log(output)
|
|
999
1037
|
}
|
|
1000
1038
|
|
|
@@ -1002,9 +1040,9 @@ async function runSingleFileMode(
|
|
|
1002
1040
|
options: ExportOptions,
|
|
1003
1041
|
appRoot: string
|
|
1004
1042
|
): Promise<void> {
|
|
1005
|
-
const configs = await loadConfigsForEnv(options.env
|
|
1043
|
+
const configs = await loadConfigsForEnv(options.env, options, appRoot)
|
|
1006
1044
|
const combined = configs.map((c) => c.config)
|
|
1007
|
-
const metadata = configs[0]
|
|
1045
|
+
const { metadata } = configs[0]
|
|
1008
1046
|
metadata.configCount = combined.length
|
|
1009
1047
|
|
|
1010
1048
|
const config = combined.length === 1 ? combined[0] : combined
|
|
@@ -1031,7 +1069,11 @@ async function loadConfigsForEnv(
|
|
|
1031
1069
|
// Use a temporary env for auto-detection, will be overridden by build config
|
|
1032
1070
|
const tempEnv = env || "development"
|
|
1033
1071
|
const loader = new ConfigFileLoader(options.configFile)
|
|
1034
|
-
const defaultBundler = await autoDetectBundler(
|
|
1072
|
+
const defaultBundler = await autoDetectBundler(
|
|
1073
|
+
tempEnv,
|
|
1074
|
+
appRoot,
|
|
1075
|
+
options.verbose
|
|
1076
|
+
)
|
|
1035
1077
|
const resolvedBuild = loader.resolveBuild(
|
|
1036
1078
|
options.build,
|
|
1037
1079
|
options,
|
|
@@ -1046,36 +1088,27 @@ async function loadConfigsForEnv(
|
|
|
1046
1088
|
|
|
1047
1089
|
// Set environment variables from config
|
|
1048
1090
|
// Security: Only allow specific environment variables to prevent malicious configs
|
|
1049
|
-
|
|
1050
|
-
"PATH",
|
|
1051
|
-
"HOME",
|
|
1052
|
-
"LD_PRELOAD",
|
|
1053
|
-
"LD_LIBRARY_PATH",
|
|
1054
|
-
"DYLD_LIBRARY_PATH",
|
|
1055
|
-
"DYLD_INSERT_LIBRARIES"
|
|
1056
|
-
]
|
|
1057
|
-
|
|
1058
|
-
if (process.env.VERBOSE) {
|
|
1091
|
+
if (options.verbose) {
|
|
1059
1092
|
console.log(
|
|
1060
1093
|
`[Config Exporter] Setting environment variables from build config...`
|
|
1061
1094
|
)
|
|
1062
1095
|
}
|
|
1063
1096
|
|
|
1064
1097
|
for (const [key, value] of Object.entries(resolvedBuild.environment)) {
|
|
1065
|
-
if (
|
|
1098
|
+
if (isDangerousEnvVar(key)) {
|
|
1066
1099
|
console.warn(
|
|
1067
1100
|
`[Config Exporter] Warning: Skipping dangerous environment variable: ${key}`
|
|
1068
1101
|
)
|
|
1069
1102
|
continue
|
|
1070
1103
|
}
|
|
1071
|
-
if (!(
|
|
1104
|
+
if (!isBuildEnvVar(key)) {
|
|
1072
1105
|
console.warn(
|
|
1073
1106
|
`[Config Exporter] Warning: Skipping non-whitelisted environment variable: ${key}. ` +
|
|
1074
1107
|
`Allowed variables are: ${BUILD_ENV_VARS.join(", ")}`
|
|
1075
1108
|
)
|
|
1076
1109
|
continue
|
|
1077
1110
|
}
|
|
1078
|
-
if (
|
|
1111
|
+
if (options.verbose) {
|
|
1079
1112
|
console.log(`[Config Exporter] ${key}=${value}`)
|
|
1080
1113
|
}
|
|
1081
1114
|
process.env[key] = value
|
|
@@ -1125,7 +1158,9 @@ async function loadConfigsForEnv(
|
|
|
1125
1158
|
finalEnv = env || "development"
|
|
1126
1159
|
|
|
1127
1160
|
// Auto-detect bundler if not specified
|
|
1128
|
-
bundler =
|
|
1161
|
+
bundler =
|
|
1162
|
+
options.bundler ||
|
|
1163
|
+
(await autoDetectBundler(finalEnv, appRoot, options.verbose))
|
|
1129
1164
|
|
|
1130
1165
|
// Set environment variables
|
|
1131
1166
|
process.env.NODE_ENV = finalEnv
|
|
@@ -1141,9 +1176,10 @@ async function loadConfigsForEnv(
|
|
|
1141
1176
|
|
|
1142
1177
|
// Find and load config file
|
|
1143
1178
|
const configFile =
|
|
1144
|
-
customConfigFile ||
|
|
1179
|
+
customConfigFile ||
|
|
1180
|
+
findConfigFile(bundler, appRoot, finalEnv, options.verbose)
|
|
1145
1181
|
// Quiet mode for cleaner output - only show if verbose or errors
|
|
1146
|
-
if (
|
|
1182
|
+
if (options.verbose) {
|
|
1147
1183
|
console.log(`[Config Exporter] Loading config: ${configFile}`)
|
|
1148
1184
|
console.log(`[Config Exporter] Environment: ${finalEnv}`)
|
|
1149
1185
|
console.log(`[Config Exporter] Bundler: ${bundler}`)
|
|
@@ -1156,7 +1192,6 @@ async function loadConfigsForEnv(
|
|
|
1156
1192
|
// Register ts-node for TypeScript config files
|
|
1157
1193
|
if (configFile.endsWith(".ts")) {
|
|
1158
1194
|
try {
|
|
1159
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1160
1195
|
require("ts-node/register/transpile-only")
|
|
1161
1196
|
} catch (error) {
|
|
1162
1197
|
throw new Error(
|
|
@@ -1205,7 +1240,6 @@ async function loadConfigsForEnv(
|
|
|
1205
1240
|
}
|
|
1206
1241
|
})
|
|
1207
1242
|
|
|
1208
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1209
1243
|
let loadedConfig: any
|
|
1210
1244
|
try {
|
|
1211
1245
|
loadedConfig = require(configFile)
|
|
@@ -1283,9 +1317,9 @@ async function loadConfigsForEnv(
|
|
|
1283
1317
|
}
|
|
1284
1318
|
|
|
1285
1319
|
throw new Error(
|
|
1286
|
-
`Failed to execute config function: ${errorMessage}\n
|
|
1287
|
-
|
|
1288
|
-
|
|
1320
|
+
`Failed to execute config function: ${errorMessage}\n${envDetails.join(
|
|
1321
|
+
"\n"
|
|
1322
|
+
)}\nTip: ${suggestion}`
|
|
1289
1323
|
)
|
|
1290
1324
|
}
|
|
1291
1325
|
}
|
|
@@ -1338,7 +1372,7 @@ async function loadConfigsForEnv(
|
|
|
1338
1372
|
}
|
|
1339
1373
|
|
|
1340
1374
|
// Debug logging
|
|
1341
|
-
if (
|
|
1375
|
+
if (options.verbose || buildOutputs.length > 0) {
|
|
1342
1376
|
console.log(
|
|
1343
1377
|
`[Config Exporter] Webpack returned ${configs.length} config(s), buildOutputs: [${buildOutputs.join(", ")}]`
|
|
1344
1378
|
)
|
|
@@ -1442,39 +1476,37 @@ function formatConfig(
|
|
|
1442
1476
|
return value
|
|
1443
1477
|
}
|
|
1444
1478
|
return JSON.stringify({ metadata, config }, jsonReplacer, 2)
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
let output =
|
|
1457
|
-
"=== METADATA ===\n\n" + inspect(metadata, inspectOptions) + "\n\n"
|
|
1458
|
-
output += "=== CONFIG ===\n\n"
|
|
1479
|
+
}
|
|
1480
|
+
// inspect format
|
|
1481
|
+
const inspectOptions = {
|
|
1482
|
+
depth: options.depth,
|
|
1483
|
+
colors: false,
|
|
1484
|
+
maxArrayLength: null,
|
|
1485
|
+
maxStringLength: null,
|
|
1486
|
+
breakLength: 120,
|
|
1487
|
+
compact: false
|
|
1488
|
+
}
|
|
1459
1489
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
config.forEach((cfg, index) => {
|
|
1463
|
-
output += `--- Config [${index}] ---\n\n`
|
|
1464
|
-
output += inspect(cfg, inspectOptions) + "\n\n"
|
|
1465
|
-
})
|
|
1466
|
-
} else {
|
|
1467
|
-
output += inspect(config, inspectOptions) + "\n"
|
|
1468
|
-
}
|
|
1490
|
+
let output = `=== METADATA ===\n\n${inspect(metadata, inspectOptions)}\n\n`
|
|
1491
|
+
output += "=== CONFIG ===\n\n"
|
|
1469
1492
|
|
|
1470
|
-
|
|
1493
|
+
if (Array.isArray(config)) {
|
|
1494
|
+
output += `Total configs: ${config.length}\n\n`
|
|
1495
|
+
config.forEach((cfg, index) => {
|
|
1496
|
+
output += `--- Config [${index}] ---\n\n`
|
|
1497
|
+
output += `${inspect(cfg, inspectOptions)}\n\n`
|
|
1498
|
+
})
|
|
1499
|
+
} else {
|
|
1500
|
+
output += `${inspect(config, inspectOptions)}\n`
|
|
1471
1501
|
}
|
|
1502
|
+
|
|
1503
|
+
return output
|
|
1472
1504
|
}
|
|
1473
1505
|
|
|
1474
1506
|
function cleanConfig(obj: any, rootPath: string): any {
|
|
1475
1507
|
const makePathRelative = (str: string): string => {
|
|
1476
1508
|
if (typeof str === "string" && str.startsWith(rootPath)) {
|
|
1477
|
-
return
|
|
1509
|
+
return `./${str.substring(rootPath.length + 1)}`
|
|
1478
1510
|
}
|
|
1479
1511
|
return str
|
|
1480
1512
|
}
|
|
@@ -1542,11 +1574,12 @@ let shakapackerConfigCache: {
|
|
|
1542
1574
|
|
|
1543
1575
|
function loadShakapackerConfig(
|
|
1544
1576
|
env: string,
|
|
1545
|
-
appRoot: string
|
|
1577
|
+
appRoot: string,
|
|
1578
|
+
verbose = false
|
|
1546
1579
|
): { bundler: "webpack" | "rspack"; configPath: string } {
|
|
1547
1580
|
// Return cached result if same environment
|
|
1548
1581
|
if (shakapackerConfigCache && shakapackerConfigCache.env === env) {
|
|
1549
|
-
if (
|
|
1582
|
+
if (verbose) {
|
|
1550
1583
|
console.log(
|
|
1551
1584
|
`[Config Exporter] Using cached bundler config for env: ${env}`
|
|
1552
1585
|
)
|
|
@@ -1554,7 +1587,7 @@ function loadShakapackerConfig(
|
|
|
1554
1587
|
return shakapackerConfigCache.result
|
|
1555
1588
|
}
|
|
1556
1589
|
|
|
1557
|
-
if (
|
|
1590
|
+
if (verbose) {
|
|
1558
1591
|
console.log(`[Config Exporter] Loading shakapacker config for env: ${env}`)
|
|
1559
1592
|
}
|
|
1560
1593
|
|
|
@@ -1623,18 +1656,20 @@ function loadShakapackerConfig(
|
|
|
1623
1656
|
*/
|
|
1624
1657
|
async function autoDetectBundler(
|
|
1625
1658
|
env: string,
|
|
1626
|
-
appRoot: string
|
|
1659
|
+
appRoot: string,
|
|
1660
|
+
verbose = false
|
|
1627
1661
|
): Promise<"webpack" | "rspack"> {
|
|
1628
|
-
const { bundler } = loadShakapackerConfig(env, appRoot)
|
|
1662
|
+
const { bundler } = loadShakapackerConfig(env, appRoot, verbose)
|
|
1629
1663
|
return bundler
|
|
1630
1664
|
}
|
|
1631
1665
|
|
|
1632
1666
|
function findConfigFile(
|
|
1633
1667
|
bundler: "webpack" | "rspack",
|
|
1634
1668
|
appRoot: string,
|
|
1635
|
-
env: string
|
|
1669
|
+
env: string,
|
|
1670
|
+
verbose = false
|
|
1636
1671
|
): string {
|
|
1637
|
-
const { configPath } = loadShakapackerConfig(env, appRoot)
|
|
1672
|
+
const { configPath } = loadShakapackerConfig(env, appRoot, verbose)
|
|
1638
1673
|
const extensions = ["ts", "js"]
|
|
1639
1674
|
|
|
1640
1675
|
if (bundler === "rspack") {
|
|
@@ -1692,7 +1727,6 @@ function setupNodePath(appRoot: string): void {
|
|
|
1692
1727
|
? `${nodePaths.join(delimiter)}${delimiter}${existingNodePath}`
|
|
1693
1728
|
: nodePaths.join(delimiter)
|
|
1694
1729
|
|
|
1695
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1696
1730
|
require("module").Module._initPaths()
|
|
1697
1731
|
}
|
|
1698
1732
|
}
|