shakapacker 9.3.0.beta.0 → 9.3.0.beta.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.
@@ -10,6 +10,7 @@ import { ExportOptions, ConfigMetadata, FileOutput } from "./types"
10
10
  import { YamlSerializer } from "./yamlSerializer"
11
11
  import { FileWriter } from "./fileWriter"
12
12
  import { ConfigFileLoader, generateSampleConfigFile } from "./configFile"
13
+ import { BuildValidator } from "./buildValidator"
13
14
 
14
15
  // Read version from package.json
15
16
  const packageJson = JSON.parse(
@@ -84,6 +85,11 @@ export async function run(args: string[]): Promise<number> {
84
85
  return runListBuildsCommand(options)
85
86
  }
86
87
 
88
+ // Handle --validate or --validate-build command
89
+ if (options.validate || options.validateBuild) {
90
+ return await runValidateCommand(options)
91
+ }
92
+
87
93
  // Handle --all-builds command
88
94
  if (options.allBuilds) {
89
95
  return runAllBuildsCommand(options)
@@ -130,8 +136,9 @@ export async function run(args: string[]): Promise<number> {
130
136
  }
131
137
 
132
138
  return 0
133
- } catch (error: any) {
134
- console.error(`[Config Exporter] Error: ${error.message}`)
139
+ } catch (error: unknown) {
140
+ const errorMessage = error instanceof Error ? error.message : String(error)
141
+ console.error(`[Config Exporter] Error: ${errorMessage}`)
135
142
  return 1
136
143
  }
137
144
  }
@@ -240,6 +247,16 @@ QUICK START (for troubleshooting):
240
247
  default: false,
241
248
  description: "Export all builds from config file"
242
249
  })
250
+ .option("validate", {
251
+ type: "boolean",
252
+ default: false,
253
+ description:
254
+ "Validate all builds by running webpack/rspack (requires config file)"
255
+ })
256
+ .option("validate-build", {
257
+ type: "string",
258
+ description: "Validate specific build from config file"
259
+ })
243
260
  .option("webpack", {
244
261
  type: "boolean",
245
262
  default: false,
@@ -276,6 +293,16 @@ QUICK START (for troubleshooting):
276
293
  "--build and --all-builds are mutually exclusive. Use one or the other."
277
294
  )
278
295
  }
296
+ if (argv.validate && argv["validate-build"]) {
297
+ throw new Error(
298
+ "--validate and --validate-build are mutually exclusive. Use one or the other."
299
+ )
300
+ }
301
+ if (argv.validate && (argv.build || argv["all-builds"])) {
302
+ throw new Error(
303
+ "--validate cannot be used with --build or --all-builds."
304
+ )
305
+ }
279
306
  return true
280
307
  })
281
308
  .help("help")
@@ -300,6 +327,11 @@ QUICK START (for troubleshooting):
300
327
  bin/export-bundler-config --save-dir=./debug
301
328
  bin/export-bundler-config # Saves to shakapacker-config-exports/
302
329
 
330
+ # Validate builds
331
+ bin/export-bundler-config --validate # Validate all builds
332
+ bin/export-bundler-config --validate-build=dev # Validate specific build
333
+ bin/export-bundler-config --validate --verbose # Validate with full logs
334
+
303
335
  # View config in terminal (stdout)
304
336
  bin/export-bundler-config --stdout
305
337
  bin/export-bundler-config --output=config.yaml # Save to specific file`
@@ -334,7 +366,9 @@ QUICK START (for troubleshooting):
334
366
  configFile: argv["config-file"],
335
367
  build: argv.build,
336
368
  listBuilds: argv["list-builds"],
337
- allBuilds: argv["all-builds"]
369
+ allBuilds: argv["all-builds"],
370
+ validate: argv.validate,
371
+ validateBuild: argv["validate-build"]
338
372
  }
339
373
  }
340
374
 
@@ -392,9 +426,141 @@ function runListBuildsCommand(options: ExportOptions): number {
392
426
  const loader = new ConfigFileLoader(options.configFile)
393
427
  loader.listBuilds()
394
428
  return 0
395
- } catch (error: any) {
396
- console.error(`[Config Exporter] Error: ${error.message}`)
429
+ } catch (error: unknown) {
430
+ const errorMessage = error instanceof Error ? error.message : String(error)
431
+ console.error(`[Config Exporter] Error: ${errorMessage}`)
432
+ return 1
433
+ }
434
+ }
435
+
436
+ async function runValidateCommand(options: ExportOptions): Promise<number> {
437
+ const savedEnv = saveBuildEnvironmentVariables()
438
+
439
+ try {
440
+ // Validate that config file exists
441
+ const loader = new ConfigFileLoader(options.configFile)
442
+ if (!loader.exists()) {
443
+ const configPath = options.configFile || ".bundler-config.yml"
444
+ throw new Error(
445
+ `Config file ${configPath} not found. Run --init to create it.`
446
+ )
447
+ }
448
+
449
+ // Set up environment
450
+ const appRoot = findAppRoot()
451
+ process.chdir(appRoot)
452
+ setupNodePath(appRoot)
453
+
454
+ const config = loader.load()
455
+ const validator = new BuildValidator({ verbose: options.verbose || false })
456
+
457
+ // Determine which builds to validate
458
+ let buildsToValidate: string[]
459
+ if (options.validateBuild) {
460
+ // Validate specific build
461
+ if (!config.builds[options.validateBuild]) {
462
+ const available = Object.keys(config.builds).join(", ")
463
+ throw new Error(
464
+ `Build '${options.validateBuild}' not found in config file.\n` +
465
+ `Available builds: ${available}`
466
+ )
467
+ }
468
+ buildsToValidate = [options.validateBuild]
469
+ } else {
470
+ // Validate all builds
471
+ buildsToValidate = Object.keys(config.builds)
472
+
473
+ // Handle empty builds edge case
474
+ if (buildsToValidate.length === 0) {
475
+ throw new Error(
476
+ `No builds found in config file. Add at least one build to .bundler-config.yml or run --init to see examples.`
477
+ )
478
+ }
479
+ }
480
+
481
+ console.log("\n" + "=".repeat(80))
482
+ console.log("🔍 Validating Builds")
483
+ console.log("=".repeat(80))
484
+ console.log(`\nValidating ${buildsToValidate.length} build(s)...\n`)
485
+
486
+ if (options.verbose) {
487
+ console.log("⚡ VERBOSE MODE ENABLED - Full build output will be shown")
488
+ console.log(
489
+ " This includes all webpack/rspack compilation logs, warnings, and progress messages"
490
+ )
491
+ console.log(" Use without --verbose to see only errors and summaries\n")
492
+ console.log("=".repeat(80) + "\n")
493
+ }
494
+
495
+ const results = []
496
+
497
+ // Validate each build
498
+ for (const buildName of buildsToValidate) {
499
+ if (options.verbose) {
500
+ console.log("\n" + "=".repeat(80))
501
+ console.log(`📦 VALIDATING BUILD: ${buildName}`)
502
+ console.log("=".repeat(80))
503
+ } else {
504
+ console.log(`\n📦 Validating build: ${buildName}`)
505
+ }
506
+
507
+ // Clear and restore environment to prevent leakage between builds
508
+ clearBuildEnvironmentVariables()
509
+ restoreBuildEnvironmentVariables(savedEnv)
510
+
511
+ // Get the build's environment to use for auto-detection
512
+ const buildConfig = config.builds[buildName]
513
+ const buildEnv =
514
+ buildConfig.environment?.NODE_ENV ||
515
+ (buildConfig.environment?.RAILS_ENV as
516
+ | "development"
517
+ | "production"
518
+ | "test"
519
+ | undefined) ||
520
+ "development"
521
+
522
+ // Auto-detect bundler using the build's environment
523
+ const defaultBundler = await autoDetectBundler(buildEnv, appRoot)
524
+
525
+ // Resolve build config with the correct default bundler
526
+ const resolvedBuild = loader.resolveBuild(
527
+ buildName,
528
+ options,
529
+ defaultBundler
530
+ )
531
+
532
+ // Validate the build
533
+ const result = await validator.validateBuild(resolvedBuild, appRoot)
534
+ results.push(result)
535
+
536
+ // Show immediate feedback
537
+ if (options.verbose) {
538
+ console.log("=".repeat(80))
539
+ }
540
+ if (result.success) {
541
+ console.log(` ✅ Build passed`)
542
+ } else {
543
+ console.log(` ❌ Build failed with ${result.errors.length} error(s)`)
544
+ }
545
+ if (options.verbose) {
546
+ console.log("")
547
+ }
548
+ }
549
+
550
+ // Print formatted results
551
+ const formattedResults = validator.formatResults(results)
552
+ console.log(formattedResults)
553
+
554
+ // Return exit code based on results
555
+ const hasFailures = results.some((r) => !r.success)
556
+ return hasFailures ? 1 : 0
557
+ } catch (error: unknown) {
558
+ const errorMessage = error instanceof Error ? error.message : String(error)
559
+ console.error(`[Config Exporter] Error: ${errorMessage}`)
397
560
  return 1
561
+ } finally {
562
+ // Restore original environment
563
+ restoreBuildEnvironmentVariables(savedEnv)
398
564
  }
399
565
  }
400
566
 
@@ -471,8 +637,9 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
471
637
  console.log("\n" + "=".repeat(80) + "\n")
472
638
 
473
639
  return 0
474
- } catch (error: any) {
475
- console.error(`[Config Exporter] Error: ${error.message}`)
640
+ } catch (error: unknown) {
641
+ const errorMessage = error instanceof Error ? error.message : String(error)
642
+ console.error(`[Config Exporter] Error: ${errorMessage}`)
476
643
  return 1
477
644
  } finally {
478
645
  // Restore original environment
@@ -543,9 +710,11 @@ async function runDoctorMode(
543
710
  printDoctorSummary(createdFiles, targetDir)
544
711
  return
545
712
  }
546
- } catch (error: any) {
713
+ } catch (error: unknown) {
547
714
  // If config file exists but is invalid, warn and fall through to default behavior
548
- console.log(`\n⚠️ Config file found but invalid: ${error.message}`)
715
+ const errorMessage =
716
+ error instanceof Error ? error.message : String(error)
717
+ console.log(`\n⚠️ Config file found but invalid: ${errorMessage}`)
549
718
  console.log("Falling back to default doctor mode...\n")
550
719
  }
551
720
  }
@@ -939,9 +1108,11 @@ async function loadConfigsForEnv(
939
1108
  const argv = { mode: finalEnv }
940
1109
  try {
941
1110
  loadedConfig = loadedConfig(envObject, argv)
942
- } catch (error: any) {
1111
+ } catch (error: unknown) {
1112
+ const errorMessage =
1113
+ error instanceof Error ? error.message : String(error)
943
1114
  throw new Error(
944
- `Failed to execute config function: ${error.message}\n` +
1115
+ `Failed to execute config function: ${errorMessage}\n` +
945
1116
  `Config file: ${configFile}\n` +
946
1117
  `Environment: ${JSON.stringify(envObject)}`
947
1118
  )
@@ -1175,7 +1346,7 @@ function loadShakapackerConfig(
1175
1346
  )
1176
1347
  return { bundler, configPath }
1177
1348
  }
1178
- } catch (error: any) {
1349
+ } catch (error: unknown) {
1179
1350
  console.warn(
1180
1351
  `[Config Exporter] Error loading shakapacker config, defaulting to webpack`
1181
1352
  )
@@ -5,9 +5,11 @@ export type {
5
5
  FileOutput,
6
6
  BundlerConfigFile,
7
7
  BuildConfig,
8
- ResolvedBuildConfig
8
+ ResolvedBuildConfig,
9
+ BuildValidationResult
9
10
  } from "./types"
10
11
  export { YamlSerializer } from "./yamlSerializer"
11
12
  export { FileWriter } from "./fileWriter"
12
13
  export { getDocForKey } from "./configDocs"
13
14
  export { ConfigFileLoader, generateSampleConfigFile } from "./configFile"
15
+ export { BuildValidator } from "./buildValidator"
@@ -18,6 +18,9 @@ export interface ExportOptions {
18
18
  build?: string
19
19
  listBuilds?: boolean
20
20
  allBuilds?: boolean
21
+ // Validation options
22
+ validate?: boolean
23
+ validateBuild?: string
21
24
  }
22
25
 
23
26
  export interface ConfigMetadata {
@@ -34,6 +37,7 @@ export interface ConfigMetadata {
34
37
  CLIENT_BUNDLE_ONLY?: string
35
38
  SERVER_BUNDLE_ONLY?: string
36
39
  WEBPACK_SERVE?: string
40
+ HMR?: string
37
41
  }
38
42
  }
39
43
 
@@ -68,3 +72,17 @@ export interface ResolvedBuildConfig {
68
72
  outputs: string[]
69
73
  configFile?: string
70
74
  }
75
+
76
+ export interface BuildValidationResult {
77
+ buildName: string
78
+ success: boolean
79
+ errors: string[]
80
+ warnings: string[]
81
+ output: string[]
82
+ outputs?: string[] // Build outputs (e.g., ["client", "server"])
83
+ configFile?: string // Config file path if specified
84
+ outputPath?: string // Output directory where files are written
85
+ startTime?: number // Unix timestamp in milliseconds
86
+ endTime?: number // Unix timestamp in milliseconds
87
+ duration?: number // Duration in milliseconds
88
+ }
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.3.0-beta.0",
3
+ "version": "9.3.0-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "shakapacker",
9
- "version": "9.3.0-beta.0",
9
+ "version": "9.3.0-beta.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "js-yaml": "^4.1.0",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.3.0-beta.0",
3
+ "version": "9.3.0-beta.1",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -49,23 +49,11 @@
49
49
  "yargs": "^17.7.2"
50
50
  },
51
51
  "devDependencies": {
52
+ "@eslint/eslintrc": "^3.2.0",
53
+ "@eslint/js": "^9.37.0",
52
54
  "@rspack/cli": "^1.5.8",
53
55
  "@rspack/core": "^1.5.8",
54
56
  "@swc/core": "^1.3.0",
55
- "babel-loader": "^8.2.4",
56
- "compression-webpack-plugin": "^9.0.0",
57
- "css-loader": "^7.1.2",
58
- "esbuild-loader": "^2.18.0",
59
- "mini-css-extract-plugin": "^2.9.4",
60
- "rspack-manifest-plugin": "^5.0.3",
61
- "sass-loader": "^16.0.5",
62
- "swc-loader": "^0.1.15",
63
- "webpack": "5.93.0",
64
- "webpack-assets-manifest": "^5.0.6",
65
- "webpack-cli": "^6.0.0",
66
- "webpack-subresource-integrity": "^5.1.0",
67
- "@eslint/eslintrc": "^3.2.0",
68
- "@eslint/js": "^9.37.0",
69
57
  "@types/babel__core": "^7.20.5",
70
58
  "@types/js-yaml": "^4.0.9",
71
59
  "@types/node": "^24.5.2",
@@ -76,6 +64,10 @@
76
64
  "@types/yargs": "^17.0.33",
77
65
  "@typescript-eslint/eslint-plugin": "^8.46.0",
78
66
  "@typescript-eslint/parser": "^8.46.0",
67
+ "babel-loader": "^8.2.4",
68
+ "compression-webpack-plugin": "^9.0.0",
69
+ "css-loader": "^7.1.2",
70
+ "esbuild-loader": "^2.18.0",
79
71
  "eslint": "^9.37.0",
80
72
  "eslint-config-airbnb": "^19.0.4",
81
73
  "eslint-config-prettier": "^10.1.8",
@@ -90,9 +82,18 @@
90
82
  "knip": "^5.64.2",
91
83
  "lint-staged": "^15.2.10",
92
84
  "memory-fs": "^0.5.0",
85
+ "mini-css-extract-plugin": "^2.9.4",
93
86
  "prettier": "^3.2.5",
87
+ "rspack-manifest-plugin": "^5.0.3",
88
+ "sass-loader": "^16.0.5",
89
+ "swc-loader": "^0.1.15",
94
90
  "thenify": "^3.3.1",
95
- "typescript": "^5.9.2"
91
+ "ts-jest": "^29.4.5",
92
+ "typescript": "^5.9.2",
93
+ "webpack": "5.93.0",
94
+ "webpack-assets-manifest": "^5.0.6",
95
+ "webpack-cli": "^6.0.0",
96
+ "webpack-subresource-integrity": "^5.1.0"
96
97
  },
97
98
  "peerDependencies": {
98
99
  "@babel/core": "^7.17.9",