Skip to content

Conversation

justin808
Copy link
Member

@justin808 justin808 commented Oct 10, 2025

Summary

Implements #687 - adds YAML configuration file support to bin/export-bundler-config, allowing users to define and version control their build configurations.

Key Features

  • 🆕 --init command to generate sample .bundler-config.yml
  • 🆕 --build=<name> flag to export specific build from config file
  • 🆕 --list-builds command to show available builds
  • 🆕 --webpack and --rspack flags for easy bundler switching
  • 📝 Support for multiple build configurations with environment variables
  • 🔄 Environment variable expansion (${VAR}, ${VAR:-default}, ${BUNDLER})
  • ⚙️ Automatic bundler_env to CLI args conversion
  • 📋 Build-specific output naming (bundler-build-type.ext)
  • ✅ Backward compatible with all existing CLI usage

YAML Config Example

default_bundler: webpack

builds:
  dev:
    description: Build admin and consumer bundles for dev env
    environment:
      NODE_OPTIONS: "--max-old-space-size=4096"
      NODE_ENV: development
      RAILS_ENV: development
    bundler_env:
      env: dev
    outputs:
      - client
      - server

Usage Examples

# Initialize config file
bin/export-bundler-config --init

# List available builds
bin/export-bundler-config --list-builds

# Export specific build
bin/export-bundler-config --build=dev --save
# Creates: webpack-dev-client.yml, webpack-dev-server.yml

# Switch to rspack easily
bin/export-bundler-config --build=dev --save --rspack
# Creates: rspack-dev-client.yml, rspack-dev-server.yml

Implementation Details

  • New file: package/configExporter/configFile.ts - Config file loading and parsing
  • Updated: package/configExporter/types.ts - Schema types for config file
  • Updated: package/configExporter/cli.ts - CLI argument parsing and integration
  • Updated: package/configExporter/fileWriter.ts - Build-name file naming support
  • Spec file: bundler-config-enhancement-spec.md - Full specification document

Test Plan

  • All existing tests pass
  • Linting passes
  • Build succeeds
  • Backward compatibility maintained
  • Manual testing with sample config file
  • Testing with both webpack and rspack

Breaking Changes

None - fully backward compatible

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • YAML-based config (.bundler-config.yml) with per-build definitions, variable expansion, and bundler auto-detection/overrides.
    • New CLI commands: --init (creates sample config), --list-builds, --config-file, and --build for build-aware exports.
    • Filenames and outputs include build name when used; bundler-specific args/env applied during export.
  • Documentation

    • Help text, sample config, and guides updated to document the YAML workflow and build-based operations.

Copy link

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds YAML-driven configuration support and CLI commands for managing/exporting bundler build configs: introduces ConfigFileLoader and sample config generation, new CLI flags (--init, --config-file, --build, --list-builds), per-build resolution with env expansion and bundler precedence, build-aware filenames, and corresponding type/export additions.

Changes

Cohort / File(s) Summary
CLI: config-file workflow & commands
package/configExporter/cli.ts
Adds --init, --config-file, --build, --list-builds; integrates ConfigFileLoader and generateSampleConfigFile; implements init and list flows; routes build-based exports through config resolution and propagates buildName into metadata and filenames.
Config loader: YAML parsing & build resolution
package/configExporter/configFile.ts
New module: ConfigFileLoader + generateSampleConfigFile; reads/validates .bundler-config.yml; resolves builds (bundler precedence, env variable expansion including ${VAR:-default}, bundler_env → CLI args, outputs, optional per-build config path); provides exists(), load(), resolveBuild(), and listBuilds().
Types: options, schema, outputs
package/configExporter/types.ts
Extends ExportOptions with init, configFile, build, listBuilds; adds BundlerConfigFile, BuildConfig, ResolvedBuildConfig; adds ConfigMetadata.buildName and FileOutput.filename.
File naming: build-aware filenames
package/configExporter/fileWriter.ts
generateFilename signature extended with optional buildName; filename construction now uses <bundler>-<buildName>-<output>.<ext> when buildName is present (backwards-compatible when absent).
Public exports: surface expansion
package/configExporter/index.ts
Public exports extended to include new types and ConfigFileLoader / generateSampleConfigFile; re-exports added for the config-file API surface and new types.
Top-level types declaration
package/index.d.ts
Large type-surface edits and export-shape changes (unchanged by config-file feature but included in diff): switches to default export object shape, adds generateWebpackConfig, adjusts env shape and exported rule typing.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as CLI (configExporter/cli.ts)
  participant Loader as ConfigFileLoader
  participant FS as File System
  participant Bundler as Bundler (webpack/rspack)
  participant Writer as fileWriter

  User->>CLI: run export-bundler-config [--init | --list-builds | --build NAME | legacy]
  alt --init
    CLI->>Loader: generateSampleConfigFile()
    CLI->>FS: write .bundler-config.yml (sample)
    CLI-->>User: show next steps
  else --list-builds
    CLI->>Loader: listBuilds()
    Loader->>FS: read .bundler-config.yml
    Loader-->>CLI: builds summary
    CLI-->>User: print builds
  else --build NAME
    CLI->>Loader: resolveBuild(NAME, options, defaultBundler)
    Loader->>FS: read & parse YAML
    Loader->>Loader: resolve bundler, expand env, map bundler_env→args
    Loader-->>CLI: ResolvedBuildConfig
    CLI->>Bundler: invoke with resolved args/configFile
    Bundler-->>CLI: produce configs
    CLI->>Writer: generateFilename(bundler, env, type, format, buildName)
    CLI->>FS: write outputs
    CLI-->>User: export results
  else legacy
    CLI->>Bundler: invoke via legacy flags
    Bundler-->>CLI: configs
    CLI->>Writer: generateFilename(bundler, env, type, format)
    CLI->>FS: write outputs
    CLI-->>User: export results
  end
  note over Loader,CLI: Bundler resolution precedence: CLI flag > build.bundler > config.default_bundler > fallback
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • tomdracz

Poem

I hopped through YAML fields at night,
Nibbled flags in moonlit light,
Builds aligned and envs expand,
Filenames tulip in my hand,
Webpack, Rspack — now both play. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 provided title clearly summarizes the main change by indicating that YAML configuration file support is being added to the bin/export-bundler-config tool, matching the PR objectives and file changes. It is concise and specific without unnecessary noise. A reviewer scanning the history can quickly grasp the primary purpose of this PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch justin808/bundler-config-yaml

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.

Copy link

claude bot commented Oct 10, 2025

Code Review: YAML Configuration Support

This is a comprehensive and well-thought-out feature addition. Overall, the implementation is solid, but I've identified several important issues.

Critical Issues

1. Missing Trailing Newlines
Files: configFile.ts, types.ts, fileWriter.ts
Per CLAUDE.md: All files must end with a trailing newline. This will cause linting failures.

2. Missing Test Coverage
No test files added for this significant feature. Need comprehensive unit tests for ConfigFileLoader, environment variable expansion, bundler resolution, file naming, and CLI parsing.

3. Security: Unvalidated YAML Loading
configFile.ts:33 uses js-yaml load() without safe options. This can execute arbitrary code. Use safe YAML loading with schema validation.

Important Issues

4. Error Handling Type Safety
configFile.ts:37-40 uses 'error: any' and accesses .message without type checking. Use proper instanceof Error checks.

5. Environment Variable Expansion Security
expandString() method has no sanitization. Missing variables become empty strings silently. Add warnings and document security implications.

6. Outputs Array Validation
Validation checks if outputs is an array but not if elements are non-empty strings.

7. File Naming Special Characters
fileWriter.ts:66 doesn't sanitize buildName. Names like 'dev/test' create invalid filenames. Add sanitization.

Suggestions

  • Extract magic strings to constants
  • Cache config loading for performance
  • Add JSDoc comments to public methods
  • Use logger pattern for consistent console output

What's Good

  • Excellent spec documentation
  • Full backward compatibility
  • Clear precedence rules
  • Comprehensive examples
  • Good TypeScript usage
  • Intuitive user commands

Before Merging

  • Add trailing newlines
  • Add unit tests
  • Use safe YAML loading
  • Fix error handling
  • Validate outputs contents
  • Sanitize build names
  • Run yarn lint

Overall

Well-architected feature with good design. Needs test coverage and security fixes before merging. Request changes for critical issues.

Great work on this enhancement!

Copy link

claude bot commented Oct 10, 2025

Code Review for PR #686

Strengths

  • Excellent backward compatibility
  • Well-structured code with clear separation of concerns
  • Comprehensive documentation
  • User-friendly CLI commands (--init, --list-builds)
  • Good environment variable expansion support

Critical Issues

  1. configFile.ts appears truncated - Verify complete file is committed
  2. Missing type definitions - ExportOptions needs: init?, configFile?, build?, listBuilds?
  3. Missing buildName in ConfigMetadata - Referenced in cli.ts but not defined
  4. FileWriter.generateFilename signature mismatch - Called with 5 params but defined with 4

Medium Priority

  1. Error handling for malformed env var syntax
  2. Stricter YAML validation for outputs array
  3. Document process.env mutation behavior
  4. Consider blocking suspicious file paths instead of warning
  5. Inconsistent error messaging
  6. Missing JSDoc comments for ConfigFileLoader

Test Coverage - MAJOR CONCERN

No test files included. Need tests for:

  • ConfigFileLoader (validation, expansion, resolution)
  • CLI integration with YAML configs
  • Environment variable expansion edge cases
  • Bundler resolution precedence
  • Error cases (missing files, malformed YAML)

Security

  • Environment variable injection is safe for intended use
  • Good path validation exists
  • js-yaml usage is standard and safe
  • Recommend documenting config files should be trusted sources only

Code Quality

  • Verify trailing newlines (CLAUDE.md requirement)
  • Extract magic strings to constants
  • Simplify complex conditionals in loadConfigsForEnv
  • Use type guards instead of assertions

Action Items

  • Verify configFile.ts fully committed
  • Add missing type definitions
  • Update FileWriter.generateFilename signature
  • Add comprehensive test suite
  • Run yarn lint
  • Test with webpack and rspack
  • Add JSDoc documentation

Overall Assessment

Valuable feature with solid design BUT has critical issues (incomplete files, type mismatches) and missing tests.

Recommendation: Request changes - Fix critical issues and add tests before merging.

Great work! Looking forward to the completed implementation.

Copy link

@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: 4

🧹 Nitpick comments (8)
bundler-config-enhancement-spec.md (2)

191-197: File extension mismatch with implementation

Examples use .yml, but the generator produces .yaml (see FileWriter.generateFilename). Align examples to .yaml or change generator to .yml to avoid confusion.


216-238: Add language to fenced code block (markdownlint MD040)

The “Output:” block lacks a language. Use a neutral language like “text”.

Apply:

-```
+```text
 Available builds in .bundler-config.yml:
 ...

</blockquote></details>
<details>
<summary>package/configExporter/types.ts (1)</summary><blockquote>

`55-56`: **Constrain outputs to known values**

Limit outputs to "client" | "server" | "all" to catch typos at compile time and match downstream usage.


```diff
-  outputs?: string[]
+  outputs?: Array<"client" | "server" | "all">
package/configExporter/cli.ts (2)

660-663: Reduce unsolicited console noise

Auto-detected bundler is logged unconditionally. Consider gating behind --verbose to keep stdout clean for scripting/inspection.

-      console.log(`[Config Exporter] Auto-detected bundler: ${bundler}`)
+      if (process.env.VERBOSE) {
+        console.log(`[Config Exporter] Auto-detected bundler: ${bundler}`)
+      }

371-390: Handle computed bundlerEnvArgs in the CLI
resolvedBuild.bundlerEnvArgs is generated by ConfigFileLoader.resolveBuild but never used in cli.ts. Either pass bundlerEnvArgs when invoking function-style configs or include it in the CLI output metadata, or remove bundlerEnvArgs from BuildConfig to eliminate dead code.

package/configExporter/configFile.ts (3)

31-41: Improve YAML error diagnostics

Pass filename to js-yaml.load for clearer parse errors.

-      const parsed = loadYaml(content) as BundlerConfigFile
+      const parsed = loadYaml(content, { filename: this.configFilePath }) as BundlerConfigFile

[retrieved_learnings]


81-92: Validate outputs tokens

Currently only checks array shape. Validate elements to be "client" | "server" | "all" to prevent downstream casts of invalid values.

-      if (build.outputs && !Array.isArray(build.outputs)) {
+      if (build.outputs && !Array.isArray(build.outputs)) {
         throw new Error(
           `Invalid outputs in build '${name}'. Must be an array of strings.`
         )
-      }
+      }
+      if (Array.isArray(build.outputs)) {
+        const allowed = new Set(["client", "server", "all"])
+        const invalid = build.outputs.filter((o) => !allowed.has(String(o)))
+        if (invalid.length > 0) {
+          throw new Error(
+            `Invalid outputs in build '${name}': ${invalid.join(
+              ", "
+            )}. Allowed: client, server, all.`
+          )
+        }
+      }

210-229: List output: clarify default bundler label

Expression may print a literal “webpack (default)”. Consider formatting to distinguish when default is used.

-      const bundler =
-        build.bundler || config.default_bundler || "webpack (default)"
+      const bundler =
+        build.bundler ??
+        config.default_bundler ??
+        "webpack"
+      const bundlerLabel =
+        build.bundler ? bundler : config.default_bundler ? `${bundler} (from default_bundler)` : `${bundler} (fallback)`
...
-      console.log(`    Bundler: ${bundler}`)
+      console.log(`    Bundler: ${bundlerLabel}`)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc741e9 and 1983922.

📒 Files selected for processing (6)
  • bundler-config-enhancement-spec.md (1 hunks)
  • package/configExporter/cli.ts (13 hunks)
  • package/configExporter/configFile.ts (1 hunks)
  • package/configExporter/fileWriter.ts (1 hunks)
  • package/configExporter/index.ts (1 hunks)
  • package/configExporter/types.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
package/configExporter/fileWriter.ts (1)
lib/shakapacker/configuration.rb (1)
  • bundler (108-110)
package/configExporter/configFile.ts (1)
package/configExporter/types.ts (3)
  • BundlerConfigFile (45-48)
  • ExportOptions (1-20)
  • ResolvedBuildConfig (59-67)
package/configExporter/cli.ts (2)
package/configExporter/configFile.ts (2)
  • ConfigFileLoader (11-230)
  • generateSampleConfigFile (232-393)
package/configExporter/types.ts (1)
  • ExportOptions (1-20)
package/configExporter/types.ts (1)
package/configExporter/index.ts (3)
  • BundlerConfigFile (6-6)
  • BuildConfig (7-7)
  • ResolvedBuildConfig (8-8)
🪛 markdownlint-cli2 (0.18.1)
bundler-config-enhancement-spec.md

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

(MD040, fenced-code-language)

🔇 Additional comments (1)
package/configExporter/index.ts (1)

2-9: Public exports LGTM

Type and symbol re-exports align with new config-file workflow. No issues.

Also applies to: 13-13

Comment on lines +139 to 147
} else if (arg === "--init") {
options.init = true
} else if (arg.startsWith("--config-file=")) {
options.configFile = parseValue(arg, "--config-file=")
} else if (arg.startsWith("--build=")) {
options.build = parseValue(arg, "--build=")
} else if (arg === "--list-builds") {
options.listBuilds = true
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing --annotate flag handling (compatibility regression)

PR claims backward compatibility incl. --annotate, but only --no-annotate is parsed. Add support for --annotate.

   } else if (arg === "--no-annotate") {
     options.annotate = false
+  } else if (arg === "--annotate") {
+    options.annotate = true
   } else if (arg === "--verbose") {
     options.verbose = true

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In package/configExporter/cli.ts around lines 139 to 147, the CLI parsing only
recognizes --no-annotate but not the legacy --annotate flag, causing a
compatibility regression; update the argument parsing to detect "--annotate" and
set options.annotate = true when present (and keep existing handling for
"--no-annotate" to set it false), ensuring you parse "--annotate" before any
generic startsWith checks or add an explicit else-if for "--annotate" alongside
the other literal flag checks so both forms are supported.

Comment on lines +158 to +218
private expandEnvironmentVariables(
vars: Record<string, string>,
bundler: string
): Record<string, string> {
const expanded: Record<string, string> = {}

for (const [key, value] of Object.entries(vars)) {
expanded[key] = this.expandString(value, bundler)
}

return expanded
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Coerce env values to strings before expansion

YAML scalars may be non-strings; calling replace on numbers/booleans will throw. Coerce to string first.

-    for (const [key, value] of Object.entries(vars)) {
-      expanded[key] = this.expandString(value, bundler)
+    for (const [key, value] of Object.entries(vars)) {
+      expanded[key] = this.expandString(String(value), bundler)
     }
📝 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
private expandEnvironmentVariables(
vars: Record<string, string>,
bundler: string
): Record<string, string> {
const expanded: Record<string, string> = {}
for (const [key, value] of Object.entries(vars)) {
expanded[key] = this.expandString(value, bundler)
}
return expanded
}
private expandEnvironmentVariables(
vars: Record<string, string>,
bundler: string
): Record<string, string> {
const expanded: Record<string, string> = {}
for (const [key, value] of Object.entries(vars)) {
expanded[key] = this.expandString(String(value), bundler)
}
return expanded
}
🤖 Prompt for AI Agents
In package/configExporter/configFile.ts around lines 158 to 169, the loop
assumes env values are strings and calls string methods during expansion, which
breaks for non-string YAML scalars; coerce each value to a string before calling
expandString (e.g., use String(value) or value?.toString()) so expandString
always receives a string, and assign the result to expanded[key]; handle
null/undefined by explicitly converting them to an empty string if desired.

Copy link

claude bot commented Oct 10, 2025

Code Review - PR #686: YAML Configuration File Support

Thank you for this comprehensive enhancement! This is a well-thought-out feature that adds significant value. Here's my detailed review:

Strengths

Code Quality

  • Excellent TypeScript typing: Comprehensive type definitions in types.ts with clear interfaces
  • Clean separation of concerns: ConfigFileLoader, CLI parsing, and file writing are well-separated
  • Good error handling: Helpful error messages with actionable guidance (e.g., "Run --init to create...")
  • Backward compatibility: Existing CLI usage remains fully functional

Design

  • Well-designed precedence system: CLI flags → build config → default config → auto-detect → fallback
  • Flexible variable expansion: Support for ${BUNDLER}, ${VAR}, and ${VAR:-default} is powerful
  • User-friendly: --init and --list-builds commands make discovery easy
  • Comprehensive spec: The 457-line spec document is excellent documentation

🔍 Issues & Recommendations

1. Security Concerns ⚠️

Issue: Environment variable expansion in configFile.ts:expandString() could allow unexpected variable injection.

// Current implementation
expanded = expanded.replace(/\${([^}:]+)}/g, (_, varName) => {
  return process.env[varName] || ""
})

Concern: If a malicious config file contains ${PATH} or ${HOME}, it could expose sensitive environment variables. While this is a local file, it's worth considering if this tool might be used in CI/CD where env vars contain secrets.

Recommendation: Consider either:

  • Whitelisting allowed env vars (e.g., only BUNDLER, RAILS_ENV, NODE_ENV)
  • Adding a note in documentation about not using this with untrusted config files
  • Logging which env vars are being expanded (in verbose mode)

2. Missing Tests

Critical: No test files are included in this PR.

Needed tests:

  • configFile.test.ts - Unit tests for:
    • Config file loading and validation
    • Environment variable expansion (especially edge cases like ${VAR:-} with empty default)
    • Bundler resolution precedence
    • convertBundlerEnvToArgs() with various inputs
  • Integration tests for:
    • --init command
    • --list-builds command
    • --build=<name> with various scenarios
    • File naming with build names

Per CLAUDE.md: "Run corresponding RSpec tests when changing source files" - The project expects tests with new features.

3. Path Traversal Risk 🔒

In cli.ts:runInitCommand():

const configPath = options.configFile || ".bundler-config.yml"
const fullPath = resolve(process.cwd(), configPath)

Issue: User-supplied --config-file=../../../etc/passwd could write outside the project directory.

Recommendation: Add validation similar to fileWriter.ts:validateOutputPath() before writing the init file:

const fileWriter = new FileWriter()
fileWriter.validateOutputPath(fullPath)

4. Type Safety Issues 🔧

package/configExporter/configFile.ts:36

} catch (error: any) {
  throw new Error(\`Failed to load config file...\`)
}

Issue: Using any defeats TypeScript purpose. Per best practices, use unknown and type guard:

} catch (error: unknown) {
  const message = error instanceof Error ? error.message : String(error)
  throw new Error(\`Failed to load config file ${this.configFilePath}: ${message}\`)
}

5. Edge Case: Empty Environment Variables

configFile.ts:192

expanded = expanded.replace(/\${([^}:]+)}/g, (_, varName) => {
  return process.env[varName] || ""
})

Issue: This treats missing vars and empty vars identically. ${NODE_ENV} returns "" if NODE_ENV="" or if unset.

Recommendation: Consider if this is intentional. If not:

return process.env[varName] !== undefined ? process.env[varName] : ""

6. Missing Trailing Newlines 📝

Per CLAUDE.md: "ALWAYS end all files with a trailing newline character."

Check: Ensure configFile.ts, types.ts, and spec file all end with \n

7. CLI Help Text Length

The updated --help output is quite long (744+ lines). Consider:

  • Adding a --help-config or --help-full for detailed config file help
  • Keeping base --help concise

8. Potential Bug: Missing Config in Build

cli.ts:387-390

const resolvedBuild = loader.resolveBuild(
  options.build,
  options,
  defaultBundler
)

Question: What happens if the build config field points to a non-existent file? Should this be validated here or later in findConfigFile()?

🎯 Performance Considerations

  • File I/O is synchronous (readFileSync, writeFileSync) - acceptable for CLI tool
  • YAML parsing with js-yaml is standard - no concerns
  • No obvious performance bottlenecks

📋 Test Plan Checklist

The PR description shows:

  • ✅ All existing tests pass
  • ✅ Linting passes
  • ✅ Build succeeds
  • ✅ Backward compatibility maintained
  • ❌ Manual testing with sample config file - Needs completion
  • ❌ Testing with both webpack and rspack - Needs completion

🚀 Suggestions for Follow-up

  1. Add validation command: --validate to check config file without exporting
  2. Support for config file includes: Allow splitting large configs
  3. JSON schema: Publish a JSON schema for IDE autocomplete in YAML files
  4. Exit codes: Document exit codes (0 = success, 1 = error)

📚 Documentation

  • ✅ Excellent inline comments in sample config
  • ✅ Comprehensive spec document
  • ⚠️ Need to update main README.md (per spec checklist)
  • ⚠️ Need migration guide for existing users

Final Recommendation

Overall Assessment: This is high-quality work with excellent design. The feature is well-architected and backward compatible.

Before merging:

  1. MUST: Add comprehensive test coverage
  2. ⚠️ SHOULD: Address security concerns (env var expansion, path traversal)
  3. ⚠️ SHOULD: Fix error: anyerror: unknown
  4. MUST: Verify trailing newlines in all files
  5. ⚠️ SHOULD: Complete manual testing checklist

Status: Requesting changes for test coverage and security hardening. The core implementation is solid and ready once tests are added.


Great work on this feature! The YAML config approach will significantly improve DX for users with complex build setups. 🎉

Copy link

claude bot commented Oct 10, 2025

Code Review - PR #686: Add YAML Configuration File Support

Thank you for this comprehensive feature addition! I've reviewed the code quality, architecture, potential issues, security, performance, and test coverage. Here's my detailed feedback:


✅ Strengths

Architecture & Design

  • Well-structured modular design: The new ConfigFileLoader class is properly separated from the CLI logic
  • Excellent backward compatibility: All existing CLI usage continues to work unchanged
  • Clear precedence hierarchy: CLI args → build config → default config → auto-detection is logical and well-documented
  • Thoughtful UX: Commands like --init, --list-builds make the feature discoverable and easy to adopt

Code Quality

  • Good TypeScript usage: Proper type definitions in types.ts (though see issues below)
  • Comprehensive spec document: The 457-line spec shows thorough planning
  • Clean error messages: User-friendly error handling with actionable guidance

🔴 Critical Issues

1. Missing Type Definitions (High Priority)

The types.ts file is missing critical type definitions referenced in cli.ts and configFile.ts:

// Missing from types.ts:
export interface ExportOptions {
  // ... existing fields ...
  init?: boolean              // ❌ MISSING
  configFile?: string         // ❌ MISSING  
  build?: string              // ❌ MISSING
  listBuilds?: boolean        // ❌ MISSING
}

export interface ConfigMetadata {
  // ... existing fields ...
  buildName?: string          // ❌ MISSING
}

// These interfaces are completely missing:
export interface BundlerConfigFile { ... }
export interface BuildConfig { ... }
export interface ResolvedBuildConfig { ... }

Impact: This will cause TypeScript compilation errors.

Fix: Update package/configExporter/types.ts with all missing interfaces.


2. Incomplete configFile.ts Implementation (High Priority)

The diff shows configFile.ts is truncated at line 147:

  private re
... [350 lines truncated] ...

Impact: The file is incomplete and won't compile or function.

Fix: Ensure the full implementation is included in the PR.


3. Missing fileWriter.ts Updates (High Priority)

The generateFilename method needs a new signature to support build names:

// Current (shown in existing code):
generateFilename(
  bundler: string,
  env: string,
  configType: "client" | "server" | "all",
  format: "yaml" | "json" | "inspect"
): string

// Referenced in cli.ts but not implemented:
generateFilename(
  bundler: string,
  env: string,
  configType: "client" | "server" | "all",
  format: "yaml" | "json" | "inspect",
  buildName?: string  // ❌ MISSING PARAMETER
): string

Impact: Calling code in cli.ts passes 5 arguments but method only accepts 4.

Fix: Update fileWriter.ts to handle the optional buildName parameter.


⚠️ High Priority Issues

4. Security: Environment Variable Injection

The expandEnvironmentVariables function (truncated in diff) needs careful implementation:

environment:
  RAILS_ENV: ${RAILS_ENV:-production}
  PATH: ${PATH}:/evil/path  # Potential injection vector

Concerns:

  • Unrestricted environment variable expansion could allow users to inject malicious values
  • No validation of expanded values
  • Could override critical system variables like PATH, HOME, NODE_OPTIONS

Recommendations:

  1. Whitelist allowed environment variables (e.g., only NODE_ENV, RAILS_ENV, BABEL_ENV, NODE_OPTIONS)
  2. Sanitize expanded values
  3. Warn users when overriding system variables
  4. Document security implications in the spec

5. Security: YAML Parsing Vulnerabilities

Using js-yaml without safe mode can execute arbitrary code:

const parsed = loadYaml(content) as BundlerConfigFile  // ⚠️ Unsafe

Fix: Use safe mode explicitly:

import { load as loadYaml, FAILSAFE_SCHEMA } from "js-yaml"

const parsed = loadYaml(content, { 
  schema: FAILSAFE_SCHEMA,  // Prevents code execution
  json: true 
}) as BundlerConfigFile

6. Path Traversal Risk

The --config-file parameter could allow reading files outside the project:

bin/export-bundler-config --config-file=../../../etc/passwd --list-builds

Fix: Add validation similar to fileWriter.validateOutputPath():

private validateConfigPath(configPath: string): void {
  const absPath = resolve(configPath)
  const cwd = process.cwd()
  const rel = relative(cwd, absPath)
  
  if (rel.startsWith("..") || isAbsolute(rel)) {
    throw new Error(
      `Config file must be within project directory: ${configPath}`
    )
  }
}

⚠️ Medium Priority Issues

7. Missing Test Coverage

The spec document lists testing as incomplete:

- [x] All existing tests pass
- [x] Linting passes  
- [x] Build succeeds
- [x] Backward compatibility maintained
- [ ] Manual testing with sample config file  # ❌ NOT DONE
- [ ] Testing with both webpack and rspack     # ❌ NOT DONE

No test files were added for the new functionality (configFile.ts, config loading, build resolution, etc.).

Recommendations:

  • Add unit tests for ConfigFileLoader class
  • Add integration tests for --build, --init, --list-builds commands
  • Test environment variable expansion edge cases
  • Test bundler precedence resolution
  • Test error handling for malformed YAML

8. Incomplete Error Handling

Several edge cases lack proper handling:

// What happens if YAML is malformed?
const parsed = loadYaml(content) as BundlerConfigFile  
// ❌ No try-catch for YAML syntax errors

// What if outputs array is empty?
const outputs = build.outputs || []  
// ❌ No validation that outputs are valid strings

// What if bundler_env has invalid types?
const bundlerEnvArgs = this.convertBundlerEnvToArgs(build.bundler_env || {})
// ❌ No type checking for boolean vs string values

Fix: Add comprehensive error handling with user-friendly messages.


9. Missing Trailing Newlines (Per CLAUDE.md)

Ensure all new TypeScript files end with a trailing newline character (project requirement).

Fix: Run yarn lint and add newlines where needed.


💡 Suggestions & Best Practices

10. Performance: Cache Config File Loading

The ConfigFileLoader reads and parses the YAML file every time load() is called. For --doctor mode with multiple builds, this is inefficient.

Suggestion:

private cachedConfig?: BundlerConfigFile

load(): BundlerConfigFile {
  if (this.cachedConfig) return this.cachedConfig
  // ... existing loading logic ...
  this.cachedConfig = parsed
  return parsed
}

11. Code Style: Inconsistent String Quoting

Mix of single and double quotes in new code:

this.configFilePath = configFilePath || resolve(process.cwd(), ".bundler-config.yml")  // Double
throw new Error('Config file must contain a \'builds\' object')  // Single

Fix: Use consistent quoting per project style (check existing codebase).


12. Documentation: Missing JSDoc Comments

New public methods lack JSDoc comments:

export class ConfigFileLoader {
  // ❌ Missing JSDoc
  exists(): boolean { ... }
  
  // ❌ Missing JSDoc
  load(): BundlerConfigFile { ... }
  
  // ❌ Missing JSDoc  
  resolveBuild(...): ResolvedBuildConfig { ... }
}

Suggestion: Add JSDoc for all public methods explaining parameters, return values, and potential errors.


13. UX: Improve --list-builds Output

Consider adding more details to help users:

  dev
    Description: Build admin and consumer bundles for dev env
    Bundler: webpack (default)
    Outputs: client, server
+   Environment: NODE_ENV=development, RAILS_ENV=development, NODE_OPTIONS="--max-old-space-size=4096"
+   Command: bin/export-bundler-config --build=dev --save

📋 Testing Checklist

Before merging, please ensure:

  • Run yarn lint and fix all linting errors
  • Add unit tests for ConfigFileLoader
  • Add integration tests for new CLI commands
  • Test with malformed YAML files (syntax errors, invalid schema)
  • Test environment variable expansion edge cases
  • Test with both webpack and rspack
  • Verify backward compatibility (all existing commands work unchanged)
  • Update TypeScript types to include all missing interfaces
  • Complete the configFile.ts implementation (currently truncated)
  • Update fileWriter.ts to handle buildName parameter
  • Add security validations (config path, env var whitelist, safe YAML parsing)

Summary

This is a well-designed feature with excellent documentation and user experience. However, there are critical implementation gaps that must be addressed before merging:

  1. 🔴 Complete the truncated configFile.ts implementation
  2. 🔴 Add missing TypeScript type definitions
  3. 🔴 Update fileWriter.ts to accept buildName parameter
  4. ⚠️ Add security validations (safe YAML parsing, path validation, env var whitelist)
  5. ⚠️ Add comprehensive test coverage

Once these issues are resolved, this will be a valuable addition to Shakapacker! 🎉


Recommendation: Request changes and re-review after fixes are applied.

Let me know if you'd like me to help with any of these fixes! 🤖

Copy link

claude bot commented Oct 10, 2025

Code Review: YAML Configuration Support

This is a well-designed feature, but there are critical issues that need to be addressed:

Critical Issues

1. Missing File Content: The configFile.ts is truncated in the diff (150/393 lines shown)

2. No Test Coverage: Zero tests for the new functionality. Required tests include:

  • Config file parsing/validation
  • Environment variable expansion
  • Build resolution
  • CLI argument parsing
  • Error handling

3. Runtime Error - Parameter Mismatch:

  • cli.ts calls fileWriter.generateFilename() with 5 params (including buildName)
  • fileWriter.ts only accepts 4 parameters
  • This will crash at runtime

4. Missing Type Definitions:

  • BundlerConfigFile, BuildConfig, ResolvedBuildConfig not in types.ts
  • ConfigMetadata missing buildName field

Code Quality Issues

5. Error Handling: Creates ConfigFileLoader twice for existence check
6. File Naming: Inconsistent .yml vs .yaml extensions
7. Environment Mutation: Mutates process.env (problematic for tests)
8. Missing Trailing Newlines: Required per CLAUDE.md

Security

  • Add path validation in ConfigFileLoader
  • Document that config files should be treated as trusted code

What's Done Well

✅ Excellent backward compatibility
✅ Comprehensive spec document
✅ Good user experience
✅ Clear help text and examples
✅ Thorough input validation

Pre-Merge Checklist

  • Fix fileWriter.generateFilename() signature
  • Add buildName to ConfigMetadata
  • Add comprehensive tests
  • Complete missing file content
  • Run yarn lint
  • Add trailing newlines
  • Manual testing with webpack and rspack

This will be a great feature once these issues are resolved!

Copy link

claude bot commented Oct 10, 2025

Code Review: YAML Configuration Support for bin/export-bundler-config

Overall, this is a well-structured and thoughtful PR that adds valuable functionality. The implementation is comprehensive, backward-compatible, and well-documented. However, there are several areas that need attention before merging.


🔴 Critical Issues

1. Missing Test Coverage (BLOCKER)

The PR adds 1,125+ lines of new code across multiple TypeScript files but includes zero automated tests. This is a significant risk.

Required:

  • Unit tests for ConfigFileLoader class (validation, parsing, security checks)
  • Unit tests for environment variable expansion logic
  • Integration tests for CLI argument parsing with config files
  • Tests for bundler resolution precedence
  • Tests for file naming with build names
  • Edge case tests (missing files, invalid YAML, malformed configs)

Suggested test file locations:

  • test/configExporter/configFile.test.js
  • test/configExporter/cli.test.js
  • test/configExporter/fileWriter.test.js

2. Missing Implementation Files

The PR diff shows changes to configFile.ts but the file doesn't exist in the repository. The diff appears truncated.

Action needed: Ensure all new files are included in the PR:

  • package/configExporter/cli.ts (modified)
  • package/configExporter/fileWriter.ts (modified)
  • package/configExporter/types.ts (modified)
  • package/configExporter/configFile.ts (appears incomplete in diff)
  • bundler-config-enhancement-spec.md (spec file - should this be in docs/ or removed?)

3. Security: Path Traversal Validation Incomplete

The ConfigFileLoader has path validation but it may not cover all attack vectors:

In configFile.ts (from diff):

private validateConfigPath(): void {
  const absPath = resolve(this.configFilePath)
  const cwd = process.cwd()
  const rel = relative(cwd, absPath)

  if (rel.startsWith("..") || (isAbsolute(rel) && !absPath.startsWith(cwd))) {
    throw new Error(...)
  }
}

Issues:

  • Symlink traversal not checked
  • Windows drive letter edge cases not fully validated
  • isAbsolute(rel) check is redundant (relative() never returns absolute paths)

Recommendation:

private validateConfigPath(): void {
  const absPath = realpathSync(this.configFilePath) // Resolves symlinks
  const cwd = realpathSync(process.cwd())
  const rel = relative(cwd, absPath)
  
  if (rel.startsWith('..') || isAbsolute(rel)) {
    throw new Error(`Config file must be within project directory`)
  }
}

⚠️ High Priority Issues

4. Missing Trailing Newlines

Per CLAUDE.md: "ALWAYS end all files with a trailing newline character."

Check these files:

  • package/configExporter/configFile.ts (likely missing based on diff)
  • package/configExporter/types.ts (verify)
  • bundler-config-enhancement-spec.md (verify)

5. Type Safety Issues

In cli.ts:368-416 - Environment variable handling:

for (const [key, value] of Object.entries(resolvedBuild.environment)) {
  process.env[key] = value  // ⚠️ 'value' is type 'any'
}

Should be:

for (const [key, value] of Object.entries(resolvedBuild.environment)) {
  if (typeof value === 'string') {
    process.env[key] = value
  } else {
    throw new Error(`Environment variable ${key} must be a string, got ${typeof value}`)
  }
}

6. fileWriter.ts Signature Mismatch

The diff shows generateFilename() now accepts a buildName parameter:

generateFilename(
  bundler: string,
  env: string,
  configType: "client" | "server" | "all",
  format: "yaml" | "json" | "inspect",
  buildName?: string  // New parameter
): string

But the implementation in the diff doesn't show how buildName is used. Verify the implementation is complete.


🟡 Medium Priority Issues

7. Error Handling Inconsistencies

In configFile.ts (from diff):

} catch (error: any) {
  throw new Error(
    `Failed to load config file ${this.configFilePath}: ${error.message}`
  )
}

Issue: If error is not an Error object, error.message will be undefined.

Better:

} catch (error) {
  const message = error instanceof Error ? error.message : String(error)
  throw new Error(`Failed to load config file ${this.configFilePath}: ${message}`)
}

8. YAML Security: FAILSAFE_SCHEMA Usage

Good use of FAILSAFE_SCHEMA to prevent code execution, but the json: true option may not be needed:

const parsed = loadYaml(content, {
  schema: FAILSAFE_SCHEMA,
  json: true  // ⚠️ What does this do with FAILSAFE_SCHEMA?
}) as BundlerConfigFile

Verify: Does json: true override schema restrictions? Test with malicious YAML.

9. Missing Input Validation

Environment variable expansion (from spec):

RAILS_ENV: ${RAILS_ENV:-production}

Questions:

  • Is there sanitization of expanded values?
  • What happens with ${RAILS_ENV:-$(rm -rf /)}?
  • Are environment variable names validated (no shell injection)?

Ensure regex-based expansion is safe and document the security model.

10. bundler_env Conversion Logic

The spec shows:

bundler_env:
  env: dev              # → --env env=dev
  instrumented: true    # → --env instrumented

Where is this conversion implemented? I don't see it in the cli.ts diff. Verify this is actually implemented and tested.


💡 Code Quality Suggestions

11. Bundler Resolution Logic is Complex

The precedence order (CLI → build-specific → default → auto-detect → fallback) is good but hard to test and debug.

Suggestions:

  • Extract into a separate resolveBundler() function
  • Add verbose logging for each decision point
  • Unit test each precedence level independently

12. Magic Strings

Multiple hardcoded strings like ".bundler-config.yml", "client", "server" appear throughout.

Consider:

const DEFAULT_CONFIG_FILE = ".bundler-config.yml"
const CONFIG_TYPES = {
  CLIENT: "client" as const,
  SERVER: "server" as const,
  ALL: "all" as const
}

13. listBuilds() Implementation Missing

The diff shows a call to loader.listBuilds() but doesn't show the implementation. Ensure it:

  • Handles empty builds gracefully
  • Shows clear output formatting
  • Includes descriptions and bundler info
  • Returns appropriate exit code

14. Spec File Location

bundler-config-enhancement-spec.md in the project root is unusual. Consider:

  • Move to docs/config-file-spec.md
  • Or remove if it was just for review purposes
  • Or convert to user-facing documentation

✅ Positive Aspects

  1. Excellent backward compatibility - all existing CLI usage continues to work
  2. Good separation of concerns - ConfigFileLoader, FileWriter, CLI are well-separated
  3. Comprehensive specification document - very helpful for understanding intent
  4. Security-conscious - path validation and FAILSAFE_SCHEMA are good starts
  5. Clear error messages - helpful for debugging
  6. Well-structured types - TypeScript interfaces are clean
  7. Consistent naming - follows existing project conventions

📋 Pre-Merge Checklist

Before this PR can be merged:

  • Add comprehensive test suite (CRITICAL)
  • Fix security issues (path traversal, type validation)
  • Verify all files included (configFile.ts appears truncated)
  • Add trailing newlines to all files (per CLAUDE.md)
  • Run yarn lint and fix all issues
  • Verify bundler_env conversion is implemented
  • Test with malicious YAML inputs (security validation)
  • Document environment variable expansion security model
  • Move or remove spec file (decide on final location)
  • Manual testing checklist in PR description - mark items complete
  • Test with both webpack and rspack (per PR description)

🎯 Recommendations

Before Merge:

  1. Add test suite - This is non-negotiable for 1000+ lines of new code
  2. Security audit - Especially path handling and YAML parsing
  3. Complete implementation - Verify configFile.ts and listBuilds()

After Merge (Future PRs):

  1. Add JSON schema validation for config file
  2. Add --validate command to check config file syntax
  3. Consider adding --exec command (as mentioned in spec)
  4. Add interactive mode for --init

Overall Assessment

Status: NEEDS WORK ⚠️

This is a valuable feature with a solid design, but it needs:

  1. Comprehensive test coverage
  2. Security fixes
  3. Implementation verification

The architecture is sound and the backward compatibility approach is excellent. With tests and security fixes, this will be a great addition to Shakapacker.

Estimated effort to address: 4-6 hours (mostly writing tests)

Copy link

claude bot commented Oct 10, 2025

Code Review - PR #686: YAML Configuration File Support

Overall Assessment

This is a well-designed feature that adds significant value. However, several critical issues need addressing before merging.

Critical Issues

  1. Missing Core Implementation: The configFile.ts appears truncated in the diff (cuts off at line ~145). Verify the file is complete.

  2. No Test Coverage: The PR adds ~1000+ lines without automated tests for:

    • ConfigFileLoader class
    • YAML parsing
    • Environment variable expansion
    • New CLI flags
    • Backward compatibility
  3. Missing Trailing Newlines: Per CLAUDE.md, all files must end with trailing newlines.

Type Safety Issues

  1. ExportOptions Missing Fields: types.ts is missing: init, configFile, build, listBuilds

  2. Missing Type Definitions: BundlerConfigFile, BuildConfig, ResolvedBuildConfig not defined in types.ts

  3. Missing buildName: ConfigMetadata missing buildName field used in cli.ts (lines 266, 342, 502)

  4. Function Signature Mismatch: generateFilename called with 5 params but only accepts 4

Strengths

  • Excellent YAML schema design
  • Proper backward compatibility
  • Good validation and error messages
  • Clear bundler resolution precedence
  • Great UX with --init and --list-builds

Recommendations

Before Merging:

  1. Fix truncated configFile.ts
  2. Add comprehensive test coverage
  3. Fix TypeScript type mismatches
  4. Add trailing newlines
  5. Run yarn lint and bundle exec rubocop
  6. Complete manual testing

Nice to Have:

  • Example config in examples/
  • JSON schema for IDE autocomplete
  • Migration guide
  • Troubleshooting docs

Security & Performance

  • No security concerns
  • Performance looks good

Conclusion

Valuable feature with solid design, but needs test coverage and bug fixes. TypeScript type mismatches and truncated file are blockers.

Great work on the design!

Copy link

@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)
package/configExporter/configFile.ts (1)

207-218: Coerce env values to strings before expansion (prevents runtime TypeError)

YAML scalars can be non-strings; calling replace on them will throw.

-    for (const [key, value] of Object.entries(vars)) {
-      expanded[key] = this.expandString(value, bundler)
-    }
+    for (const [key, value] of Object.entries(vars)) {
+      expanded[key] = this.expandString(String(value ?? ""), bundler)
+    }
🧹 Nitpick comments (3)
package/configExporter/configFile.ts (3)

35-45: Harden path traversal check (simplify and avoid brittle startsWith)

Use relative + isAbsolute only; drop the cwd prefix check which can be misleading on Windows and with missing separators.

   private validateConfigPath(): void {
-    const absPath = resolve(this.configFilePath)
-    const cwd = process.cwd()
+    const absPath = resolve(this.configFilePath)
+    const cwd = resolve(process.cwd())
     const rel = relative(cwd, absPath)

-    if (rel.startsWith("..") || (isAbsolute(rel) && !absPath.startsWith(cwd))) {
+    if (rel.startsWith("..") || isAbsolute(rel)) {
       throw new Error(
         `Config file must be within project directory. Attempted path: ${this.configFilePath}`
       )
     }
   }

181-186: Validate per-build configFile path is within project (same policy as loader path)

Prevent using a config outside the project (accidental or malicious).

-    const configFile = build.config
-      ? this.expandEnvironmentVariables({ config: build.config }, bundler)
-          .config
-      : undefined
+    const configFile = build.config
+      ? this.expandEnvironmentVariables({ config: build.config }, bundler).config
+      : undefined
+    if (configFile) {
+      const abs = resolve(configFile)
+      const cwd = resolve(process.cwd())
+      const rel = relative(cwd, abs)
+      if (rel.startsWith("..") || isAbsolute(rel)) {
+        throw new Error(`Per-build config must be within project directory. Got: ${configFile}`)
+      }
+    }

240-257: Support numeric bundler_env values (e.g., retries: 3 → --env retries=3)

Coerce numbers to strings; optionally update the type to include number.

-  private convertBundlerEnvToArgs(
-    bundlerEnv: Record<string, string | boolean>
-  ): string[] {
+  private convertBundlerEnvToArgs(
+    bundlerEnv: Record<string, string | boolean | number>
+  ): string[] {
@@
-      } else if (typeof value === "string") {
+      } else if (typeof value === "string" || typeof value === "number") {
         // String value becomes --env key=value
         args.push("--env", `${key}=${value}`)
       }

If you adopt this, also widen the type in BuildConfig.bundler_env in package/configExporter/types.ts:

// in types.ts
bundler_env?: Record<string, string | number | boolean>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1983922 and 2a0801b.

📒 Files selected for processing (1)
  • package/configExporter/configFile.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
package/configExporter/configFile.ts (1)
package/configExporter/types.ts (3)
  • BundlerConfigFile (45-48)
  • ExportOptions (1-20)
  • ResolvedBuildConfig (59-67)
🔇 Additional comments (1)
package/configExporter/configFile.ts (1)

68-75: Clarify YAML duplicate-key behavior when using json:true

js-yaml v4’s json: true follows JSON semantics (allows duplicate mapping keys, last-one-wins). Default (json: false) throws on duplicates. Choose one:

Option A (strict, throw on warnings):

-      const parsed = loadYaml(content, {
-        schema: FAILSAFE_SCHEMA,
-        json: true
-      }) as BundlerConfigFile
+      const parsed = loadYaml(content, {
+        schema: FAILSAFE_SCHEMA,
+        onWarning: (e) => { throw e }
+      }) as BundlerConfigFile

Option B (accept last-key-wins): keep json:true and document this behavior in the spec.

Confirm which behavior you want.

Comment on lines +104 to +133
// Validate each build
for (const [name, build] of Object.entries(config.builds)) {
if (
build.bundler &&
build.bundler !== "webpack" &&
build.bundler !== "rspack"
) {
throw new Error(
`Invalid bundler '${build.bundler}' in build '${name}'. Must be 'webpack' or 'rspack'.`
)
}

if (build.bundler_env && typeof build.bundler_env !== "object") {
throw new Error(
`Invalid bundler_env in build '${name}'. Must be an object.`
)
}

if (build.environment && typeof build.environment !== "object") {
throw new Error(
`Invalid environment in build '${name}'. Must be an object.`
)
}

if (build.outputs && !Array.isArray(build.outputs)) {
throw new Error(
`Invalid outputs in build '${name}'. Must be an array of strings.`
)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Strengthen schema validation (null/array checks, element/value types, config type)

Avoid arrays/null for maps; ensure env values are strings; ensure bundler_env values are string|boolean; ensure outputs are string[]; ensure config is string.

     // Validate each build
     for (const [name, build] of Object.entries(config.builds)) {
@@
-      if (build.bundler_env && typeof build.bundler_env !== "object") {
+      if (build.bundler_env) {
+        if (
+          typeof build.bundler_env !== "object" ||
+          build.bundler_env === null ||
+          Array.isArray(build.bundler_env)
+        ) {
+          throw new Error(
+            `Invalid bundler_env in build '${name}'. Must be an object.`
+          )
+        }
+        for (const [k, v] of Object.entries(build.bundler_env)) {
+          if (
+            !(typeof v === "string" || typeof v === "boolean")
+          ) {
+            throw new Error(
+              `Invalid bundler_env['${k}'] in build '${name}'. Must be string or boolean.`
+            )
+          }
+        }
-        throw new Error(
-          `Invalid bundler_env in build '${name}'. Must be an object.`
-        )
       }
@@
-      if (build.environment && typeof build.environment !== "object") {
-        throw new Error(
-          `Invalid environment in build '${name}'. Must be an object.`
-        )
-      }
+      if (build.environment) {
+        if (
+          typeof build.environment !== "object" ||
+          build.environment === null ||
+          Array.isArray(build.environment)
+        ) {
+          throw new Error(
+            `Invalid environment in build '${name}'. Must be an object.`
+          )
+        }
+        for (const [k, v] of Object.entries(build.environment)) {
+          if (typeof v !== "string") {
+            throw new Error(
+              `Invalid environment['${k}'] in build '${name}'. Must be a string.`
+            )
+          }
+        }
+      }
@@
-      if (build.outputs && !Array.isArray(build.outputs)) {
-        throw new Error(
-          `Invalid outputs in build '${name}'. Must be an array of strings.`
-        )
-      }
+      if (build.outputs) {
+        if (!Array.isArray(build.outputs) || !build.outputs.every((o) => typeof o === "string")) {
+          throw new Error(
+            `Invalid outputs in build '${name}'. Must be an array of strings.`
+          )
+        }
+      }
+      if (build.config && typeof build.config !== "string") {
+        throw new Error(
+          `Invalid config in build '${name}'. Must be a string path.`
+        )
+      }
📝 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
// Validate each build
for (const [name, build] of Object.entries(config.builds)) {
if (
build.bundler &&
build.bundler !== "webpack" &&
build.bundler !== "rspack"
) {
throw new Error(
`Invalid bundler '${build.bundler}' in build '${name}'. Must be 'webpack' or 'rspack'.`
)
}
if (build.bundler_env && typeof build.bundler_env !== "object") {
throw new Error(
`Invalid bundler_env in build '${name}'. Must be an object.`
)
}
if (build.environment && typeof build.environment !== "object") {
throw new Error(
`Invalid environment in build '${name}'. Must be an object.`
)
}
if (build.outputs && !Array.isArray(build.outputs)) {
throw new Error(
`Invalid outputs in build '${name}'. Must be an array of strings.`
)
}
}
// Validate each build
for (const [name, build] of Object.entries(config.builds)) {
if (
build.bundler &&
build.bundler !== "webpack" &&
build.bundler !== "rspack"
) {
throw new Error(
`Invalid bundler '${build.bundler}' in build '${name}'. Must be 'webpack' or 'rspack'.`
)
}
if (build.bundler_env) {
if (
typeof build.bundler_env !== "object" ||
build.bundler_env === null ||
Array.isArray(build.bundler_env)
) {
throw new Error(
`Invalid bundler_env in build '${name}'. Must be an object.`
)
}
for (const [k, v] of Object.entries(build.bundler_env)) {
if (!(typeof v === "string" || typeof v === "boolean")) {
throw new Error(
`Invalid bundler_env['${k}'] in build '${name}'. Must be string or boolean.`
)
}
}
}
if (build.environment) {
if (
typeof build.environment !== "object" ||
build.environment === null ||
Array.isArray(build.environment)
) {
throw new Error(
`Invalid environment in build '${name}'. Must be an object.`
)
}
for (const [k, v] of Object.entries(build.environment)) {
if (typeof v !== "string") {
throw new Error(
`Invalid environment['${k}'] in build '${name}'. Must be a string.`
)
}
}
}
if (build.outputs) {
if (
!Array.isArray(build.outputs) ||
!build.outputs.every((o) => typeof o === "string")
) {
throw new Error(
`Invalid outputs in build '${name}'. Must be an array of strings.`
)
}
}
if (build.config && typeof build.config !== "string") {
throw new Error(
`Invalid config in build '${name}'. Must be a string path.`
)
}
}
🤖 Prompt for AI Agents
In package/configExporter/configFile.ts around lines 104 to 133, the current
validation is too permissive: add checks that config.builds is an object (not
null/array), each build is an object, and reject null/array for maps; enforce
build.config is a string; enforce build.bundler_env is a plain object whose
values are only string or boolean; enforce build.environment is a plain object
whose values are only strings; enforce build.outputs, when present, is an array
and every element is a string; tighten bundler check remains the same. For each
failing condition throw an Error with the build name and offending key so
callers can pinpoint the exact invalid value.

Copy link

claude bot commented Oct 10, 2025

Code Review - PR #686: YAML Configuration Support

This PR adds YAML configuration file support to bin/export-bundler-config. The implementation is well-structured with good separation of concerns.

Strengths

  • Excellent architecture with dedicated ConfigFileLoader class
  • Strong security: Uses FAILSAFE_SCHEMA for YAML parsing
  • Path traversal protection and good error messages
  • Well-defined TypeScript interfaces
  • Excellent documentation and backward compatibility

Critical Issues

1. CRITICAL: Missing Test Coverage

The spec document shows testing tasks marked as TODO. This is a blocker. Add tests for:

  • Config file parsing
  • Build resolution
  • Environment variable expansion
  • Bundler override precedence
  • Backward compatibility

2. Missing Trailing Newlines

Per CLAUDE.md requirements, all files MUST end with a trailing newline.

3. Code Quality Issues

  • configFile.ts:39-42: Path validation may miss Windows edge cases
  • configFile.ts:68-73: Type cast bypasses safety - add runtime validation
  • configFile.ts:221-232: Regex doesn't handle nested braces
  • cli.ts:418-419: customConfigFile may need path resolution

4. Potential Bug

The resolveBuild() method creates bundlerEnvArgs but I don't see where they're used. Please verify.

5. Security

Add warning in generated config about not committing secrets.

Recommendations

Before Merging:

  1. Add comprehensive test coverage (CRITICAL)
  2. Ensure trailing newlines (per CLAUDE.md)
  3. Run yarn lint (per CLAUDE.md)
  4. Fix path resolution for customConfigFile
  5. Verify bundlerEnvArgs usage

Should Address:

  • Improve type safety in YAML parsing
  • Add security warning to template
  • Add edge case tests

Assessment

Well-architected code with good security practices. Main blocker is lack of test coverage. Should not be merged without tests.

Status: Changes Requested

Great work! 🚀

Copy link

claude bot commented Oct 10, 2025

Code Review - PR #686: YAML Configuration Support

Completed comprehensive review. This is a well-designed feature with excellent security practices and backward compatibility.

Strengths

  • Excellent security: Path traversal protection, FAILSAFE_SCHEMA for YAML parsing, input sanitization
  • Robust error handling with clear messages
  • Clean code structure following project patterns
  • Full backward compatibility
  • Great UX with --init, --list-builds commands

Critical Issues to Address

1. Missing Trailing Newlines

Per CLAUDE.md, all files must end with trailing newline. Check: bundler-config-enhancement-spec.md, configFile.ts, types.ts, cli.ts, fileWriter.ts, index.ts
Run: bundle exec rubocop and yarn lint

2. Missing Test Coverage (CRITICAL)

Need comprehensive tests for:

  • Config file parsing edge cases
  • Variable expansion (default value syntax)
  • Path traversal attack prevention
  • Bundler resolution precedence
  • Error handling paths
  • CLI argument parsing with new flags

3. Code Quality Issues

  • configFile.ts:72-73: Type assertion without runtime validation (FAILSAFE_SCHEMA returns strings, but expecting string OR boolean)
  • configFile.ts:39: Path traversal check has redundant condition (isAbsolute(rel) always false for relative paths)
  • cli.ts:392-394: process.env mutation side effects not documented
  • configFile.ts:218-233: Variable expansion doesn't handle edge cases (nested patterns, empty string vs undefined)

4. Missing Error Handling

  • cli.ts:203: No try-catch for writeFileSync (permissions, disk full)
  • cli.ts:368-416: Build config resolution needs try-catch blocks

Security Assessment: GOOD

  • YAML bomb protection
  • Path traversal prevention
  • Input sanitization
  • No arbitrary code execution
    Minor concern: Variable expansion could leak secrets if config files committed

Before Merge (Required):

  1. Add trailing newlines to all files
  2. Run bundle exec rubocop and yarn lint
  3. Add comprehensive test coverage
  4. Fix path traversal check logic
  5. Add error handling for file write operations

Nice to Have:

  • Add JSDoc to CLI functions
  • Move spec to docs/ directory
  • Improve variable expansion edge cases
  • Document env mutation side effects

Conclusion

High-quality PR with thoughtful design. Main concerns: missing tests (critical), trailing newlines, error handling edge cases.

Estimated effort: 4-6 hours for tests + 1 hour for fixes

Great work on security and backward compatibility!

justin808 and others added 2 commits October 9, 2025 19:50
This enhancement adds YAML configuration file support to bin/export-bundler-config,
allowing users to define and version control their build configurations.

Key Features:
- New --init command to generate sample .bundler-config.yml
- New --build=<name> flag to export specific build from config file
- New --list-builds command to show available builds
- New --webpack and --rspack flags for easy bundler switching
- Support for multiple build configurations with environment variables
- Environment variable expansion (${VAR}, ${VAR:-default}, ${BUNDLER})
- Automatic bundler_env to CLI args conversion
- Build-specific output naming (bundler-build-type.ext)
- Backward compatible with all existing CLI usage

File Structure:
- package/configExporter/configFile.ts - Config file loading and parsing
- package/configExporter/types.ts - Schema types for config file
- bundler-config-enhancement-spec.md - Full specification document

Usage Examples:
  bin/export-bundler-config --init
  bin/export-bundler-config --list-builds
  bin/export-bundler-config --build=dev --save
  bin/export-bundler-config --build=dev --save --rspack

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

Co-Authored-By: Claude <[email protected]>
Security Improvements:
- Use FAILSAFE_SCHEMA for YAML parsing to prevent code execution
- Add path validation to prevent directory traversal attacks
- Validate config file paths are within project directory

Documentation:
- Add comprehensive JSDoc comments to all public methods
- Document parameters, return values, and error conditions
- Add usage examples in class documentation

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

Co-Authored-By: Claude <[email protected]>
@justin808 justin808 force-pushed the justin808/bundler-config-yaml branch from 2a0801b to bf8f93a Compare October 10, 2025 05:50
Copy link

claude bot commented Oct 10, 2025

Code Review Summary

I've reviewed PR #686 adding YAML configuration file support for bin/export-bundler-config. Overall this is a well-designed feature with good backward compatibility, but I have several concerns and recommendations.

✅ Strengths

  1. Excellent backward compatibility - All existing CLI usage continues to work
  2. Clear separation of concerns - New ConfigFileLoader class is well-isolated
  3. Good error messages - Helpful guidance when config file is missing
  4. Comprehensive specification - The spec document is thorough and helpful
  5. Security-conscious - Path validation to prevent directory traversal
  6. Well-documented - Help text is comprehensive and includes examples

🔴 Critical Issues

1. Security: YAML Parsing Risk (configFile.ts:72-74)

const parsed = loadYaml(content, {
  schema: FAILSAFE_SCHEMA,
  json: true
}) as BundlerConfigFile

Good use of FAILSAFE_SCHEMA, but the json: true option may not be necessary and could introduce unexpected behavior. Consider removing it unless specifically needed.

2. Missing Input Validation (configFile.ts:244-264)

The expandEnvVariables function doesn't sanitize or validate expanded values. Environment variables could contain malicious content that gets executed by the shell. Consider:

  • Validating expanded values don't contain shell metacharacters
  • Escaping values before they're used in shell commands
  • Documenting security implications in the spec

3. Type Safety Issues (cli.ts:398-402)

for (const [key, value] of Object.entries(resolvedBuild.environment)) {
  process.env[key] = value
}

No validation that value is a string. TypeScript types alone won't prevent runtime issues if the YAML is malformed.

Recommendation: Add runtime type checking:

for (const [key, value] of Object.entries(resolvedBuild.environment)) {
  if (typeof value !== 'string') {
    throw new Error(`Environment variable ${key} must be a string, got ${typeof value}`)
  }
  process.env[key] = value
}

⚠️ Moderate Issues

4. Missing Error Handling (cli.ts:15-31)

The main run() function lacks try-catch for config file operations. If ConfigFileLoader throws an unexpected error, users get raw stack traces instead of helpful messages.

Recommendation:

try {
  // Handle --init command
  if (options.init) {
    return runInitCommand(options)
  }
  // ... rest of logic
} catch (error) {
  console.error(`[Config Exporter] Error: ${error.message}`)
  if (options.verbose) {
    console.error(error.stack)
  }
  return 1
}

5. Incomplete Type Definitions (types.ts)

The ExportOptions interface is missing the new fields mentioned in the diff (init, configFile, build, listBuilds). This will cause TypeScript compilation errors.

Required additions to types.ts:

export interface ExportOptions {
  // ... existing fields
  init?: boolean
  configFile?: string
  build?: string
  listBuilds?: boolean
}

export interface BundlerConfigFile {
  default_bundler?: "webpack" | "rspack"
  builds: Record<string, BuildConfig>
}

export interface BuildConfig {
  description?: string
  environment?: Record<string, string>
  bundler_env?: Record<string, string | boolean>
  outputs?: string[]
  config?: string
  bundler?: "webpack" | "rspack"
}

export interface ResolvedBuildConfig {
  name: string
  bundler: "webpack" | "rspack"
  environment: Record<string, string>
  bundlerEnvArgs: string[]
  outputs: string[]
  configFile?: string
}

export interface ConfigMetadata {
  // ... existing fields
  buildName?: string  // Add this
}

6. Missing File Extension Validation (fileWriter.ts:55-63)

The generateFilename function needs updating to support build names as mentioned in the spec, but the current implementation doesn't match the spec's promised format.

Expected format from spec: <bundler>-<build-name>-<output>.yml

Current implementation: <bundler>-<env>-<type>.{ext}

Recommendation: Update signature:

generateFilename(
  bundler: string,
  env: string,
  configType: "client" | "server" | "all",
  format: "yaml" | "json" | "inspect",
  buildName?: string  // Add this parameter
): string {
  const ext = format === "yaml" ? "yaml" : format === "json" ? "json" : "txt"
  const middle = buildName || env
  return `${bundler}-${middle}-${configType}.${ext}`
}

📋 Testing Concerns

7. No Test Files Visible

The PR doesn't include any test files for the new functionality. Critical areas needing tests:

  • ConfigFileLoader.load() with valid/invalid YAML
  • ConfigFileLoader.resolveBuild() with various scenarios
  • Environment variable expansion edge cases
  • Path traversal attack prevention
  • Bundler resolution precedence order
  • File naming with build names

Recommendation: Add comprehensive tests before merging. Example test cases:

// package/configExporter/__tests__/configFile.test.ts
describe('ConfigFileLoader', () => {
  it('should prevent path traversal attacks')
  it('should expand environment variables correctly')
  it('should handle missing config file gracefully')
  it('should validate bundler names')
  it('should resolve build with correct precedence')
})

🚀 Performance Considerations

8. Inefficient File Reading (configFile.ts)

Each call to load() reads and parses the YAML file. For --doctor mode or when listing builds multiple times, this could be optimized with caching.

Recommendation: Add simple memoization:

private cachedConfig?: BundlerConfigFile

load(): BundlerConfigFile {
  if (this.cachedConfig) return this.cachedConfig
  // ... existing load logic
  this.cachedConfig = parsed
  return parsed
}

💡 Code Quality Suggestions

9. Magic Strings

Constants like ".bundler-config.yml" appear in multiple places. Extract to constants:

// types.ts or constants.ts
export const DEFAULT_CONFIG_FILE = '.bundler-config.yml'
export const BUNDLER_PLACEHOLDER = '${BUNDLER}'

10. Inconsistent Error Messages

Some errors include hints, others don't. Standardize format:

throw new Error(
  `Config file not found: ${this.configFilePath}\n` +
  `💡 Hint: Run 'bin/export-bundler-config --init' to generate one.`
)

11. Missing JSDoc Comments

While configFile.ts has some JSDoc, many public methods lack documentation. Add comprehensive JSDoc for:

  • resolveBuild()
  • listBuilds()
  • expandEnvVariables()
  • generateSampleConfigFile()

📝 Documentation Issues

12. Spec File in Root Directory

The file bundler-config-enhancement-spec.md should probably be in a docs/ directory or removed before merge (it's a spec, not runtime code).

✅ Required Before Merge

  1. Add missing type definitions to types.ts
  2. Add comprehensive tests for new functionality
  3. Add runtime type validation for environment variables
  4. Fix generateFilename() to support build names as per spec
  5. Run linters - Ensure yarn lint and bundle exec rubocop pass
  6. Test the --init command - Verify the generated sample file is valid YAML
  7. Add trailing newlines to all files (per CLAUDE.md requirement)

🎯 Nice to Have

  1. Add caching for repeated config file reads
  2. Extract magic strings to constants
  3. Improve error handling in main run() function
  4. Add more examples to help text
  5. Consider moving spec file to docs/

Overall Assessment: This is a solid feature with good design, but it needs tests, type definition updates, and some security hardening before it's production-ready. The backward compatibility approach is excellent and the spec is very thorough.

Recommendation: Request changes for the critical and moderate issues above, particularly around testing and type safety.

Copy link

@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 (5)
package/configExporter/cli.ts (2)

135-147: Add support for --annotate flag.

Only --no-annotate is parsed. The PR claims backward compatibility including --annotate, but this flag is not handled.

Apply this diff:

   } else if (arg === "--no-annotate") {
     options.annotate = false
+  } else if (arg === "--annotate") {
+    options.annotate = true
   } else if (arg === "--verbose") {
     options.verbose = true

396-402: Config's NODE_ENV is ignored due to default value.

options.env defaults to "development" (line 63), so !options.env is always false and the config's NODE_ENV is never applied.

Track whether --env was explicitly set during parsing:

 function parseArguments(args: string[]): ExportOptions {
   const options: ExportOptions = {
     bundler: undefined,
-    env: "development",
+    env: undefined,
     clientOnly: false,

Then apply a default later:

 function applyDefaults(options: ExportOptions): void {
+  if (options.env === undefined) {
+    options.env = "development"
+  }
   if (options.doctor) {
package/configExporter/fileWriter.ts (1)

47-67: Standardize file extension to .yaml.

Comments and examples mix .yml and .yaml. The code uses .yaml (line 64), but examples show .yml. Standardize to .yaml throughout.

Update examples:

    # Optional: Override config file path
    # Supports ${"$"}{BUNDLER} substitution (webpack/rspack)
-   # config: ${"$"}{BUNDLER}.config.ts  # Defaults to auto-detected file
+   # config: webpack.config.ts  # Defaults to auto-detected file

Apply this diff:

    *   webpack-development-client.yaml
    *   rspack-production-server.yaml
-   *   webpack-dev-client.yaml (with build name)
-   *   rspack-cypress-dev-server.yaml (with build name)
+   *   webpack-dev-client.yaml (with build name)
+   *   rspack-cypress-dev-server.yaml (with build name)
package/configExporter/configFile.ts (2)

85-134: Strengthen validation to reject null, arrays, and invalid value types.

Current validation is too permissive:

  • builds could be null or an array
  • bundler_env values aren't validated as string|boolean
  • environment values aren't validated as strings
  • outputs elements aren't validated as strings
  • config isn't validated as a string

Apply this diff:

   private validate(config: BundlerConfigFile): void {
-    if (!config.builds || typeof config.builds !== "object") {
+    if (!config.builds || typeof config.builds !== "object" || Array.isArray(config.builds)) {
       throw new Error("Config file must contain a 'builds' object")
     }

     // Validate each build
     for (const [name, build] of Object.entries(config.builds)) {
+      if (!build || typeof build !== "object" || Array.isArray(build)) {
+        throw new Error(`Build '${name}' must be an object`)
+      }
+
       if (
         build.bundler &&

-      if (build.bundler_env && typeof build.bundler_env !== "object") {
+      if (build.bundler_env) {
+        if (typeof build.bundler_env !== "object" || build.bundler_env === null || Array.isArray(build.bundler_env)) {
+          throw new Error(`Invalid bundler_env in build '${name}'. Must be an object.`)
+        }
+        for (const [k, v] of Object.entries(build.bundler_env)) {
+          if (typeof v !== "string" && typeof v !== "boolean") {
+            throw new Error(`Invalid bundler_env['${k}'] in build '${name}'. Must be string or boolean.`)
+          }
+        }
-        throw new Error(
-          `Invalid bundler_env in build '${name}'. Must be an object.`
-        )
       }

-      if (build.environment && typeof build.environment !== "object") {
+      if (build.environment) {
+        if (typeof build.environment !== "object" || build.environment === null || Array.isArray(build.environment)) {
+          throw new Error(`Invalid environment in build '${name}'. Must be an object.`)
+        }
+        for (const [k, v] of Object.entries(build.environment)) {
+          if (typeof v !== "string") {
+            throw new Error(`Invalid environment['${k}'] in build '${name}'. Must be a string.`)
+          }
+        }
-        throw new Error(
-          `Invalid environment in build '${name}'. Must be an object.`
-        )
       }

-      if (build.outputs && !Array.isArray(build.outputs)) {
+      if (build.outputs) {
+        if (!Array.isArray(build.outputs) || !build.outputs.every(o => typeof o === "string")) {
+          throw new Error(`Invalid outputs in build '${name}'. Must be an array of strings.`)
+        }
-        throw new Error(
-          `Invalid outputs in build '${name}'. Must be an array of strings.`
-        )
       }
+
+      if (build.config && typeof build.config !== "string") {
+        throw new Error(`Invalid config in build '${name}'. Must be a string path.`)
+      }
     }
   }

207-218: Coerce values to strings before expansion.

YAML scalars may be non-strings (numbers, booleans, null). Calling .replace() on these will throw.

Apply this diff:

     for (const [key, value] of Object.entries(vars)) {
-      expanded[key] = this.expandString(value, bundler)
+      expanded[key] = this.expandString(String(value), bundler)
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a0801b and bf8f93a.

📒 Files selected for processing (6)
  • bundler-config-enhancement-spec.md (1 hunks)
  • package/configExporter/cli.ts (13 hunks)
  • package/configExporter/configFile.ts (1 hunks)
  • package/configExporter/fileWriter.ts (1 hunks)
  • package/configExporter/index.ts (1 hunks)
  • package/configExporter/types.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package/configExporter/index.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

End every file with a trailing newline character

Files:

  • package/configExporter/types.ts
  • package/configExporter/fileWriter.ts
  • package/configExporter/configFile.ts
  • package/configExporter/cli.ts
  • bundler-config-enhancement-spec.md
🧬 Code graph analysis (3)
package/configExporter/types.ts (1)
package/configExporter/index.ts (3)
  • BundlerConfigFile (6-6)
  • BuildConfig (7-7)
  • ResolvedBuildConfig (8-8)
package/configExporter/configFile.ts (1)
package/configExporter/types.ts (3)
  • BundlerConfigFile (45-48)
  • ExportOptions (1-20)
  • ResolvedBuildConfig (59-67)
package/configExporter/cli.ts (2)
package/configExporter/configFile.ts (2)
  • ConfigFileLoader (17-284)
  • generateSampleConfigFile (290-451)
package/configExporter/types.ts (1)
  • ExportOptions (1-20)
🪛 markdownlint-cli2 (0.18.1)
bundler-config-enhancement-spec.md

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

(MD040, fenced-code-language)

🔇 Additional comments (7)
package/configExporter/types.ts (1)

15-67: LGTM!

The type definitions are well-structured and provide clear contracts for the config file schema and resolution logic. The optional fields and union types appropriately model the different configuration scenarios.

package/configExporter/cli.ts (2)

200-228: LGTM!

The init command implementation correctly generates the sample config file and provides helpful next-steps guidance.


230-234: LGTM!

The list-builds command delegates to the loader appropriately.

package/configExporter/configFile.ts (4)

60-83: LGTM!

Using FAILSAFE_SCHEMA prevents code execution via YAML parsing, which is the correct security posture for untrusted config files.

Based on learnings


35-45: LGTM!

Path traversal protection correctly prevents configs from outside the project directory.


144-196: LGTM!

The build resolution logic correctly applies precedence for bundler selection and properly expands environment variables.


290-451: LGTM!

The sample config file is comprehensive and well-documented with clear examples for various build scenarios.

Comment on lines 216 to 238
```
Available builds in .bundler-config.yml:
dev
Description: Build admin and consumer bundles for dev env
Bundler: webpack (default)
Outputs: client, server
dev-server
Description: Build server bundle for dev env
Bundler: webpack (default)
Outputs: server
cypress-dev
Description: Build for Cypress development
Bundler: webpack (default)
Outputs: client, server
prod-server
Description: Build server bundle for production
Bundler: webpack (default)
Outputs: server
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language identifier to fenced code block.

The code block is missing a language identifier, which reduces readability and tooling support.

Apply this diff:

-```
+```text
 Available builds in .bundler-config.yml:
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

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

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In bundler-config-enhancement-spec.md around lines 216 to 238, the fenced code
block lacks a language identifier; update the opening fence to include "text"
(i.e., change ``` to ```text) so the block becomes ```text and leave the closing
fence as-is to improve readability and tooling support.

Copy link

claude bot commented Oct 10, 2025

Code Review: YAML Configuration Support

Thank you for this comprehensive PR! Below is my detailed review.

Strengths

  1. Excellent Architecture - Clean separation of concerns with dedicated configFile.ts module
  2. Strong Security - Path traversal protection and safe YAML parsing using FAILSAFE_SCHEMA
  3. Good Documentation - Comprehensive spec document and helpful comments

Critical Issues

1. Missing Type Definitions

The types.ts file is missing several type definitions referenced in the code (BundlerConfigFile, BuildConfig, ResolvedBuildConfig, buildName field).

Action: Verify all types are properly defined in types.ts

2. FileWriter Signature Mismatch

generateFilename is called with 5 parameters (including buildName) but only accepts 4.

Action: Update signature to include optional buildName parameter

3. Incomplete Validation Logic

Validation for --build flag only checks config file existence when options.configFile is undefined.

Fix: Always validate when --build is specified

Significant Issues

4. Environment Variable Overwriting

Environment variables from build config overwrite existing ones without warning.

Consider: Add precedence check or logging

5. Type Assertion Without Validation

cli.ts:485-486 uses unsafe type assertion for output types.

Fix: Validate before casting

6. Index Out of Bounds

Accessing buildOutputs[index] without bounds checking.

Fix: Add bounds check

Code Quality

  1. Missing Trailing Newlines - Per CLAUDE.md requirements
  2. Error Handling - runListBuildsCommand lacks try-catch

Testing Concerns

  1. No Automated Tests - Missing tests for config parsing, env expansion, security checks
  2. Linting - Run yarn lint per CLAUDE.md

Additional

  1. Help Text - Keep both --doctor and config file workflows visible
  2. Performance - Consider caching config file loading

Summary

Must Fix: Types, FileWriter signature, validation logic, trailing newlines, linting, tests

Should Fix: Error handling, type validation, bounds checking, env var warnings

Recommendation: Request Changes - Address critical issues and add test coverage before approval.

Great work on the design and backward compatibility! Once issues are addressed, this will be valuable.

Critical Fixes:
- Fix --build validation to always check config file existence
- Add bounds checking and type validation for buildOutputs array
- Add try-catch error handling to runListBuildsCommand
- Improve error messages with specific config file paths

Type Safety:
- Add explicit validation before casting output types
- Check array bounds before accessing buildOutputs[index]
- Validate output values are valid config types (client/server/all)

Error Handling:
- Return proper exit codes from runListBuildsCommand
- Better error messages for missing config files
- Warn on invalid output types instead of silent failures

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

Co-Authored-By: Claude <[email protected]>
Copy link

@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: 0

♻️ Duplicate comments (2)
package/configExporter/cli.ts (2)

135-147: Restore support for --annotate.

Legacy callers rely on --annotate, but we only parse --no-annotate, so the flag silently stops working. Please add the explicit handler to keep backward compatibility.

Apply this diff:

   } else if (arg === "--no-annotate") {
     options.annotate = false
+  } else if (arg === "--annotate") {
+    options.annotate = true

381-408: Config build NODE_ENV never applied.

options.env defaults to "development", so !options.env is never true and the build’s NODE_ENV can’t win. This forces every --build export to stay in dev even when the config asks for prod/test. Track whether the user passed --env and only block the override when they did.

Apply this diff:

-function parseArguments(args: string[]): ExportOptions {
-  const options: ExportOptions = {
+function parseArguments(args: string[]): ExportOptions {
+  const options: ExportOptions & { envWasExplicit?: boolean } = {
     bundler: undefined,
-    env: "development",
+    env: "development",
@@
     } else if (arg.startsWith("--env=")) {
       const env = parseValue(arg, "--env=")
       if (env !== "development" && env !== "production" && env !== "test") {
         throw new Error(
           `Invalid environment '${env}'. Must be 'development', 'production', or 'test'.`
         )
       }
       options.env = env
+      options.envWasExplicit = true
@@
-  return options
+  return options
 }
@@
-    if (resolvedBuild.environment.NODE_ENV && !options.env) {
+    const envWasExplicit =
+      (options as ExportOptions & { envWasExplicit?: boolean }).envWasExplicit === true
+    if (resolvedBuild.environment.NODE_ENV && !envWasExplicit) {
       env = resolvedBuild.environment.NODE_ENV as
         | "development"
         | "production"
         | "test"
     }
🧹 Nitpick comments (1)
package/index.d.ts (1)

20-22: Keep env types narrow for better safety.

railsEnv and nodeEnv are now typed as any, which drops the string semantics we rely on across the codebase. Please keep these as string | undefined (or the narrowest accurate type) so downstream consumers retain proper type checking.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf8f93a and 9b34de3.

📒 Files selected for processing (2)
  • package/configExporter/cli.ts (13 hunks)
  • package/index.d.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

End every file with a trailing newline character

Files:

  • package/index.d.ts
  • package/configExporter/cli.ts
🧬 Code graph analysis (1)
package/configExporter/cli.ts (2)
package/configExporter/configFile.ts (2)
  • ConfigFileLoader (17-284)
  • generateSampleConfigFile (290-451)
package/configExporter/types.ts (1)
  • ExportOptions (1-20)

The spec document is already available in issue #687.
Removing it from the PR to avoid prettier formatting conflicts in CI.

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

Co-Authored-By: Claude <[email protected]>
@justin808
Copy link
Member Author

The main branch has been updated with significant changes to CLI parsing (migration to yargs). To avoid complex merge conflicts, I'll create a fresh PR based on the latest main with all the YAML configuration features integrated properly with the new yargs approach. This PR will be closed in favor of the new one.

@justin808
Copy link
Member Author

Closing this PR in favor of #699 which is based on the latest main and integrates with the new yargs-based CLI. All functionality from this PR has been reimplemented in #699 with the same features plus integration with the updated CLI architecture.

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