Skip to content

Add config export utility for verbose webpack/rspack analysis#647

Merged
justin808 merged 31 commits intomainfrom
justin808/config-export-util
Oct 9, 2025
Merged

Add config export utility for verbose webpack/rspack analysis#647
justin808 merged 31 commits intomainfrom
justin808/config-export-util

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Oct 6, 2025

Summary

  • Adds bin/export-bundler-config utility for exporting webpack/rspack configurations
  • Supports doctor mode for comprehensive troubleshooting (dev + prod, client + server)
  • Supports save mode for exporting configs to files
  • Supports stdout mode for viewing configs in terminal
  • Outputs in YAML, JSON, or inspect format with optional inline documentation
  • Includes smart environment isolation to ensure dev/prod configs are truly different
  • Pretty-prints functions and validates output paths for security

Motivation

When comparing webpack/rspack configurations across different environments or debugging configuration issues, it's helpful to have a utility that can export the entire resolved configuration in a verbose, inspectable format. This is particularly useful for:

  • Troubleshooting: Compare client vs server, development vs production configs
  • Migration: Analyze differences when migrating from webpack to rspack
  • AI Analysis: Export configs in structured YAML format for LLM analysis
  • Documentation: Generate annotated configs with inline documentation

Key Features

🏥 Doctor Mode (Recommended)

bin/export-bundler-config --doctor
  • Exports all configs: dev + prod, client + server
  • Creates organized directory: shakapacker-config-exports/
  • Annotated YAML format with inline documentation
  • Smart .gitignore suggestions
  • Environment isolation ensures accurate dev/prod differences

💾 Save Mode

# Save current environment
bin/export-bundler-config --save

# Save to custom directory
bin/export-bundler-config --save --save-dir=./debug

# Save production client only
bin/export-bundler-config --save --env=production --client-only

📺 Stdout Mode

# View in terminal (default)
bin/export-bundler-config

# JSON output
bin/export-bundler-config --format=json

# Production config
bin/export-bundler-config --env=production

Implementation Highlights

Smart Environment Isolation

  • Clears require.cache for config files AND shakapacker modules
  • Ensures NODE_ENV/RAILS_ENV changes are properly detected
  • Fixes issue where dev/prod configs would be identical

YAML Serialization

  • Custom serializer handles functions, RegExp, complex objects
  • Pretty-prints functions (up to 50 lines, cleaned indentation)
  • Escapes special characters in RegExp patterns
  • Removes excessive blank lines in arrays
  • Optional inline documentation from webpack/rspack docs

Security & Validation

  • Validates bundler value from shakapacker.yml (prevents typos)
  • Sanitizes output filenames to prevent path traversal
  • Validates output paths (must be in CWD or temp directory)

User Experience

  • Auto-detects bundler from shakapacker.yml
  • Decorative console output with progress indicators
  • Helpful error messages and warnings
  • Comprehensive --help documentation

Usage Examples

# RECOMMENDED: Export everything for troubleshooting
bin/export-bundler-config --doctor

# Save current environment configs
bin/export-bundler-config --save

# Export only client config for production
bin/export-bundler-config --save --env=production --client-only

# View config in terminal
bin/export-bundler-config

# View production config as JSON
bin/export-bundler-config --env=production --format=json

# Using rake task
bundle exec rake shakapacker:export_bundler_config

Test Plan

  • Tested doctor mode with dev + prod, client + server configs
  • Verified environment isolation (dev/prod configs are different)
  • Tested save mode with custom directories
  • Tested stdout mode with JSON/YAML/inspect formats
  • Tested bundler validation with invalid values
  • Tested RegExp serialization with special characters
  • Verified YAML formatting (no excessive blank lines)
  • Passed bundle exec rubocop
  • Passed yarn lint
  • Tested in real React on Rails demo app

Files Changed

New Files

  • package/configExporter/cli.ts - Main CLI logic with doctor/save/stdout modes
  • package/configExporter/yamlSerializer.ts - Custom YAML serializer
  • package/configExporter/fileWriter.ts - Safe file writing with validation
  • package/configExporter/index.ts - Public API
  • lib/install/bin/export-bundler-config - Binary for new installations
  • lib/tasks/shakapacker/export_bundler_config.rake - Rake task wrapper

Modified Files

  • docs/troubleshooting.md - Added comprehensive export utility documentation
  • .gitignore - Added shakapacker-config-exports/

Breaking Changes

None - this is a new utility.

Related Issues

Lays groundwork for future config diff feature tracked in #667

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Introduced bin/export-bundler-config CLI and a Rake task to export webpack/rspack configs for dev/prod, client/server, to files or stdout, in YAML/JSON with optional annotations.
    • Added “doctor” export flow for quick troubleshooting and comparison.
  • Documentation
    • Expanded troubleshooting guide with detailed export use cases, examples, and a new section on generating webpack stats.
  • Chores
    • Ignored shakapacker-config-exports/ in version control.
  • Refactor/UX
    • Doctor output now includes guidance on exporting configs and where to find help.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 6, 2025

Warning

Rate limit exceeded

@justin808 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 33 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c522d26 and 8cfb76b.

📒 Files selected for processing (7)
  • CHANGELOG.md (1 hunks)
  • README.md (1 hunks)
  • docs/rspack_migration_guide.md (1 hunks)
  • lib/shakapacker/doctor.rb (3 hunks)
  • lib/tasks/shakapacker/export_bundler_config.rake (1 hunks)
  • package/configExporter/yamlSerializer.ts (1 hunks)
  • spec/shakapacker/doctor_spec.rb (2 hunks)

Walkthrough

Adds a config-exporting toolchain: a Node CLI shim and gem shim, a Rake task, a TypeScript config-exporter package (CLI, serializer, file writer, types, docs), docs updates for usage, and small doctor output guidance referencing the new tool.

Changes

Cohort / File(s) Summary
Docs: troubleshooting updates
docs/troubleshooting.md
Expanded troubleshooting with full documentation for bin/export-bundler-config, usage examples (doctor/save/stdout, envs, formats, annotate/verbose), quick-troubleshooting flow, and webpack stats guidance.
Doctor guidance
lib/shakapacker/doctor.rb
Appends informational lines to diagnostic output recommending bin/export-bundler-config (doctor mode) and --help; no control-flow changes.
CLI shims (project + gem install)
bin/export-bundler-config, lib/install/bin/export-bundler-config
Add minimal Node shims that require shakapacker/configExporter and call run(args), forwarding exit codes and printing errors on failure.
Rake task integration
lib/tasks/shakapacker/export_bundler_config.rake
Adds shakapacker:export_bundler_config task that prefers local bin/export-bundler-config and falls back to gem-provided script; forwards args and uses exec.
Package exports
package.json
Adds "./configExporter": "./package/configExporter/index.js" to package exports.
Config exporter CLI & barrel
package/configExporter/cli.ts, package/configExporter/index.ts
New CLI run(args: string[]): Promise<number> implementing doctor/save/stdout modes, bundler auto-detection, config loading/normalization, formatting, validation, and orchestration; barrel re-exports run, types, serializer, file writer, and docs helper.
Serializer / File output
package/configExporter/yamlSerializer.ts, package/configExporter/fileWriter.ts
YAML serializer with optional inline docs, path relativization, function/RegExp handling, nested structures; FileWriter for single/multiple outputs, filename generation, and output path validation.
Docs & key map for annotations
package/configExporter/configDocs.ts
Adds CONFIG_DOCS mapping and helpers getDocForKey / getDocForKeyWithFallback for per-key inline documentation.
Types
package/configExporter/types.ts
Adds ExportOptions, ConfigMetadata, and FileOutput TypeScript interfaces.
Package entrypoints
package/configExporter/*.ts, package/configExporter/index.ts
New package modules (cli, types, yamlSerializer, fileWriter, configDocs) and an index barrel exposing key APIs.
Git ignore
.gitignore
Ignore shakapacker-config-exports/ directory.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant Rake as Rake Task<br/>(shakapacker:export_bundler_config)
  participant Bin as bin/export-bundler-config
  participant Shim as lib/install/bin/export-bundler-config
  participant NodePkg as package/configExporter (run)
  participant AppCfg as App Configs
  participant Writer as FileWriter / YamlSerializer
  participant Out as Output (stdout/files)

  Dev->>Rake: rake shakapacker:export_bundler_config [args]
  alt binstub present
    Rake->>Bin: exec with forwarded args
    Bin->>NodePkg: require(...).run(args)
  else fallback
    Rake->>Shim: exec node install/bin/export-bundler-config [args]
    Shim->>NodePkg: require(...).run(args)
  end

  Note right of NodePkg: parse args, detect bundler/env, load configs
  NodePkg->>AppCfg: loadConfigsForEnv(env, bundler)
  alt save/write
    NodePkg->>Writer: writeMultipleFiles / writeSingleFile(outputs)
    Writer-->>Out: files on disk
  else stdout
    NodePkg-->>Out: printed YAML/JSON/inspect
  end
Loading
sequenceDiagram
  autonumber
  actor Dev as Developer
  participant Doctor as shakapacker Doctor
  participant Console as Console Output

  Dev->>Doctor: run diagnostics
  Doctor-->>Console: print fix command
  Doctor-->>Console: print empty line
  Doctor-->>Console: print export-bundler-config guidance (doctor mode) and `--help` hint
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • tomdracz
  • G-Rath

Poem

In burrows of code I hop and peep,
I fetch your bundles, tidy and neat,
A shim and rake to call my name,
I write your YAML, stamp my claim,
Exports done — the build sleeps tight. 🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the main change by describing the addition of a configuration export utility for detailed webpack and rspack analysis, making it clear and specific for future readers.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Oct 6, 2025

Code Review for PR #647: Config Export Utility

I've reviewed the pull request and here's my detailed feedback:

✅ Strengths

  1. Well-structured and comprehensive: The implementation is thorough with good error handling, validation, and user-friendly help text
  2. Good code organization: Clear separation of concerns with logical flow from argument parsing → validation → config loading → output generation
  3. Flexible design: Multiple output formats (inspect/json), bundle types, and environments make this very versatile
  4. Follows project patterns: Correctly placed in lib/install/bin/ for installation and includes a rake task wrapper following existing conventions
  5. Good documentation: Extensive inline comments and usage examples in both the code and PR description

🔍 Code Quality Issues

1. Rake Task Missing Environment Dependency (lib/tasks/shakapacker/export_config.rake:5)

The rake task doesn't have :environment dependency like other tasks in the codebase (e.g., doctor.rake). This could cause issues since it references Rails.root. However, looking at info.rake, some tasks don't require :environment. Since this task runs a Node script, :environment may not be necessary, but you should verify Rails.root is available without it.

2. Error Handling in Rake Task (lib/tasks/shakapacker/export_config.rake:16)

The system call doesn't check the return value. Consider using exec or checking the result for better error handling.

3. Argument Parsing Fragility (bin/export-config:95-111)

Using arg.split('=')[1] doesn't handle edge cases like empty values, multiple equals signs, or space-separated arguments. Consider more robust parsing or document the limitation.

4. NODE_PATH Platform Dependency (bin/export-config:75-76)

Uses colon as path separator, which won't work on Windows. Consider using path.delimiter from Node's path module or document Unix-only support.

5. Potential Security Issue (bin/export-config:303)

Using require(configFile) with a user-controlled path could be risky if the config file path can be manipulated. While auto-detection mitigates this, consider validating the resolved path is within expected directories.

🐛 Potential Bugs

  1. String Comparison in findAppRoot (bin/export-config:44)
    currentDir !== '/' uses strict inequality which is fine, but on Windows this would never match root. Consider using path utilities or document Unix-only.

  2. Argument Consumption in Rake Task (lib/tasks/shakapacker/export_config.rake:16)
    ARGV[1..] will pass all rake arguments, including the task name itself in some contexts. This could lead to unexpected behavior. Consider using a more explicit argument passing mechanism.

⚡ Performance Considerations

  1. Module Path Initialization (bin/export-config:80)
    Calling Module._initPaths() is an internal API and may not be reliable across Node versions. Consider documenting Node version requirements or using a more stable approach.

  2. Unlimited Depth Inspection (bin/export-config:351)
    Default depth: null could cause performance issues or stack overflows with circular references in large configs. While webpack/rspack configs are typically safe, consider a reasonable default (e.g., 10) with option to override.

🔒 Security Concerns

  1. Process Environment Mutation (bin/export-config:179-192)
    The script mutates process.env globally. While acceptable for a CLI tool, this could cause issues if the config loading has side effects or spawns child processes. Consider documenting this behavior.

  2. No Input Sanitization for File Paths (bin/export-config:105)
    User-provided --output path isn't validated. Could write to arbitrary locations. Consider validating or at least documenting this.

📝 Test Coverage

Missing Tests: No automated tests were added for this functionality. Consider adding:

  1. Unit tests for the rake task to verify it calls the binary correctly
  2. Integration tests for the script to test different argument combinations and error cases
  3. Fixture-based tests with sample webpack/rspack configs

📋 Additional Suggestions

  1. Installation Verification: Add a check in shakapacker:verify_install or shakapacker:check_binstubs to verify bin/export-config is present and executable

  2. Documentation: Consider adding this utility to the main README or creating a doc/CONFIG_EXPORT.md file

  3. Linting Compliance: The file has many ESLint rules disabled at the top. While this is pragmatic for a standalone script, consider whether some rules could be followed

  4. Trailing Newline: ✅ Both files correctly end with trailing newlines as required by CLAUDE.md

🎯 Recommendations

Before Merging:

  1. Fix the rake task's system call error handling
  2. Add at least basic integration tests
  3. Consider the argument parsing edge cases
  4. Document platform limitations (Unix-only) if applicable

Nice to Have:

  1. Add verification to existing tasks
  2. Improve argument parsing robustness
  3. Add reasonable default depth limit
  4. Consider Windows compatibility

Overall Assessment

This is a well-implemented and useful utility that adds significant debugging value to Shakapacker. The code quality is good, and it follows project conventions. With the suggested fixes, particularly around error handling and testing, this would be production-ready.

The PR description shows thorough testing and attention to detail. Great work! 👍

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
docs/troubleshooting.md (1)

26-46: Add a language hint to the new shell block

markdownlint is flagging this fence (MD040). Please tag it as Bash to keep lint clean.

-   ```bash
+   ```bash
    # Export production config for analysis
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16f6d71 and 0a0c50f.

📒 Files selected for processing (5)
  • bin/export-config (1 hunks)
  • docs/troubleshooting.md (12 hunks)
  • lib/install/bin/export-config (1 hunks)
  • lib/shakapacker/doctor.rb (1 hunks)
  • lib/tasks/shakapacker/export_config.rake (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
lib/shakapacker/**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

When changing lib/shakapacker/**/*.rb, run the corresponding spec at spec/shakapacker/**/*_spec.rb

Files:

  • lib/shakapacker/doctor.rb
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
PR: shakacode/shakapacker#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T02:23:21.827Z
Learning: This gem supports both webpack and rspack configurations
📚 Learning: 2025-09-28T02:23:21.827Z
Learnt from: CR
PR: shakacode/shakapacker#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T02:23:21.827Z
Learning: This gem supports both webpack and rspack configurations

Applied to files:

  • lib/install/bin/export-config
  • lib/tasks/shakapacker/export_config.rake
  • bin/export-config
  • docs/troubleshooting.md
📚 Learning: 2025-09-28T02:23:21.827Z
Learnt from: CR
PR: shakacode/shakapacker#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T02:23:21.827Z
Learning: Applies to lib/shakapacker/**/*.rb : When changing `lib/shakapacker/**/*.rb`, run the corresponding spec at `spec/shakapacker/**/*_spec.rb`

Applied to files:

  • lib/tasks/shakapacker/export_config.rake
🪛 GitHub Actions: Ruby based checks
lib/shakapacker/doctor.rb

[error] 834-834: Shakapacker::Doctor with optional peer dependencies doctor reporting with optional dependencies when running full diagnostic provides helpful information about optional dependencies

🪛 markdownlint-cli2 (0.18.1)
docs/troubleshooting.md

52-52: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.0, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.0, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Test with RSpack
  • GitHub Check: Test with Webpack
  • GitHub Check: claude-review
  • GitHub Check: test
🔇 Additional comments (1)
lib/shakapacker/doctor.rb (1)

832-835: Align Doctor specs with the new guidance lines

The added instructions change the doctor report text, and the CI run is already failing the Shakapacker::Doctor ... provides helpful information about optional dependencies spec. Please update the corresponding expectation (or shared fixture) so the test covers the new export-config guidance. See the pipeline failure log linked in the run.

Comment thread bin/export-bundler-config Outdated
Comment on lines +398 to +406
let config
try {
config = require(configFile)
console.log(`[Config Exporter] Config loaded successfully`)
} catch (error) {
console.error(`[Config Exporter] Error loading config:`, error)
process.exit(1)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Support TypeScript configs before requiring them

When the project’s config is webpack.config.ts/rspack.config.ts, require(configFile) throws ERR_UNKNOWN_FILE_EXTENSION because we never register ts-node. Load the TS handler (following the main binstub’s approach) before the require, and unwrap module.default exports so the exporter works for TypeScript setups.

+if (configFile.endsWith(".ts")) {
+  try {
+    require("ts-node/register/transpile-only")
+  } catch (error) {
+    console.error("[Config Exporter] TypeScript config detected but ts-node/register is unavailable.")
+    console.error("Install ts-node (dev dependency) so the exporter can execute TypeScript configs.")
+    process.exit(1)
+  }
+}
+
 config = require(configFile)
+if (config && config.__esModule && config.default) {
+  config = config.default
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let config
try {
config = require(configFile)
console.log(`[Config Exporter] Config loaded successfully`)
} catch (error) {
console.error(`[Config Exporter] Error loading config:`, error)
process.exit(1)
}
let config
try {
if (configFile.endsWith(".ts")) {
try {
require("ts-node/register/transpile-only")
} catch (error) {
console.error("[Config Exporter] TypeScript config detected but ts-node/register is unavailable.")
console.error("Install ts-node (dev dependency) so the exporter can execute TypeScript configs.")
process.exit(1)
}
}
config = require(configFile)
if (config && config.__esModule && config.default) {
config = config.default
}
console.log(`[Config Exporter] Config loaded successfully`)
} catch (error) {
console.error(`[Config Exporter] Error loading config:`, error)
process.exit(1)
}
🤖 Prompt for AI Agents
In bin/export-config around lines 398–406, requiring a TypeScript config throws
ERR_UNKNOWN_FILE_EXTENSION because we never register the TS handler; load the
same TS registration used by the main binstub (i.e. require the project's
TS/register module or ts-node/register variant) before calling
require(configFile), then after require unwrap module.default if present (use
config = module.default ?? module) so .ts config files work; keep the existing
try/catch and log/exit behavior.

Comment thread lib/install/bin/export-config Outdated
Comment on lines +398 to +406
let config
try {
config = require(configFile)
console.log(`[Config Exporter] Config loaded successfully`)
} catch (error) {
console.error(`[Config Exporter] Error loading config:`, error)
process.exit(1)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Support TypeScript configs before requiring them

We already search for .ts configs in the detection step, but require(configFile) will throw ERR_UNKNOWN_FILE_EXTENSION when the project’s config is written in TypeScript (a common setup for Shakapacker apps). We need to register ts-node (or the same helper we use in the main binstub) before requiring the file, otherwise the exporter exits immediately. Please load the TS loader and, after requiring, unwrap any default export. Mirror the fix here and in bin/export-config.

+  if (configFile.endsWith(".ts")) {
+    try {
+      require("ts-node/register/transpile-only")
+    } catch (error) {
+      console.error("[Config Exporter] TypeScript config detected but ts-node/register is unavailable.")
+      console.error("Install ts-node (dev dependency) so the exporter can execute TypeScript configs.")
+      process.exit(1)
+    }
+  }
+
   config = require(configFile)
+  if (config && config.__esModule && config.default) {
+    config = config.default
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let config
try {
config = require(configFile)
console.log(`[Config Exporter] Config loaded successfully`)
} catch (error) {
console.error(`[Config Exporter] Error loading config:`, error)
process.exit(1)
}
let config
try {
if (configFile.endsWith(".ts")) {
try {
require("ts-node/register/transpile-only")
} catch (error) {
console.error("[Config Exporter] TypeScript config detected but ts-node/register is unavailable.")
console.error("Install ts-node (dev dependency) so the exporter can execute TypeScript configs.")
process.exit(1)
}
}
config = require(configFile)
if (config && config.__esModule && config.default) {
config = config.default
}
console.log(`[Config Exporter] Config loaded successfully`)
} catch (error) {
console.error(`[Config Exporter] Error loading config:`, error)
process.exit(1)
}
🤖 Prompt for AI Agents
In lib/install/bin/export-config around lines 398 to 406, the code uses
require(configFile) which will fail for TypeScript configs; before requiring the
config, require and initialize the project's TS loader (e.g. ts-node/register or
the same helper used by the main binstub) so .ts files can be imported, then
require the config file and if the imported module has a .default export, unwrap
it to get the actual config object; apply the same change to the sibling
bin/export-config script so both paths handle TypeScript configs consistently.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
bin/export-bundler-config (1)

395-403: Handle TypeScript configs before requiring them

webpack.config.ts/rspack.config.ts currently blow up with ERR_UNKNOWN_FILE_EXTENSION, and default-exported configs remain wrapped. Mirror the main binstub: register ts-node before require, bail with guidance if it’s missing, and unwrap any module.default afterwards.

+if (configFile.endsWith(".ts")) {
+  try {
+    require("ts-node/register/transpile-only")
+  } catch (error) {
+    console.error(
+      "[Config Exporter] TypeScript config detected but ts-node/register is unavailable."
+    )
+    console.error(
+      "Install ts-node (dev dependency) so the exporter can execute TypeScript configs."
+    )
+    process.exit(1)
+  }
+}
+
 config = require(configFile)
+if (config && typeof config === "object" && "default" in config) {
+  config = config.default
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a0c50f and 70a1f87.

📒 Files selected for processing (5)
  • bin/export-bundler-config (1 hunks)
  • docs/troubleshooting.md (12 hunks)
  • lib/install/bin/export-bundler-config (1 hunks)
  • lib/shakapacker/doctor.rb (1 hunks)
  • lib/tasks/shakapacker/export_bundler_config.rake (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/shakapacker/doctor.rb
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
PR: shakacode/shakapacker#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T02:23:21.827Z
Learning: This gem supports both webpack and rspack configurations
📚 Learning: 2025-09-28T02:23:21.827Z
Learnt from: CR
PR: shakacode/shakapacker#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T02:23:21.827Z
Learning: Test changes with both bundlers (webpack and rspack) when modifying core functionality

Applied to files:

  • bin/export-bundler-config
📚 Learning: 2025-09-28T02:23:21.827Z
Learnt from: CR
PR: shakacode/shakapacker#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T02:23:21.827Z
Learning: Applies to lib/shakapacker/**/*.rb : When changing `lib/shakapacker/**/*.rb`, run the corresponding spec at `spec/shakapacker/**/*_spec.rb`

Applied to files:

  • lib/tasks/shakapacker/export_bundler_config.rake
📚 Learning: 2025-09-28T02:23:21.827Z
Learnt from: CR
PR: shakacode/shakapacker#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T02:23:21.827Z
Learning: This gem supports both webpack and rspack configurations

Applied to files:

  • lib/tasks/shakapacker/export_bundler_config.rake
  • docs/troubleshooting.md
🪛 markdownlint-cli2 (0.18.1)
docs/troubleshooting.md

60-60: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Testing (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.6.1.x)
  • GitHub Check: Testing (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.6.0.x)
  • GitHub Check: Test Bundler Switching
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.0, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.0, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: test
  • GitHub Check: claude-review

Comment thread docs/troubleshooting.md Outdated
Comment thread lib/install/bin/export-bundler-config Outdated
Comment on lines +395 to +403
let config
try {
config = require(configFile)
console.log(`[Config Exporter] Config loaded successfully`)
} catch (error) {
console.error(`[Config Exporter] Error loading config:`, error)
process.exit(1)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Handle TypeScript configs before requiring them

Requiring webpack.config.ts/rspack.config.ts fails (ERR_UNKNOWN_FILE_EXTENSION) because we never register the TS loader, and TS configs using export default stay wrapped in module.default. Register ts-node (same as the main binstub) before the require, and unwrap the default export afterwards so .ts configs work end to end.

+```diff
+if (configFile.endsWith(".ts")) {

  • try {
  • require("ts-node/register/transpile-only")
  • } catch (error) {
  • console.error(
  •  "[Config Exporter] TypeScript config detected but ts-node/register is unavailable."
    
  • )
  • console.error(
  •  "Install ts-node (dev dependency) so the exporter can execute TypeScript configs."
    
  • )
  • process.exit(1)
  • }
    +}

config = require(configFile)
+if (config && typeof config === "object" && "default" in config) {

  • config = config.default
    +}

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
let config
try {
  if (configFile.endsWith(".ts")) {
    try {
      require("ts-node/register/transpile-only")
    } catch (error) {
      console.error(
        "[Config Exporter] TypeScript config detected but ts-node/register is unavailable."
      )
      console.error(
        "Install ts-node (dev dependency) so the exporter can execute TypeScript configs."
      )
      process.exit(1)
    }
  }

  config = require(configFile)
  if (config && typeof config === "object" && "default" in config) {
    config = config.default
  }

  console.log(`[Config Exporter] Config loaded successfully`)
} catch (error) {
  console.error(`[Config Exporter] Error loading config:`, error)
  process.exit(1)
}
🤖 Prompt for AI Agents
In lib/install/bin/export-bundler-config around lines 395 to 403, the code
directly requires the config file which breaks for TypeScript configs
(ERR_UNKNOWN_FILE_EXTENSION) and leaves default-exported configs under
module.default; before calling require, register ts-node the same way the main
binstub does (import/require and configure ts-node/register or
ts-node/register/transpile-only as appropriate) so .ts files can be required,
then after loading unwrap module.default if present (use config = config &&
config.default ? config.default : config) and proceed; keep existing try/catch
and logging behavior.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (3)
package/configExporter/fileWriter.ts (2)

19-22: Sanitize filenames to prevent path traversal and accidental absolute paths

If output.filename is absolute or contains separators, resolve(targetDir, output.filename) can escape targetDir. Use basename to constrain writes to targetDir.

Apply this diff:

-      const filePath = resolve(targetDir, output.filename)
+      const safeName = require("path").basename(output.filename)
+      const filePath = resolve(targetDir, safeName)
       this.writeFile(filePath, output.content)

13-27: Call validateOutputPath before writing to disk

Currently validateOutputPath isn’t used. Invoke it to warn users automatically.

Apply this diff:

   writeMultipleFiles(outputs: FileOutput[], targetDir: string): void {
     // Ensure directory exists
     this.ensureDirectory(targetDir)
 
     // Write each file
     outputs.forEach((output) => {
-      const filePath = resolve(targetDir, output.filename)
+      const safeName = require("path").basename(output.filename)
+      const filePath = resolve(targetDir, safeName)
+      this.validateOutputPath(filePath)
       this.writeFile(filePath, output.content)
       console.log(`[Config Exporter] Created: ${filePath}`)
     })
@@
   writeSingleFile(filePath: string, content: string): void {
     // Ensure parent directory exists
     const dir = dirname(filePath)
     this.ensureDirectory(dir)
 
+    this.validateOutputPath(filePath)
     this.writeFile(filePath, content)
     console.log(`[Config Exporter] Config exported to: ${filePath}`)
   }

Also applies to: 32-39

package/configExporter/yamlSerializer.ts (1)

1-222: Consider delegating YAML emission to js-yaml and customizing only special cases

Manually emitting YAML is brittle (quoting, edge cases, anchors, escapes). Use js-yaml for core serialization and post-process functions/RegExps as strings or via custom types. This reduces correctness risks and maintenance.

Based on learnings (js-yaml 4.1.0): Prefer yaml.dump(obj, { lineWidth: 120, noRefs: true, sortKeys: true }) and pre-walk the config to stringify functions/RegExps; or define custom types for them.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70a1f87 and 9942a70.

📒 Files selected for processing (9)
  • bin/export-bundler-config (1 hunks)
  • lib/install/bin/export-bundler-config (1 hunks)
  • package.json (1 hunks)
  • package/configExporter/cli.ts (1 hunks)
  • package/configExporter/configDocs.ts (1 hunks)
  • package/configExporter/fileWriter.ts (1 hunks)
  • package/configExporter/index.ts (1 hunks)
  • package/configExporter/types.ts (1 hunks)
  • package/configExporter/yamlSerializer.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
package/configExporter/cli.ts (2)
package/configExporter/types.ts (3)
  • ExportOptions (1-15)
  • FileOutput (32-36)
  • ConfigMetadata (17-30)
package/configExporter/yamlSerializer.ts (1)
  • makePathRelative (208-221)

Comment thread package/configExporter/cli.ts
Comment thread package/configExporter/cli.ts
Comment thread package/configExporter/fileWriter.ts
Comment thread package/configExporter/yamlSerializer.ts
Comment thread package/configExporter/yamlSerializer.ts
Comment thread package/configExporter/yamlSerializer.ts
Comment thread package/configExporter/yamlSerializer.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/troubleshooting.md (1)

78-80: Add the fenced code language.

This block still needs a language hint for markdownlint (MD040). Please change the opening fence to ```bash.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9942a70 and 471fc3e.

📒 Files selected for processing (2)
  • docs/troubleshooting.md (12 hunks)
  • lib/shakapacker/doctor.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/shakapacker/doctor.rb
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-28T02:23:21.827Z
Learnt from: CR
PR: shakacode/shakapacker#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T02:23:21.827Z
Learning: This gem supports both webpack and rspack configurations

Applied to files:

  • docs/troubleshooting.md
🪛 markdownlint-cli2 (0.18.1)
docs/troubleshooting.md

78-78: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Testing (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Testing (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.0.x)

Comment thread docs/troubleshooting.md
@justin808
Copy link
Copy Markdown
Member Author

@claude review this PR

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Oct 8, 2025

Claude finished @justin808's task —— View job


PR Review: Config Export Utility

I've completed a thorough review of this PR. This is a well-designed and useful utility that will significantly help users debug webpack/rspack configurations. However, there are several critical issues that must be addressed before merging, plus some important improvements.


🔴 Critical Issues

1. TypeScript Config Support Missing (cli.ts:278, both bin files)

Impact: The utility will crash with ERR_UNKNOWN_FILE_EXTENSION when users have TypeScript config files (webpack.config.ts / rspack.config.ts), which is a common setup.

Fix Required: Before require(configFile) on line 278 in cli.ts:

// Handle TypeScript configs
if (configFile.endsWith(".ts")) {
  try {
    require("ts-node/register/transpile-only")
  } catch (error) {
    throw new Error(
      "TypeScript config detected but ts-node/register/transpile-only could not be loaded. " +
      "Install ts-node as a dev dependency to execute TypeScript configs."
    )
  }
}

delete require.cache[require.resolve(configFile)]
let loadedConfig = require(configFile)

// Unwrap default exports from ES modules
if (loadedConfig && typeof loadedConfig === "object" && "default" in loadedConfig) {
  loadedConfig = loadedConfig.default
}

Apply the same fix to both bin/export-bundler-config and lib/install/bin/export-bundler-config.

Reference: CodeRabbit correctly identified this issue in their review.


2. Default Values Not Applied (cli.ts:126-138)

Impact: The format and annotate options are initialized as undefined in parseArguments, but applyDefaults checks for null. This means defaults never apply, causing:

  • Files named like webpack-production-client.undefined
  • Wrong output format
  • Missing annotations

Fix Required:

function applyDefaults(options: ExportOptions): void {
  if (options.doctor) {
    options.save = true
  }

  if (options.format === undefined) {
    options.format = options.doctor || options.save ? "yaml" : "inspect"
  }

  if (options.annotate === undefined) {
    options.annotate = Boolean(options.doctor || options.save)
  }
}

Reference: CodeRabbit identified this in their review at cli.ts:138.


3. YAML Serialization Produces Invalid YAML

Multiple Problems:

a) Function serialization breaks (yamlSerializer.ts:109-126): Functions containing quotes are wrapped in raw double quotes without escaping:

private serializeFunction(fn: Function): string {
  const source = fn.toString()
  const compacted = source
    .split("\n")
    .map((line) => line.trim())
    .filter((line) => line.length > 0)
    .join(" ")

  if (compacted.length > 500) {
    return this.serializeString(compacted.substring(0, 500) + "...")  // ← Use existing escaper
  }

  return this.serializeString(compacted)  // ← Use existing escaper
}

b) Array items with objects/multiline mis-indented (yamlSerializer.ts:128-149): Nested objects in arrays lose indentation:

private serializeArray(arr: any[], indent: number, keyPath: string): string {
  if (arr.length === 0) return "[]"

  const lines: string[] = []
  const itemIndent = " ".repeat(indent + 2)
  const nestedIndent = indent + 4

  arr.forEach((item, index) => {
    const itemPath = `${keyPath}[${index}]`

    if (item && typeof item === "object" && !Array.isArray(item)) {
      // Nested object → dash on own line, then indented block
      lines.push(`${itemIndent}-`)
      lines.push(this.serializeObject(item, nestedIndent, itemPath))
    } else if (Array.isArray(item)) {
      lines.push(`${itemIndent}-`)
      lines.push(this.serializeArray(item, nestedIndent, itemPath))
    } else if (typeof item === "string" && item.includes("\n")) {
      lines.push(`${itemIndent}- |`)
      for (const line of item.split("\n")) {
        lines.push(`${" ".repeat(nestedIndent)}${line}`)
      }
    } else {
      const serialized = this.serializeValue(item, indent + 2, itemPath)
      lines.push(`${itemIndent}- ${serialized}`)
    }
  })

  return "\n" + lines.join("\n")
}

c) Multiline string values under keys (yamlSerializer.ts:161-202): Block scalars need proper indentation:

// In serializeObject, before const serialized = ... line:
if (typeof value === "string" && value.includes("\n")) {
  lines.push(`${keyIndent}${key}: |`)
  for (const line of value.split("\n")) {
    lines.push(`${valueIndent}${line}`)
  }
  return  // early return
}

Reference: CodeRabbit identified all three YAML issues.


4. Path Validation Issues (fileWriter.ts:72-81)

Problems:

  • startsWith(cwd) incorrectly allows sibling directories (e.g., /proj2 starts with /proj)
  • Hardcoded /tmp is POSIX-only, won't work on Windows

Fix Required:

import { resolve, dirname, relative, isAbsolute } from "path"
import { tmpdir } from "os"

validateOutputPath(outputPath: string): void {
  const absPath = resolve(outputPath)
  const cwd = process.cwd()

  const isWithin = (base: string, target: string) => {
    const rel = relative(base, target)
    return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))
  }

  if (!isWithin(cwd, absPath) && !isWithin(tmpdir(), absPath)) {
    console.warn(
      `[Config Exporter] Warning: Writing to ${absPath} which is outside ` +
      `current directory (${cwd}) or temp (${tmpdir()})`
    )
  }
}

5. Windows Path Handling (yamlSerializer.ts:208-221)

Problem: makePathRelative uses hardcoded / separator and string startsWith, failing on Windows.

Fix Required:

import { relative, isAbsolute } from "path"

private makePathRelative(str: string): string {
  if (typeof str !== "string") return str

  try {
    const rel = relative(this.appRoot, str)
    const within = rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))
    if (within) return rel === "" ? "." : `./${rel}`
  } catch {
    // fall through
  }
  return str
}

🟡 Major Issues

6. Missing Tests

No automated tests were added for this significant new feature. The PR description mentions manual testing, but lacks:

  • Unit tests for YAML serialization (especially edge cases)
  • Integration tests for CLI argument parsing
  • Tests for TypeScript config loading
  • Tests for the rake task

Recommendation: Add at least basic integration tests before merge. Consider testing:

  • YAML output validity (can it be parsed back?)
  • Different config types (client/server/both)
  • Error handling for missing configs

7. Doctor.rb Spec Failure

The changes to lib/shakapacker/doctor.rb (lines 832-836) break existing specs. The GitHub Actions run shows:

Shakapacker::Doctor with optional peer dependencies doctor reporting...
provides helpful information about optional dependencies [FAILED]

Fix Required: Update the corresponding spec to expect the new guidance text about bin/export-bundler-config --doctor.


8. validateOutputPath Never Called

The FileWriter.validateOutputPath method is defined but never invoked, so the validation warning never appears.

Fix Required: Call it before writes:

writeSingleFile(filePath: string, content: string): void {
  const dir = dirname(filePath)
  this.ensureDirectory(dir)
  
  this.validateOutputPath(filePath)  // ← Add this
  this.writeFile(filePath, content)
  console.log(`[Config Exporter] Config exported to: ${filePath}`)
}

Also add to writeMultipleFiles before the forEach loop.


9. Filename Sanitization Missing (fileWriter.ts:19-22)

If output.filename contains path separators or is absolute, it could escape targetDir.

Fix Required:

import { basename } from "path"

writeMultipleFiles(outputs: FileOutput[], targetDir: string): void {
  this.ensureDirectory(targetDir)

  outputs.forEach((output) => {
    const safeName = basename(output.filename)  // ← Sanitize
    const filePath = resolve(targetDir, safeName)
    this.validateOutputPath(filePath)
    this.writeFile(filePath, output.content)
    console.log(`[Config Exporter] Created: ${filePath}`)
  })
  // ...
}

🔵 Minor Issues & Suggestions

10. Consider Using js-yaml for YAML

The manual YAML serialization in yamlSerializer.ts is complex and error-prone. Since js-yaml is already a dependency (I see it in package.json:44), consider using it for serialization and only customizing function/RegExp handling via custom types or pre-processing.

Benefits:

  • Eliminates indentation bugs
  • Handles edge cases (anchors, escaping, Unicode)
  • Less maintenance burden

Reference: CodeRabbit suggested this.


11. Markdownlint Warning (docs/troubleshooting.md:78-80)

Missing language hint on fenced code block triggers MD040:

-```
+```bash
 NODE_ENV=development bin/shakapacker --profile --json > /tmp/webpack-stats.json

---

#### 12. **Project Guidelines Compliance**

✅ **Good**:
- All files end with trailing newlines ✓
- Follows existing code patterns ✓
- Good separation of concerns ✓

⚠️ **Missing**:
- `bundle exec rubocop` mentioned in test plan but no RSpec tests added
- Should run corresponding spec for `lib/shakapacker/doctor.rb` changes

---

### 📊 Overall Assessment

**Strengths**:
- Well-designed CLI with good UX (--doctor mode, clear help text)
- Comprehensive functionality (multiple formats, environments, bundle types)
- Good code organization and documentation
- Addresses a real pain point for users debugging configs
- TypeScript refactoring makes the code more maintainable

**Concerns**:
- TypeScript config support is **blocking** - will break for many users
- YAML serialization issues produce **invalid output** - blocking
- Default value bug causes wrong filenames - blocking
- No automated tests for a utility this complex
- Several cross-platform issues (Windows paths)

**Verdict**: ⚠️ **Changes Requested**

This is excellent work and the feature is very valuable, but the critical issues (especially TypeScript support, YAML validity, and default value handling) must be fixed before merge. The YAML bugs in particular mean the utility won't work correctly for its primary use case.

---

### 🎯 Required Before Merge

**Must Fix** (Blocking):
1. ✅ TypeScript config support (cli.ts + bin files)
2. ✅ Fix default value logic (cli.ts:126-138)
3. ✅ Fix YAML function serialization (yamlSerializer.ts:109-126)
4. ✅ Fix YAML array indentation (yamlSerializer.ts:128-149)
5. ✅ Fix YAML multiline strings (yamlSerializer.ts:161-202)
6. ✅ Fix doctor.rb spec failure
7. ✅ Fix path validation (fileWriter.ts:72-81)
8. ✅ Add validateOutputPath calls
9. ✅ Sanitize filenames in writeMultipleFiles

**Should Fix** (Recommended):
- Add basic integration tests
- Fix markdownlint warning
- Consider js-yaml for YAML generation

---

Great work on this feature! Once these issues are addressed, this will be a fantastic addition to Shakapacker. The --doctor mode in particular will be incredibly helpful for troubleshooting.

---

*[View run](https://github.com/shakacode/shakapacker/actions/runs/1) • Branch: `justin808/config-export-util`*

@justin808
Copy link
Copy Markdown
Member Author

@claude review this PR

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Oct 8, 2025

Claude finished @justin808's task —— View job


Code Review for PR #647: Config Export Utility

I've completed a comprehensive review of this PR. This is excellent work that adds significant value for debugging webpack/rspack configurations. The TypeScript refactoring is well-structured and the feature is thoughtfully designed. However, I found that all critical issues identified by CodeRabbit have already been fixed in recent commits! 🎉


✅ Previously Critical Issues - NOW FIXED

1. ✅ TypeScript Config Support (cli.ts:277-290)

Status: Fixed in commit 0b2275f

The code now properly handles TypeScript configs:

if (configFile.endsWith(".ts")) {
  require("ts-node/register/transpile-only")
}
// ...
if (typeof loadedConfig === "object" && "default" in loadedConfig) {
  loadedConfig = loadedConfig.default
}

2. ✅ Default Value Logic (cli.ts:126-138)

Status: Fixed in commits 0b2275f and e5e3e54

Defaults now correctly check for undefined instead of null:

if (options.format === undefined) {
  options.format = options.doctor || options.save ? "yaml" : "inspect"
}

3. ✅ YAML Function Serialization (yamlSerializer.ts:111-129)

Status: Fixed in commit 0b2275f

Functions now use serializeString() for proper escaping:

return this.serializeString(compacted)  // ✓ Properly escaped

4. ✅ YAML Array Indentation (yamlSerializer.ts:131-163)

Status: Fixed in commit 0b2275f

Arrays with nested objects now properly indent content:

if (typeof item === "object" && !Array.isArray(item) && item !== null) {
  lines.push(`${itemIndent}-`)
  serialized.split("\n").forEach((line: string) => {
    lines.push(contentIndent + line)
  })
}

5. ✅ YAML Multiline Strings (yamlSerializer.ts:188-192)

Status: Fixed in commit 0b2275f

Multiline strings now use block scalar syntax:

if (typeof value === "string" && value.includes("\n")) {
  lines.push(`${keyIndent}${key}: |`)
  for (const line of value.split("\n")) {
    lines.push(`${valueIndent}${line}`)
  }
}

6. ✅ Path Validation (fileWriter.ts:76-89)

Status: Fixed in commit 0b2275f

Now uses path.relative() and os.tmpdir() for cross-platform support:

const isWithin = (base: string, target: string) => {
  const rel = relative(base, target)
  return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))
}
if (!isWithin(cwd, absPath) && !isWithin(tmpdir(), absPath)) {
  console.warn(...)
}

7. ✅ Cross-Platform Path Handling (yamlSerializer.ts:226-243)

Status: Fixed in commit 0b2275f

Now uses Node's path.relative() for Windows compatibility:

const rel = relative(this.appRoot, str)
if (rel === "") return "."
if (rel.startsWith("..") || isAbsolute(rel)) return str
return "./" + rel

8. ✅ Filename Sanitization (fileWriter.ts:19-25)

Status: Fixed in commit 4062ada

Filenames are now sanitized with basename():

const safeName = basename(output.filename)
const filePath = resolve(targetDir, safeName)

9. ✅ validateOutputPath Called (fileWriter.ts:22, 40)

Status: Fixed in commit 4062ada

Validation is now invoked before writes:

this.validateOutputPath(filePath)  // ✓ Called
this.writeFile(filePath, output.content)

🟢 Code Quality Assessment

Strengths

  1. Excellent TypeScript Architecture

    • Clean separation of concerns (CLI, serializer, file writer, docs)
    • Well-typed interfaces in types.ts
    • Good abstraction with YamlSerializer and FileWriter classes
  2. Comprehensive Feature Set

    • --doctor mode for complete troubleshooting (brilliant!)
    • Multiple output formats (YAML, JSON, inspect)
    • Inline documentation with --annotate
    • Client/server bundle filtering
    • Auto-detection of bundler from shakapacker.yml
  3. Great User Experience

    • Clear help text with examples
    • Sensible defaults (YAML + annotate for --save, inspect for stdout)
    • Auto-generated filenames (webpack-production-client.yaml)
    • Integration with shakapacker doctor
  4. Cross-Platform Support

    • Uses Node's path and os modules correctly
    • delimiter for NODE_PATH (cli.ts:542)
    • tmpdir() instead of hardcoded /tmp
  5. Error Handling

    • Good validation in validateOptions() (cli.ts:140-162)
    • Clear error messages
    • Try-catch blocks with user-friendly output

🟡 Minor Issues & Suggestions

1. Documentation Reference Inconsistency (docs/troubleshooting.md)

Severity: Minor (cosmetic)

The troubleshooting docs were updated in commit 471fc3e to reference the new utility, but all examples still use the correct command name bin/export-bundler-config. No action needed.

2. Markdown Linting (docs/troubleshooting.md:78-80)

Severity: Trivial

One fenced code block is missing a language hint:

-```
+```bash
 NODE_ENV=development bin/shakapacker --profile --json > /tmp/webpack-stats.json

### 3. Consider Using js-yaml for Serialization
**Severity**: Suggestion (not required)

CodeRabbit suggested using `js-yaml`'s `dump()` instead of manual YAML generation. While the current implementation works correctly, using the library would:
- Eliminate edge case bugs
- Handle anchors, Unicode, complex escaping
- Reduce maintenance burden

You could pre-process the config to convert functions/RegExps to strings, then use:
```typescript
yaml.dump(processedConfig, { 
  lineWidth: 120, 
  noRefs: true, 
  sortKeys: true 
})

However, the current manual approach gives you more control over formatting and annotations, which is valuable for this use case. Either approach is valid.

4. Test Coverage

Severity: Recommended

No automated tests were added for this new functionality. Consider adding:

  • Unit tests for YAML serialization edge cases
  • Integration tests for CLI argument parsing
  • Tests for the rake task
  • Fixture-based tests with sample configs

Per CLAUDE.md guidelines, you should also update the doctor.rb spec since you modified that file (line 832-836).


📋 Project Guidelines Compliance

✅ Followed Correctly

  • All files end with trailing newlines ✓
  • Code follows existing patterns ✓
  • Good separation of concerns ✓
  • Documentation is comprehensive ✓
  • Feature branch workflow ✓

⚠️ Missing Per Guidelines

  • No corresponding RSpec tests (CLAUDE.md says: "Run corresponding RSpec tests when changing source files")
  • Should run spec/shakapacker/doctor_spec.rb after changing lib/shakapacker/doctor.rb
  • Should add tests for new functionality before merge

🔒 Security Review

✅ No Security Issues Found

  1. Path Validation: Properly warns when writing outside cwd/tmp ✓
  2. Filename Sanitization: Uses basename() to prevent path traversal ✓
  3. Input Validation: Validates bundler, env, format options ✓
  4. No Command Injection: Uses require() and file APIs, not exec()
  5. Environment Variables: Only reads, doesn't expose sensitive data ✓

🎯 Recommendations

Before Merge (Recommended)

  1. Fix markdown lint warning (trivial, 1 line)
  2. Add basic RSpec tests for doctor.rb changes
  3. Consider adding integration test for the utility

Nice to Have (Optional)

  1. Consider using js-yaml for serialization
  2. Add more comprehensive test coverage
  3. Document the TypeScript module in package/configExporter/README.md

💯 Overall Assessment

Rating: ⭐⭐⭐⭐½ (4.5/5)

This is outstanding work! The feature is well-designed, thoroughly implemented, and the recent commits addressed all the critical issues. The --doctor mode is particularly brilliant for troubleshooting.

Major Kudos:

  • All CodeRabbit critical issues have been fixed ✓
  • TypeScript refactoring makes code more maintainable ✓
  • Cross-platform path handling is correct ✓
  • YAML serialization handles edge cases properly ✓
  • User experience is excellent (help text, defaults, examples) ✓

Only Missing:

  • Automated tests (recommended but not blocking)
  • One trivial markdown lint fix

The PR is in excellent shape and ready to merge after addressing the minor items above (or merge as-is if you prefer to add tests later).


📝 Summary

This PR successfully adds a powerful config export utility that will significantly help users debug webpack/rspack configurations. The implementation quality is high, all critical issues have been resolved, and it follows Shakapacker's patterns well.

Great job, Justin! 👏


View run • Branch: justin808/config-export-util

justin808 and others added 18 commits October 8, 2025 19:18
This commit adds a new bin/export-config utility and rake task for
exporting webpack or rspack configurations in a verbose, human-readable
format. This tool is useful for comparing configurations across different
environments, bundle types, and bundlers.

Features:
- Auto-detects bundler from shakapacker.yml or accepts --bundler flag
- Supports all environments: development, production, test
- Supports bundle types: client, server, both (via env vars like WEBPACK_SERVE)
- Two output formats: inspect (default, colored) and json
- Outputs to file or stdout
- Includes metadata about export context (env vars, timestamps, etc.)

Usage examples:
  bin/export-config --bundle-type=client
  bin/export-config --env=production --bundle-type=both --format=json --output=config.json
  bundle exec rake shakapacker:export_config -- --bundler=rspack

Files added:
- bin/export-config: Main Node.js CLI utility
- lib/install/bin/export-config: Installable version for new projects
- lib/tasks/shakapacker/export_config.rake: Rake task wrapper

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This commit addresses security and cross-platform concerns in the config
export utility based on code review feedback.

Changes:
- Use path.delimiter instead of hardcoded ':' for NODE_PATH (Windows support)
- Fix root directory detection to work on Windows (use path.sep)
- Improve argument parsing with validation for empty values
- Add default depth of 10 to prevent stack overflow with circular refs
- Add path validation warning when writing outside current directory
- Replace system() with exec() in rake task for proper exit code handling
- Use Shakapacker::Env.current_dir instead of Rails.root (no :environment dependency)
- Document Module._initPaths() as internal API with version requirement

Security improvements:
- Validate output paths and warn on suspicious locations
- Prevent empty argument values
- Better error handling throughout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This commit enhances documentation and tooling around the new config
export utility to make it more discoverable for users debugging issues
or performing migrations.

Documentation updates (docs/troubleshooting.md):
- Add comprehensive section on exporting webpack/rspack configs
- Highlight three key use cases: migrations, debugging, AI analysis
- Provide practical examples for common scenarios:
  - Comparing webpack vs rspack configs
  - Exporting client/server bundle configs
  - Using JSON format for programmatic analysis
- Reorder existing documentation for better flow

Doctor command integration (lib/shakapacker/doctor.rb):
- Add helpful reminder at end of doctor output
- Suggest running export-config when issues are found
- Include current environment in suggestion
- Point users to --help for more options

Benefits:
- Users debugging config issues will discover the export tool via doctor
- Migration paths between webpack/rspack are clearer
- Easier troubleshooting with AI tools (ChatGPT, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…nv.current_dir

The export_config rake task was incorrectly trying to use
Shakapacker::Env.current_dir which doesn't exist. Changed to use
Rails.root which is the standard approach used by other rake tasks
in the codebase (see info.rake, binstubs.rake).

Fixes NoMethodError when running: rake shakapacker:export_config

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Users upgrading shakapacker shouldn't need to reinstall binstubs just to
use the new export-config utility. The rake task now:

1. First tries to use bin/export-config if it exists (installed via binstubs)
2. Falls back to using the gem's version directly with a helpful note
3. Suggests running 'rake shakapacker:binstubs' to install the binstub

This provides a seamless experience for existing users while still
encouraging the binstub installation for better performance and
convenience.

Before:
  - Would error if bin/export-config wasn't installed
  - Required running shakapacker:binstubs first

After:
  - Works immediately without any installation
  - Provides a helpful note about installing the binstub
  - Both paths tested and working

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The verbose output was overwhelming with noise:
- 100+ environment variables from EnvironmentPlugin (repeated per bundle)
- Long absolute paths taking up space
- Functions shown as '[Function: name]' without useful info

This commit adds --compact and --show-functions flags:

--compact (recommended):
- Filters EnvironmentPlugin to only show webpack/shakapacker relevant vars
  (NODE_ENV, RAILS_ENV, WEBPACK_SERVE, etc.)
- Converts absolute paths to relative paths (./public/packs vs /full/path/...)
- Reduces output by ~120 lines while keeping all important config

--show-functions:
- Includes full function source code for generators/loaders
- Useful for debugging custom webpack functions
- Verbose, so off by default

Example usage:
  bin/export-config --compact  # Cleaner output (recommended)
  bin/export-config --compact --show-functions  # See function code

Benefits:
- Easier to compare configs (less noise to scroll through)
- Better for AI analysis (more signal, less noise)
- Paths are portable (relative vs absolute)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Based on feedback, made --compact mode even better:

1. **Completely remove EnvironmentPlugin keys and defaultValues**:
   - Before: Filtered to ~10 relevant vars, still showing keys/defaultValues arrays
   - After: Removes keys/defaultValues entirely from EnvironmentPlugin
   - Saves even more lines of noise

2. **Always show function source code in compact mode**:
   - Before: Functions shown as '[Function: functionName]' (not useful)
   - After: Shows actual function source, compacted to single line
   - Example: 'generate: function(seed, files) { return files.reduce(...) }'
   - Useful for understanding webpack loaders, generators, and hooks

3. **Remove --show-functions flag** (no longer needed):
   - Compact mode now always includes function source
   - Functions are compacted (whitespace removed) to keep output dense

Results:
- **171 lines removed** from original (724 → 553 lines)
- **24% smaller** while showing MORE useful information
- EnvironmentPlugin completely clean (no env var noise)
- Functions are inspectable (not opaque)
- Paths are relative (portable)

Perfect for comparing configs or feeding to AI tools.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The name 'export-config' was too generic. 'export-bundler-config' makes it
clear this utility exports webpack/rspack bundler configurations specifically.

Changes:
- Renamed bin/export-config → bin/export-bundler-config
- Renamed lib/install/bin/export-config → lib/install/bin/export-bundler-config
- Renamed rake task: export_config → export_bundler_config
- Updated all documentation references in docs/troubleshooting.md
- Updated doctor.rb to reference new name
- Added --compact flag to all examples (recommended default)
- Updated help text and usage examples in the script

Usage:
  bin/export-bundler-config --compact
  rake shakapacker:export_bundler_config -- --compact

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…React on Rails compatibility

This change improves the export-bundler-config utility to better support
React on Rails applications that use separate client and server webpack configs.

Key improvements:
- Replace --bundle-type=client|server|both with --client-only and --server-only flags
- Add validation to ensure --client-only and --server-only are mutually exclusive
- Set CLIENT_BUNDLE_ONLY=yes for --client-only (instead of WEBPACK_SERVE)
- Set SERVER_BUNDLE_ONLY=yes for --server-only
- Default behavior (no flags) generates both client and server configs
- Improve multi-config array output handling:
  - Display total config count when N > 1
  - Label each config in array: "--- Config [0] ---", "--- Config [1] ---"
  - Apply cleaning to each config individually in compact mode
  - Add configCount to metadata
- Remove WEBPACK_SERVE from metadata (no longer used)
- Update all documentation and examples
- Function source code is always shown (removed --show-functions flag)
- Compact mode is now the default (use --verbose for raw output)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Webpack/rspack configs can have deeply nested structures (e.g.,
module.rules[].use[].options.jsc.parser.syntax is depth 7). Setting
depth to 20 ensures we don't truncate nested config objects.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Created modular TypeScript architecture in package/configExporter/:
- types.ts: Interfaces for ExportOptions, ConfigMetadata, FileOutput
- configDocs.ts: Documentation mapping for webpack/rspack config keys

This is the foundation for the --doctor and --save features.
Next steps: yamlSerializer, fileWriter, cli modules.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Complete core TypeScript modules for config export feature:
- yamlSerializer.ts: Serializes configs to YAML with optional inline docs
  - Handles functions (show source), RegExp, special objects
  - Makes paths relative for cleaner output
  - Adds documentation comments when --annotate enabled
- fileWriter.ts: Writes config files with smart naming
  - Generates filenames: {bundler}-{env}-{type}.{ext}
  - Supports multi-file output (one per config)
  - Creates directories as needed

Still TODO: cli.ts (main CLI logic), then compile and test.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Major refactoring from 502-line bin script to modular TypeScript:

**New TypeScript Modules** (package/configExporter/):
- cli.ts: Main CLI logic (~571 lines) with arg parsing, validation, config loading
- index.ts: Public API exports
- Fixed type errors and added package.json export

**Simplified bin script**:
- Reduced from 502 lines to 11 lines
- Now just a shim that calls the TS module
- All logic moved to maintainable TypeScript

**Features Implemented**:
- --doctor: Export both dev + prod configs (annotated YAML)
- --save: Export current env with auto-generated filenames
- --save-dir: Specify output directory
- Multi-file output (one per config type: client/server)
- YAML format with inline documentation
- Backward compatible with all existing flags

**File naming**: {bundler}-{env}-{type}.{ext}
Examples: webpack-development-client.yaml, rspack-production-server.yaml

Help command working. Next: test functionality, update docs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Updated troubleshooting guide and doctor command:

**docs/troubleshooting.md**:
- Prominently feature --doctor as RECOMMENDED quick troubleshooting
- Show all new flags: --save, --save-dir, --no-annotate
- Document auto-generated filename pattern
- Explain YAML annotations and multi-file output
- Add comparison workflow examples

**lib/shakapacker/doctor.rb**:
- Update suggestion to use --doctor flag
- Explain it exports dev + prod configs with annotations
- Better help text for users

The --doctor flag is now the recommended way to export configs
for troubleshooting, AI analysis, and configuration comparison.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
**Path handling fixes:**
- Replace string startsWith checks with path.relative for proper cross-platform path validation
- Fix makePathRelative to use path.relative instead of hardcoded "/" separator
- Update validateOutputPath to use os.tmpdir() instead of hardcoded "/tmp"
- Add proper isAbsolute guards to prevent false positives on Windows

**YAML serialization fixes:**
- Fix multiline string indentation to use dynamic indent based on nesting level
- Fix array item indentation to properly indent nested objects and multiline strings
- Emit array markers on separate lines for complex content with proper 2-space sub-indentation
- Fix function source escaping to use serializeString helper for proper quote/special char handling

**TypeScript config loading:**
- Add ts-node/register/transpile-only support for loading .ts config files
- Handle ES module default exports by checking for .default property
- Prevent ERR_UNKNOWN_FILE_EXTENSION errors when requiring TypeScript configs

All changes maintain backward compatibility and improve reliability across platforms.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Changed null checks to undefined checks in applyDefaults() so that defaults
are properly applied when values are unset. Previously, parseArguments set
options to undefined but applyDefaults checked for null, causing defaults
to never apply.

This ensures:
- --doctor mode defaults to YAML + annotated when not explicitly set
- --save mode defaults to YAML + annotated when not explicitly set
- stdout mode defaults to inspect + no annotations when not explicitly set
- Explicitly provided flags (including false) are never overwritten

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
**YAML Serialization Fixes:**
- Fix multiline string values under object keys to use proper block scalar (|) syntax
- Multiline strings in objects now properly indented with valueIndent

**File Security & Validation:**
- Add basename() sanitization to prevent path traversal in writeMultipleFiles
- Call validateOutputPath() in both writeSingleFile and writeMultipleFiles
- Ensure filenames cannot escape target directory

**Documentation:**
- Add bash language hint to code fence at line 78 (fixes MD040 markdownlint warning)

All changes address issues identified in code review while maintaining backward compatibility.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
**Output organization:**
- Create dedicated `shakapacker-config-exports/` subdirectory for --doctor output
- Add directory to .gitignore to keep working directory tidy

**YAML formatting improvements:**
- Remove extra blank lines between array items for cleaner output
- Pretty-print functions with proper indentation instead of single-line compacting
- Add documentation comments for array items (including plugins)
- Skip empty lines in serialized content

**Environment isolation:**
- Fix dev/prod configs being identical by clearing module cache more aggressively
- Clear all webpack/rspack config modules from require cache between environment loads
- Ensures NODE_ENV changes properly affect config generation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@justin808 justin808 force-pushed the justin808/config-export-util branch from 4062ada to 441c850 Compare October 9, 2025 05:21
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Oct 9, 2025

Claude finished @justin808's task —— View job


Code Review for PR #647: Config Export Utility

I've completed a thorough review of this PR. This is an excellent and well-designed feature that adds significant value for debugging webpack/rspack configurations. The TypeScript refactoring is clean and the implementation is comprehensive. However, I found one critical issue that must be fixed before merging.


🔴 Critical Issue

RegExp/String Escaping Missing Backslash Handling (yamlSerializer.ts:105)

Severity: Critical - Can produce invalid YAML output

The string serialization at line 105 in yamlSerializer.ts only escapes double quotes but not backslashes. This affects RegExp serialization (which calls serializeString) and any string values containing backslashes.

Problem Example:

  • RegExp: /\d+/ → toString() → /\d+/ → YAML: /\d+/ (unquoted, OK)
  • RegExp: /"/ → toString() → /"/ → needs quoting → "/"/" (broken!)
  • RegExp: /\"/ → toString() → /\"/ → needs quoting → "/\"/" (broken - backslash not escaped!)

Current code (line 105):

return `"${cleaned.replace(/"/g, '\\"')}"`

Fixed code (escape backslashes FIRST, then quotes):

// Escape backslashes first, then quotes to avoid double-escaping
return `"${cleaned.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`

Why this matters: Webpack/rspack configs commonly use RegExp patterns with backslashes (e.g., /\.js$/, /node_modules/). If these patterns also contain characters that trigger quoting (:, #, ', "), the output YAML will be invalid and won't parse correctly.

Note: I see commit 9b773fd titled "Fix RegExp serialization to properly escape quotes and backslashes" but the actual code at line 105 doesn't escape backslashes. This fix needs to be applied.


✅ Previously Critical Issues - NOW FIXED

Excellent work! All the critical issues identified by CodeRabbit in previous reviews have been properly addressed:

  1. TypeScript Config Support (cli.ts:321-330) - Fixed in c522d26

    • Properly registers ts-node/register/transpile-only with try-catch
    • Has helpful error message directing users to install ts-node
    • Unwraps ES module default exports (lines 358-360)
  2. Default Value Logic (cli.ts:126-138) - Fixed in a48414b

    • Correctly checks undefined instead of null
    • Defaults apply properly: YAML+annotate for save/doctor, inspect for stdout
  3. YAML Function Serialization (yamlSerializer.ts:111-136) - Fixed in 51d3b8e

    • Functions use serializeString() for proper escaping (line 135)
    • Maintains readable formatting with line limits and truncation
  4. YAML Array Indentation (yamlSerializer.ts:138-184) - Fixed in 41acc21

    • Nested objects emit dash on separate line with proper content indentation
    • Complex array items (objects, multiline strings) are correctly structured
  5. YAML Multiline Strings (yamlSerializer.ts:209-213) - Fixed in 51d3b8e

    • Uses block scalar syntax (|) with proper indentation
    • Each line correctly indented under the key
  6. Path Validation (fileWriter.ts:78-91) - Fixed in da8ea55

    • Uses path.relative() and os.tmpdir() for correct cross-platform containment checks
    • Warns when writing outside cwd or temp directory
  7. Cross-Platform Path Handling (yamlSerializer.ts:247-264) - Fixed in da8ea55

    • Uses Node's path.relative() and isAbsolute() instead of string operations
    • Works correctly on Windows and Unix systems
  8. Filename Sanitization (fileWriter.ts:20) - Fixed in 51d3b8e

    • Uses basename() to prevent path traversal attacks
  9. validateOutputPath Called (fileWriter.ts:22, 40) - Fixed in 51d3b8e

    • Validation invoked before all write operations
  10. Bundler Validation (cli.ts:542-547) - Fixed in 5d1302f

    • Validates bundler value from shakapacker.yml against allowed values
    • Warns and defaults to "webpack" for typos or invalid values

📋 Code Quality Assessment

Strengths ⭐⭐⭐⭐⭐

  1. Excellent TypeScript Architecture

    • Clean separation: CLI, serializer, file writer, config docs as separate modules
    • Well-typed interfaces (ExportOptions, ConfigMetadata, FileOutput)
    • Good abstractions with YamlSerializer and FileWriter classes
    • Proper use of private methods for internal logic
  2. Comprehensive Feature Set

    • Doctor mode (--doctor): Exports all configs for complete troubleshooting
    • Save mode (--save): Export current environment with auto-naming
    • Stdout mode (default): View in terminal without files
    • Multiple output formats: YAML (with optional annotations), JSON, inspect
    • Client/server bundle filtering
    • Auto-detection of bundler from shakapacker.yml
    • Helpful CLI with extensive examples in --help
  3. Brilliant User Experience

    • --doctor flag is perfect for "just export everything" use case
    • Clear, descriptive help text with real-world examples
    • Sensible defaults (YAML+annotate for files, inspect for stdout)
    • Auto-generated filenames: webpack-production-client.yaml
    • Pretty progress output with decorative separators
    • Helpful .gitignore suggestions
    • Integration with shakapacker doctor output
  4. Cross-Platform Support

    • Uses path.delimiter for NODE_PATH (cli.ts:619)
    • Uses os.tmpdir() instead of hardcoded /tmp
    • Uses path.relative() for containment checks
    • Proper handling of Windows vs Unix path separators
  5. Robust Error Handling

    • Comprehensive validation in validateOptions() (cli.ts:140-162)
    • Clear, actionable error messages
    • Try-catch blocks with user-friendly output
    • Graceful fallbacks (e.g., bundler detection)
  6. Smart Environment Isolation (cli.ts:333-352)

    • Clears require cache for config files AND shakapacker modules
    • Critical for doctor mode: ensures dev/prod configs are actually different
    • Clears env module so env.nodeEnv is re-read
    • Handles both npm-installed and local development shakapacker
  7. Security Conscious

    • Validates output paths (warns if writing outside cwd/tmp)
    • Sanitizes filenames to prevent path traversal
    • Validates bundler value to prevent injection

🟡 Minor Issues & Suggestions

1. Documentation Integration (docs/troubleshooting.md)

Well done! The documentation is comprehensive with:

  • Clear installation steps
  • Multiple usage examples
  • Explanation of when to use each mode
  • Comparison workflow examples

The only minor note: Line 78 has a code block without a language hint (markdown lint warning), but this is trivial.

2. Doctor.rb Integration (lib/shakapacker/doctor.rb:877-892)

Great addition! The guidance about using bin/export-bundler-config --doctor in the fix instructions is very helpful. However:

  • Specs may need updating: The new output text might break existing expectations in spec/shakapacker/doctor_spec.rb
  • Per CLAUDE.md: "When changing lib/shakapacker/**/*.rb, run the corresponding spec"

3. Rake Task (lib/tasks/shakapacker/export_bundler_config.rake)

Excellent implementation!

  • Graceful fallback when binstub not installed
  • Clear user guidance
  • Proper use of exec for signal handling
  • Correct argument forwarding with ARGV[1..]

4. Consider js-yaml for YAML Generation (Suggestion, Not Required)

The manual YAML serialization is well-implemented and handles edge cases correctly. However, using js-yaml's dump() with custom types for functions/RegExp could:

  • Reduce maintenance burden
  • Handle more edge cases automatically (anchors, Unicode, complex nesting)
  • Eliminate the backslash escaping bug

But the current approach gives you fine control over formatting and annotations, which is valuable. Either approach is valid.


🧪 Test Coverage

Issue: No automated tests were added for this significant new feature.

Missing:

  • Unit tests for YAML serialization edge cases:
    • Functions with quotes/backslashes
    • RegExp with special characters (/"/, /\d+/, etc.)
    • Multiline strings with special characters
    • Nested objects and arrays
    • Path relativization
  • Integration tests for CLI:
    • Argument parsing
    • Different modes (doctor, save, stdout)
    • Error handling
    • TypeScript config loading
  • Tests for rake task execution
  • Tests for doctor.rb changes (lines 831-842, 877-892)

Per CLAUDE.md Guidelines:

When changing lib/shakapacker/**/*.rb, run the corresponding spec at spec/shakapacker/**/*_spec.rb

Action Required:

bundle exec rspec spec/shakapacker/doctor_spec.rb
# Update expectations if the new output text breaks tests

Recommendation: Add at least basic integration tests before or shortly after merge. Consider:

  • Test that YAML output can be parsed back by js-yaml
  • Test different config types (client/server/both)
  • Test error handling for missing configs
  • Fixture-based tests with sample webpack/rspack configs

🎯 Required Before Merge

Must Fix (Blocking):

  1. ⚠️ Fix backslash escaping (yamlSerializer.ts:105)

    return `"${cleaned.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
  2. ⚠️ Update doctor specs (if broken by new output)

    bundle exec rspec spec/shakapacker/doctor_spec.rb
    # Update expectations in spec file if needed
  3. ⚠️ Run linters (per CLAUDE.md requirements)

    bundle exec rubocop
    yarn lint

Recommended (Strongly Advised):

  • Add basic integration tests for the utility
  • Add unit tests for YAML serialization edge cases

💯 Overall Assessment

Rating: ⭐⭐⭐⭐½ (4.5/5)

This is outstanding work! The feature is thoughtfully designed, comprehensively implemented, and solves a real pain point for debugging webpack/rspack configurations. The --doctor mode is particularly brilliant – it's exactly what users need when troubleshooting.

What Makes This Excellent:

  • All previous CodeRabbit critical issues have been fixed ✅
  • TypeScript refactoring is clean and maintainable ✅
  • Cross-platform support is correct ✅
  • Environment isolation strategy is solid ✅
  • User experience is superb (help text, defaults, examples) ✅
  • Documentation is comprehensive ✅
  • Security-conscious design ✅
  • Great integration with existing doctor command ✅

What Needs Fixing:

  • Backslash escaping in YAML (critical but easy fix)
  • Doctor spec validation (may or may not be broken)
  • Linter validation (should pass, need to verify)

Verdict: ⚠️ Approval with required changes

The PR is in excellent shape overall. Fix the backslash escaping bug (critical), verify the doctor spec passes, run linters, and this is ready to merge. The missing automated tests are a concern but not blocking given the comprehensive manual testing described in the PR and the 28 commits of iteration showing thorough testing.


🔧 Quick Fix Checklist

  • yamlSerializer.ts:105 - Add backslash escaping:

    return `"${cleaned.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
  • Run specs:

    bundle exec rspec spec/shakapacker/doctor_spec.rb
    # Update expectations if needed for new output text
  • Run linters (per CLAUDE.md):

    bundle exec rubocop
    yarn lint
  • Optional but recommended: Add basic integration test


🎉 Closing Thoughts

This is a fantastic addition to Shakapacker! The --doctor mode will save users countless hours of debugging. The TypeScript refactoring makes the codebase more maintainable, and the comprehensive feature set (multiple formats, annotations, environment isolation) shows great attention to detail.

Once the small escaping fix is applied and specs/linters pass, this will be an excellent contribution. Great work, Justin! 👏


View job • Branch: justin808/config-export-util

- Detailed usage instructions with examples
- Documents all common options
- Explains rake vs rails invocation differences
- Notes about binstub fallback behavior
Critical fixes:
- Escape backslashes before quotes in serializeString to prevent invalid YAML
- Handles RegExp patterns correctly (e.g., patterns with backslashes and quotes)

Spec updates:
- Updated doctor_spec.rb binstub checks to cover both shakapacker and export-bundler-config
- Added comprehensive test cases for all binstub scenarios
- All 93 examples passing
@justin808
Copy link
Copy Markdown
Member Author

@claude review this pr

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Oct 9, 2025

Claude finished @justin808's task —— View job


Code Review for PR #647: Config Export Utility

I've completed a comprehensive review of this PR. This is excellent work that adds significant value for debugging webpack/rspack configurations. The TypeScript refactoring is clean and the implementation is thorough. However, I found one critical issue that must be fixed before merging.


🔴 Critical Issue

RegExp Serialization Missing Escaping (yamlSerializer.ts:71)

Severity: Critical - Produces invalid YAML output

The RegExp serialization wraps value.toString() in raw double quotes without escaping. If a RegExp pattern contains a double quote (e.g., /"/ or /["']/), the resulting YAML will be invalid.

Current code (line 70-72):

if (value instanceof RegExp) {
  return `"${value.toString()}"`  // ❌ No escaping
}

Example failure: The regex /"/ becomes "/"/" which breaks YAML parsing.

Fix Required: Use the existing serializeString() method which properly handles escaping:

if (value instanceof RegExp) {
  return this.serializeString(value.toString())  // ✅ Properly escaped
}

Location: package/configExporter/yamlSerializer.ts:70-72


🟡 Major Issues

1. Missing ts-node Error Handling (cli.ts:321-324)

Severity: Major - Will crash with unhelpful error if ts-node not installed

The code attempts to require("ts-node/register/transpile-only") without a try-catch block. If ts-node is not installed (common in projects with JS-only configs), this throws Cannot find module 'ts-node/register/transpile-only' instead of guiding the user.

Fix Required:

if (configFile.endsWith(".ts")) {
  try {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    require("ts-node/register/transpile-only")
  } catch (error) {
    throw new Error(
      "TypeScript config detected but ts-node is not available. " +
      "Install ts-node as a dev dependency: npm install --save-dev ts-node"
    )
  }
}

Location: package/configExporter/cli.ts:321-324


2. Doctor.rb Spec May Fail (spec impact)

The changes to lib/shakapacker/doctor.rb (lines 877-881) add new output text. Previous CodeRabbit reviews flagged a failing spec:

Shakapacker::Doctor ... provides helpful information about optional dependencies [FAILED]

Action Required: Update the corresponding spec at spec/shakapacker/doctor_spec.rb to expect the new guidance text about bin/export-bundler-config --doctor.


✅ Strengths

  1. Excellent TypeScript Architecture

    • Clean separation of concerns (cli.ts, yamlSerializer.ts, fileWriter.ts, configDocs.ts)
    • Well-defined types in types.ts
    • Proper module exports and package.json integration
  2. Outstanding User Experience

    • --doctor mode is brilliant for comprehensive troubleshooting
    • Auto-generated filenames (webpack-development-client.yaml)
    • Inline documentation with --annotate
    • Clear help text with examples
    • Multiple output formats (YAML, JSON, inspect)
  3. Robust Functionality

    • Environment isolation with proper cache clearing (cli.ts:326-345)
    • Cross-platform path handling with path.relative and os.tmpdir
    • TypeScript config support with ES module default export unwrapping
    • Client/server bundle filtering
    • Auto-detection of bundler from shakapacker.yml
  4. Good Documentation

    • Comprehensive troubleshooting.md updates with examples
    • Helpful inline code comments
    • Clear commit messages
  5. Fixed Previous Issues

    • TypeScript config loading and default export unwrapping (cli.ts:321-353)
    • Default value logic for format/annotate (cli.ts:126-138)
    • YAML function serialization using serializeString (yamlSerializer.ts:111-136)
    • YAML array indentation for nested objects (yamlSerializer.ts:138-184)
    • YAML multiline strings with block scalars (yamlSerializer.ts:209-213)
    • Path validation using path.relative (fileWriter.ts:78-91)
    • Cross-platform path handling (yamlSerializer.ts:247-264)
    • Filename sanitization with basename (fileWriter.ts:20)
    • validateOutputPath is now called (fileWriter.ts:22, 40)

📋 Code Quality Assessment

Security ✅

  • Path validation warns when writing outside cwd/tmp ✓
  • Filename sanitization prevents path traversal ✓
  • Input validation for bundler, env, format options ✓
  • No command injection risks ✓

Performance ✅

  • Efficient cache clearing strategy ✓
  • Reasonable depth limits (default: 20) ✓
  • No obvious performance bottlenecks ✓

Maintainability ✅

  • Well-structured TypeScript modules ✓
  • Clear separation of concerns ✓
  • Comprehensive inline documentation ✓
  • Follows project conventions ✓

🧪 Test Coverage

Issue: No automated tests were added for this significant new feature.

Missing:

  • Unit tests for YAML serialization (especially RegExp, functions, edge cases)
  • Integration tests for CLI argument parsing
  • Tests for TypeScript config loading
  • Tests for the rake task
  • Update to doctor_spec.rb for the new output (lines 622-668 cover binstub checks)

Recommendation: Add at least:

  1. Basic integration test for the utility
  2. Update the doctor spec to expect new guidance text
  3. Unit tests for YAML serialization edge cases (especially RegExp with quotes)

📏 Project Guidelines Compliance (CLAUDE.md)

✅ Followed

  • All files end with trailing newlines ✓
  • Code follows existing patterns ✓
  • Good separation of concerns ✓
  • Documentation is comprehensive ✓
  • Feature branch workflow ✓

⚠️ Missing

  • No corresponding RSpec tests (CLAUDE.md: "Run corresponding RSpec tests when changing source files")
  • Should update spec/shakapacker/doctor_spec.rb after changing lib/shakapacker/doctor.rb
  • Should add tests for new functionality before merge

🎯 Required Before Merge

Must Fix (Blocking):

  1. RegExp serialization escaping (yamlSerializer.ts:71) - CRITICAL
  2. ts-node error handling (cli.ts:321-324)
  3. Update doctor spec to expect new output text

Recommended (Strongly advised):

  • Add basic integration tests for the utility
  • Add unit tests for YAML edge cases (RegExp with quotes, functions with quotes)

💯 Overall Assessment

Rating: ⭐⭐⭐⭐½ (4.5/5)

This is outstanding work! The feature is well-designed, comprehensive, and solves a real pain point. The --doctor mode is particularly brilliant for troubleshooting. The TypeScript refactoring makes the code much more maintainable than the original bash scripts.

What Makes This Great:

  • All previous critical issues from CodeRabbit have been fixed ✓
  • TypeScript implementation is clean and well-structured ✓
  • Cross-platform support is correct ✓
  • Environment isolation strategy is solid ✓
  • User experience is excellent (help text, defaults, examples) ✓
  • Documentation is comprehensive ✓

What Needs Fixing:

  • RegExp escaping bug (critical, easy fix)
  • ts-node error handling (important for UX)
  • Doctor spec update (required for CI to pass)

Verdict: ⚠️ Approval with required changes

The PR is in excellent shape overall. Fix the RegExp escaping bug (critical), add ts-node error handling, and update the doctor spec, then this is ready to merge. The missing tests are a concern but not blocking given the comprehensive manual testing described in the PR.


🔧 Quick Fix Summary

  1. yamlSerializer.ts:71 - Change to use this.serializeString(value.toString())
  2. cli.ts:321-324 - Wrap ts-node require in try-catch with helpful error
  3. spec/shakapacker/doctor_spec.rb - Update expectation for new output text

Great work, Justin! This will be an incredibly useful tool for the community. 👏


Branch: justin808/config-export-utilView job

- Set release date for v9.1.1 as October 9, 2025
- Add note about switch_bundler integration in CHANGELOG
- Add Debugging Configuration section to README
- Add config export utility to rspack migration guide with comparison workflow
- Document integration with switch_bundler feature
@justin808 justin808 merged commit 1f44d4d into main Oct 9, 2025
54 checks passed
@justin808 justin808 deleted the justin808/config-export-util branch October 9, 2025 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant