shakapacker 9.3.0.beta.2 → 9.3.0.beta.4

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.
@@ -101,20 +101,21 @@ export async function run(args: string[]): Promise<number> {
101
101
  setupNodePath(appRoot)
102
102
 
103
103
  // Apply defaults
104
- applyDefaults(options)
104
+ const resolvedOptions = applyDefaults(options)
105
105
 
106
106
  // Validate after defaults are applied
107
- if (options.annotate && options.format !== "yaml") {
107
+ if (resolvedOptions.annotate && resolvedOptions.format !== "yaml") {
108
108
  throw new Error(
109
109
  "Annotation requires YAML format. Use --no-annotate or --format=yaml."
110
110
  )
111
111
  }
112
112
 
113
113
  // Validate --build requires config file
114
- if (options.build) {
115
- const loader = new ConfigFileLoader(options.configFile)
114
+ if (resolvedOptions.build) {
115
+ const loader = new ConfigFileLoader(resolvedOptions.configFile)
116
116
  if (!loader.exists()) {
117
- const configPath = options.configFile || ".bundler-config.yml"
117
+ const configPath =
118
+ resolvedOptions.configFile || "config/shakapacker-builds.yml"
118
119
  throw new Error(
119
120
  `--build requires a config file but ${configPath} not found. Run --init to create it.`
120
121
  )
@@ -122,17 +123,17 @@ export async function run(args: string[]): Promise<number> {
122
123
  }
123
124
 
124
125
  // Execute based on mode
125
- if (options.doctor) {
126
- await runDoctorMode(options, appRoot)
127
- } else if (options.stdout) {
126
+ if (resolvedOptions.doctor) {
127
+ await runDoctorMode(resolvedOptions, appRoot)
128
+ } else if (resolvedOptions.stdout) {
128
129
  // Explicit stdout mode
129
- await runStdoutMode(options, appRoot)
130
- } else if (options.output) {
130
+ await runStdoutMode(resolvedOptions, appRoot)
131
+ } else if (resolvedOptions.output) {
131
132
  // Save to single file
132
- await runSingleFileMode(options, appRoot)
133
+ await runSingleFileMode(resolvedOptions, appRoot)
133
134
  } else {
134
135
  // Default: save to directory
135
- await runSaveMode(options, appRoot)
136
+ await runSaveMode(resolvedOptions, appRoot)
136
137
  }
137
138
 
138
139
  return 0
@@ -153,61 +154,75 @@ Exports webpack or rspack configuration in a verbose, human-readable format
153
154
  for comparison and analysis.
154
155
 
155
156
  QUICK START (for troubleshooting):
156
- bin/export-bundler-config --doctor
157
+ bin/shakapacker-config --doctor
157
158
 
158
159
  Exports annotated YAML configs for both development and production.
159
160
  Creates separate files for client and server bundles.
160
161
  Best for debugging, AI analysis, and comparing configurations.`
161
162
  )
162
- .option("doctor", {
163
+ // Build Configuration Options (most important - users interact with these most)
164
+ .option("init", {
163
165
  type: "boolean",
164
166
  default: false,
165
167
  description:
166
- "Export all configs for troubleshooting (dev + prod, annotated YAML)"
168
+ "Generate config/shakapacker-builds.yml (use with --ssr for SSR builds)"
167
169
  })
168
- .option("save-dir", {
169
- type: "string",
170
- description:
171
- "Directory for output files (default: shakapacker-config-exports)"
170
+ .option("ssr", {
171
+ type: "boolean",
172
+ default: false,
173
+ description: "Include SSR builds when using --init"
172
174
  })
173
- .option("stdout", {
175
+ .option("list-builds", {
174
176
  type: "boolean",
175
177
  default: false,
176
- description: "Output to stdout instead of saving to files"
178
+ description: "List all available builds from config file"
177
179
  })
178
- .option("bundler", {
180
+ .option("build", {
179
181
  type: "string",
180
- choices: ["webpack", "rspack"] as const,
181
- description: "Specify bundler (auto-detected if not provided)"
182
+ description: "Export config for specific build from config file"
182
183
  })
183
- .option("env", {
184
+ .option("all-builds", {
185
+ type: "boolean",
186
+ default: false,
187
+ description: "Export all builds from config file"
188
+ })
189
+ .option("config-file", {
184
190
  type: "string",
185
- choices: ["development", "production", "test"] as const,
186
191
  description:
187
- "Node environment (default: development, ignored with --doctor or --build)"
192
+ "Path to config file (default: config/shakapacker-builds.yml)"
188
193
  })
189
- .option("client-only", {
194
+ // Validation Options
195
+ .option("validate", {
190
196
  type: "boolean",
191
197
  default: false,
192
- description: "Generate only client config (sets CLIENT_BUNDLE_ONLY=yes)"
198
+ description:
199
+ "Validate all builds by running webpack/rspack (requires config file)"
193
200
  })
194
- .option("server-only", {
201
+ .option("validate-build", {
202
+ type: "string",
203
+ description: "Validate specific build from config file"
204
+ })
205
+ // Troubleshooting
206
+ .option("doctor", {
195
207
  type: "boolean",
196
208
  default: false,
197
- description: "Generate only server config (sets SERVER_BUNDLE_ONLY=yes)"
209
+ description:
210
+ "Export all configs for troubleshooting (uses config file builds if available)"
211
+ })
212
+ // Output Options
213
+ .option("save-dir", {
214
+ type: "string",
215
+ description:
216
+ "Directory for output files (default: shakapacker-config-exports)"
198
217
  })
199
218
  .option("output", {
200
219
  type: "string",
201
220
  description: "Output to specific file instead of directory"
202
221
  })
203
- .option("depth", {
204
- type: "number",
205
- default: 20,
206
- coerce: (value: number | string) => {
207
- if (value === "null" || value === null) return null
208
- return typeof value === "number" ? value : parseInt(String(value), 10)
209
- },
210
- description: "Inspection depth (use 'null' for unlimited)"
222
+ .option("stdout", {
223
+ type: "boolean",
224
+ default: false,
225
+ description: "Output to stdout instead of saving to files"
211
226
  })
212
227
  .option("format", {
213
228
  type: "string",
@@ -219,53 +234,54 @@ QUICK START (for troubleshooting):
219
234
  description:
220
235
  "Enable inline documentation (YAML only, default with --doctor or file output)"
221
236
  })
237
+ .option("depth", {
238
+ type: "number",
239
+ default: 20,
240
+ coerce: (value: number | string) => {
241
+ if (value === "null" || value === null) return null
242
+ return typeof value === "number" ? value : parseInt(String(value), 10)
243
+ },
244
+ description: "Inspection depth (use 'null' for unlimited)"
245
+ })
222
246
  .option("verbose", {
223
247
  type: "boolean",
224
248
  default: false,
225
249
  description: "Show full output without compact mode"
226
250
  })
227
- .option("init", {
228
- type: "boolean",
229
- default: false,
230
- description: "Generate sample .bundler-config.yml with examples"
231
- })
232
- .option("config-file", {
233
- type: "string",
234
- description: "Path to config file (default: .bundler-config.yml)"
235
- })
236
- .option("build", {
251
+ // Bundler Options
252
+ .option("bundler", {
237
253
  type: "string",
238
- description: "Export config for specific build from config file"
239
- })
240
- .option("list-builds", {
241
- type: "boolean",
242
- default: false,
243
- description: "List all available builds from config file"
254
+ choices: ["webpack", "rspack"] as const,
255
+ description: "Specify bundler (auto-detected if not provided)"
244
256
  })
245
- .option("all-builds", {
257
+ .option("webpack", {
246
258
  type: "boolean",
247
259
  default: false,
248
- description: "Export all builds from config file"
260
+ description: "Use webpack (overrides config file)"
249
261
  })
250
- .option("validate", {
262
+ .option("rspack", {
251
263
  type: "boolean",
252
264
  default: false,
253
- description:
254
- "Validate all builds by running webpack/rspack (requires config file)"
265
+ description: "Use rspack (overrides config file)"
255
266
  })
256
- .option("validate-build", {
267
+ // Legacy/Fallback Options (when no config file exists)
268
+ .option("env", {
257
269
  type: "string",
258
- description: "Validate specific build from config file"
270
+ choices: ["development", "production", "test"] as const,
271
+ description:
272
+ "Node environment (fallback when no config file exists, ignored with --doctor or --build)"
259
273
  })
260
- .option("webpack", {
274
+ .option("client-only", {
261
275
  type: "boolean",
262
276
  default: false,
263
- description: "Use webpack (overrides config file)"
277
+ description:
278
+ "Generate only client config (fallback when no config file exists)"
264
279
  })
265
- .option("rspack", {
280
+ .option("server-only", {
266
281
  type: "boolean",
267
282
  default: false,
268
- description: "Use rspack (overrides config file)"
283
+ description:
284
+ "Generate only server config (fallback when no config file exists)"
269
285
  })
270
286
  .check((argv) => {
271
287
  if (argv.webpack && argv.rspack) {
@@ -303,6 +319,11 @@ QUICK START (for troubleshooting):
303
319
  "--validate cannot be used with --build or --all-builds."
304
320
  )
305
321
  }
322
+ if (argv.ssr && !argv.init) {
323
+ throw new Error(
324
+ "--ssr can only be used with --init. Use: bin/shakapacker-config --init --ssr"
325
+ )
326
+ }
306
327
  return true
307
328
  })
308
329
  .help("help")
@@ -310,31 +331,27 @@ QUICK START (for troubleshooting):
310
331
  .epilogue(
311
332
  `Examples:
312
333
 
313
- # Config File Workflow
314
- bin/export-bundler-config --init
315
- bin/export-bundler-config --list-builds
316
- bin/export-bundler-config --build=dev
317
- bin/export-bundler-config --all-builds --save-dir=./configs
318
- bin/export-bundler-config --build=dev --rspack
319
-
320
- # Traditional Workflow (without config file)
321
- bin/export-bundler-config --doctor
322
- # Creates: webpack-development-client-hmr.yaml, webpack-development-client.yaml,
323
- # webpack-development-server.yaml, webpack-production-client.yaml,
324
- # webpack-production-server.yaml
325
-
326
- bin/export-bundler-config --env=production --client-only
327
- bin/export-bundler-config --save-dir=./debug
328
- bin/export-bundler-config # Saves to shakapacker-config-exports/
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
-
335
- # View config in terminal (stdout)
336
- bin/export-bundler-config --stdout
337
- bin/export-bundler-config --output=config.yaml # Save to specific file`
334
+ # Config File Workflow (recommended)
335
+ bin/shakapacker-config --init # Create config file
336
+ bin/shakapacker-config --init --ssr # Create config with SSR builds
337
+ bin/shakapacker-config --list-builds # List available builds
338
+ bin/shakapacker-config --build=dev # Export specific build
339
+ bin/shakapacker-config --all-builds --save-dir=./configs
340
+ bin/shakapacker-config --build=dev --rspack # Override bundler
341
+
342
+ # Troubleshooting
343
+ bin/shakapacker-config --doctor # Export all configs for debugging
344
+ # If config file exists: exports all builds from config
345
+ # If no config file: exports dev/prod client/server configs
346
+
347
+ # Validate builds (requires config file)
348
+ bin/shakapacker-config --validate # Validate all builds
349
+ bin/shakapacker-config --validate-build=dev # Validate specific build
350
+ bin/shakapacker-config --validate --verbose # Validate with full logs
351
+
352
+ # Advanced output options
353
+ bin/shakapacker-config --build=dev --stdout # View in terminal
354
+ bin/shakapacker-config --build=dev --output=config.yaml # Save to specific file`
338
355
  )
339
356
  .strict()
340
357
  .parseSync()
@@ -363,6 +380,7 @@ QUICK START (for troubleshooting):
363
380
  stdout: argv.stdout,
364
381
  annotate: argv.annotate,
365
382
  init: argv.init,
383
+ ssr: argv.ssr,
366
384
  configFile: argv["config-file"],
367
385
  build: argv.build,
368
386
  listBuilds: argv["list-builds"],
@@ -372,29 +390,43 @@ QUICK START (for troubleshooting):
372
390
  }
373
391
  }
374
392
 
375
- function applyDefaults(options: ExportOptions): void {
376
- if (options.doctor) {
377
- if (options.format === undefined) options.format = "yaml"
378
- if (options.annotate === undefined) options.annotate = true
379
- } else if (!options.stdout && !options.output) {
393
+ function applyDefaults(options: ExportOptions): ExportOptions {
394
+ const updatedOptions = { ...options }
395
+
396
+ if (updatedOptions.doctor) {
397
+ if (updatedOptions.format === undefined) updatedOptions.format = "yaml"
398
+ if (updatedOptions.annotate === undefined) updatedOptions.annotate = true
399
+ } else if (!updatedOptions.stdout && !updatedOptions.output) {
380
400
  // Default mode: save to directory
381
- if (options.format === undefined) options.format = "yaml"
382
- if (options.annotate === undefined) options.annotate = true
401
+ if (updatedOptions.format === undefined) updatedOptions.format = "yaml"
402
+ if (updatedOptions.annotate === undefined) updatedOptions.annotate = true
383
403
  } else {
384
- if (options.format === undefined) options.format = "inspect"
385
- if (options.annotate === undefined) options.annotate = false
404
+ if (updatedOptions.format === undefined) updatedOptions.format = "inspect"
405
+ if (updatedOptions.annotate === undefined) updatedOptions.annotate = false
386
406
  }
387
407
 
388
408
  // Set default save directory for file output modes
389
- if (!options.stdout && !options.output && !options.saveDir) {
390
- options.saveDir = resolve(process.cwd(), "shakapacker-config-exports")
409
+ if (
410
+ !updatedOptions.stdout &&
411
+ !updatedOptions.output &&
412
+ !updatedOptions.saveDir
413
+ ) {
414
+ updatedOptions.saveDir = resolve(
415
+ process.cwd(),
416
+ "shakapacker-config-exports"
417
+ )
391
418
  }
419
+
420
+ return updatedOptions
392
421
  }
393
422
 
394
423
  function runInitCommand(options: ExportOptions): number {
395
- const configPath = options.configFile || ".bundler-config.yml"
424
+ const configPath = options.configFile || "config/shakapacker-builds.yml"
396
425
  const fullPath = resolve(process.cwd(), configPath)
397
426
 
427
+ // Check if SSR variant is requested via --ssr flag
428
+ const ssrMode = options.ssr || false
429
+
398
430
  if (existsSync(fullPath)) {
399
431
  console.error(
400
432
  `[Config Exporter] Error: Config file already exists: ${fullPath}`
@@ -405,22 +437,78 @@ function runInitCommand(options: ExportOptions): number {
405
437
  return 1
406
438
  }
407
439
 
408
- const sampleConfig = generateSampleConfigFile()
440
+ // Create bin stub if it doesn't exist
441
+ const binStubPath = resolve(process.cwd(), "bin/shakapacker-config")
442
+ const createdStub = !existsSync(binStubPath)
443
+ if (createdStub) {
444
+ createBinStub(binStubPath)
445
+ }
446
+
447
+ const sampleConfig = generateSampleConfigFile(ssrMode)
409
448
  writeFileSync(fullPath, sampleConfig, "utf8")
410
449
 
411
450
  console.log(`[Config Exporter] ✅ Created config file: ${fullPath}`)
451
+ if (ssrMode) {
452
+ console.log(
453
+ `[Config Exporter] ℹ️ Generated SSR build configuration (5 builds)`
454
+ )
455
+ } else {
456
+ console.log(
457
+ `[Config Exporter] ℹ️ Generated standard build configuration (3 builds)`
458
+ )
459
+ console.log(
460
+ `[Config Exporter] 💡 Uncomment SSR builds in the file if needed, or regenerate with: bin/shakapacker-config --init --ssr`
461
+ )
462
+ }
463
+
464
+ if (createdStub) {
465
+ console.log(`[Config Exporter] ✅ Created bin stub: ${binStubPath}`)
466
+ }
467
+
412
468
  console.log(`\nNext steps:`)
413
- console.log(` 1. Edit the config file to match your build setup`)
414
- console.log(
415
- ` 2. List available builds: bin/export-bundler-config --list-builds`
416
- )
417
- console.log(
418
- ` 3. Export a build: bin/export-bundler-config --build=<name> --save\n`
419
- )
469
+ console.log(` 1. List available builds: bin/shakapacker --list-builds`)
470
+ console.log(` 2. Run a build: bin/shakapacker --build <name>\n`)
420
471
 
421
472
  return 0
422
473
  }
423
474
 
475
+ function createBinStub(binStubPath: string): void {
476
+ const binDir = dirname(binStubPath)
477
+ const { mkdirSync, chmodSync } = require("fs")
478
+
479
+ // Ensure bin directory exists
480
+ if (!existsSync(binDir)) {
481
+ mkdirSync(binDir, { recursive: true })
482
+ }
483
+
484
+ const stubContent = `#!/usr/bin/env ruby
485
+ # frozen_string_literal: true
486
+
487
+ ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
488
+ ENV["NODE_ENV"] ||= "development"
489
+
490
+ require "pathname"
491
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
492
+ Pathname.new(__FILE__).realpath)
493
+
494
+ require "bundler/setup"
495
+
496
+ APP_ROOT = File.expand_path("..", __dir__)
497
+ Dir.chdir(APP_ROOT) do
498
+ exec "node", "./node_modules/.bin/shakapacker-config", *ARGV
499
+ end
500
+ `
501
+
502
+ writeFileSync(binStubPath, stubContent, { mode: 0o755 })
503
+
504
+ // Make executable
505
+ try {
506
+ chmodSync(binStubPath, 0o755)
507
+ } catch (e) {
508
+ // chmod might fail on some systems, but mode in writeFileSync should handle it
509
+ }
510
+ }
511
+
424
512
  function runListBuildsCommand(options: ExportOptions): number {
425
513
  try {
426
514
  const loader = new ConfigFileLoader(options.configFile)
@@ -440,7 +528,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
440
528
  // Validate that config file exists
441
529
  const loader = new ConfigFileLoader(options.configFile)
442
530
  if (!loader.exists()) {
443
- const configPath = options.configFile || ".bundler-config.yml"
531
+ const configPath = options.configFile || "config/shakapacker-builds.yml"
444
532
  throw new Error(
445
533
  `Config file ${configPath} not found. Run --init to create it.`
446
534
  )
@@ -473,7 +561,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
473
561
  // Handle empty builds edge case
474
562
  if (buildsToValidate.length === 0) {
475
563
  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.`
564
+ `No builds found in config file. Add at least one build to config/shakapacker-builds.yml or run --init to see examples.`
477
565
  )
478
566
  }
479
567
  }
@@ -575,11 +663,12 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
575
663
  setupNodePath(appRoot)
576
664
 
577
665
  // Apply defaults
578
- applyDefaults(options)
666
+ const resolvedOptions = applyDefaults(options)
579
667
 
580
- const loader = new ConfigFileLoader(options.configFile)
668
+ const loader = new ConfigFileLoader(resolvedOptions.configFile)
581
669
  if (!loader.exists()) {
582
- const configPath = options.configFile || ".bundler-config.yml"
670
+ const configPath =
671
+ resolvedOptions.configFile || "config/shakapacker-builds.yml"
583
672
  throw new Error(
584
673
  `Config file ${configPath} not found. Run --init to create it.`
585
674
  )
@@ -592,7 +681,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
592
681
  `\n📦 Exporting ${buildNames.length} builds from config file...\n`
593
682
  )
594
683
 
595
- const targetDir = options.saveDir! // Set by applyDefaults
684
+ const targetDir = resolvedOptions.saveDir! // Set by applyDefaults
596
685
  const createdFiles: string[] = []
597
686
 
598
687
  // Export each build
@@ -604,16 +693,16 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
604
693
  restoreBuildEnvironmentVariables(savedEnv)
605
694
 
606
695
  // Create a modified options object for this build
607
- const buildOptions = { ...options, build: buildName }
696
+ const buildOptions = { ...resolvedOptions, build: buildName }
608
697
  const configs = await loadConfigsForEnv(undefined, buildOptions, appRoot)
609
698
 
610
699
  for (const { config: cfg, metadata } of configs) {
611
- const output = formatConfig(cfg, metadata, options, appRoot)
700
+ const output = formatConfig(cfg, metadata, resolvedOptions, appRoot)
612
701
  const filename = FileWriter.generateFilename(
613
702
  metadata.bundler,
614
703
  metadata.environment,
615
704
  metadata.configType,
616
- options.format!,
705
+ resolvedOptions.format!,
617
706
  metadata.buildName
618
707
  )
619
708
 
@@ -662,63 +751,65 @@ async function runDoctorMode(
662
751
 
663
752
  const createdFiles: string[] = []
664
753
 
665
- // Check if config file exists with shakapacker_doctor_default_builds_here flag
666
- const configFilePath = options.configFile || ".bundler-config.yml"
754
+ // Check if config file exists - always use it for doctor mode
755
+ const configFilePath = options.configFile || "config/shakapacker-builds.yml"
667
756
  const loader = new ConfigFileLoader(configFilePath)
668
757
 
669
758
  if (loader.exists()) {
670
759
  try {
671
760
  const configData = loader.load()
672
- if (configData.shakapacker_doctor_default_builds_here) {
673
- console.log(
674
- "\nUsing builds from config file (shakapacker_doctor_default_builds_here: true)...\n"
675
- )
676
- // Use config file builds
677
- const buildNames = Object.keys(configData.builds)
761
+ console.log(`\nUsing builds from ${configFilePath}...\n`)
678
762
 
679
- for (const buildName of buildNames) {
680
- console.log(`\n📦 Loading build: ${buildName}`)
763
+ // Use config file builds
764
+ const buildNames = Object.keys(configData.builds)
681
765
 
682
- // Clear and restore environment to prevent leakage between builds
683
- clearBuildEnvironmentVariables()
684
- restoreBuildEnvironmentVariables(savedEnv)
766
+ for (const buildName of buildNames) {
767
+ console.log(`\n📦 Loading build: ${buildName}`)
685
768
 
686
- const configs = await loadConfigsForEnv(
687
- undefined,
688
- { ...options, build: buildName },
689
- appRoot
690
- )
769
+ // Clear and restore environment to prevent leakage between builds
770
+ clearBuildEnvironmentVariables()
771
+ restoreBuildEnvironmentVariables(savedEnv)
691
772
 
692
- for (const { config, metadata } of configs) {
693
- const output = formatConfig(config, metadata, options, appRoot)
694
- const filename = FileWriter.generateFilename(
695
- metadata.bundler,
696
- metadata.environment,
697
- metadata.configType,
698
- options.format!,
699
- metadata.buildName
700
- )
701
- const fullPath = resolve(targetDir, filename)
702
- FileWriter.writeSingleFile(fullPath, output)
703
- createdFiles.push(fullPath)
704
- }
705
- }
773
+ const configs = await loadConfigsForEnv(
774
+ undefined,
775
+ { ...options, build: buildName },
776
+ appRoot
777
+ )
706
778
 
707
- // Print summary and exit early
708
- printDoctorSummary(createdFiles, targetDir)
709
- return
779
+ for (const { config, metadata } of configs) {
780
+ const output = formatConfig(config, metadata, options, appRoot)
781
+ const filename = FileWriter.generateFilename(
782
+ metadata.bundler,
783
+ metadata.environment,
784
+ metadata.configType,
785
+ options.format!,
786
+ metadata.buildName
787
+ )
788
+ const fullPath = resolve(targetDir, filename)
789
+ FileWriter.writeSingleFile(fullPath, output)
790
+ createdFiles.push(fullPath)
791
+ }
710
792
  }
793
+
794
+ // Print summary and exit early
795
+ printDoctorSummary(createdFiles, targetDir)
796
+ return
711
797
  } catch (error: unknown) {
712
- // If config file exists but is invalid, warn and fall through to default behavior
798
+ // If config file exists but is invalid, show error and exit
713
799
  const errorMessage =
714
800
  error instanceof Error ? error.message : String(error)
715
- console.log(`\n⚠️ Config file found but invalid: ${errorMessage}`)
716
- console.log("Falling back to default doctor mode...\n")
801
+ console.error(`\nConfig file found but invalid: ${errorMessage}`)
802
+ console.error(
803
+ `Fix the config file or run: bin/shakapacker-config --init\n`
804
+ )
805
+ throw error
717
806
  }
718
807
  }
719
808
 
720
- // Default behavior: hardcoded configs
721
- console.log("\nExporting all development and production configs...")
809
+ // No config file found - suggest creating one
810
+ console.log(`\n⚠️ No build config file found at ${configFilePath}`)
811
+ console.log(`Run: bin/shakapacker-config --init to create one.\n`)
812
+ console.log("Exporting default development and production configs...")
722
813
  console.log("")
723
814
 
724
815
  const configsToExport = [