shakapacker 9.5.0 → 9.6.0.beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b214b9c8a64b8d7a39fa119818f9cd85e872abf2278ca9659685ebfe73d3324
4
- data.tar.gz: 06a7f954d5909f19df8cf13188b398e0b3ce2d5b4b17c6d74eca65e8573e6a30
3
+ metadata.gz: 16a87e8e339c085afc34f5f70af6fee7aa534590949f02faa1863d9c5e7f9f7d
4
+ data.tar.gz: 19d1f859e00dc29bfe4c593ba1b4a1e5b64c6347fc1b56a2cbabcc8c8020205b
5
5
  SHA512:
6
- metadata.gz: f728d9d77e03e1c8113edefdd426fd96b900eb64ce7c8e6470cd2e78f45e2f326645d58c31a95dc383809cfebef6af157cd06c145c9204d9975368212e842cb1
7
- data.tar.gz: 7feac095bbf2182d7e87c21fd8f739cbf774ae1bdb8ec55dbe297e76b154f00b81cbabb3ee6f838dda9fd42b67fe718a749b4778924cf23c8f65b896e6a1b7da
6
+ metadata.gz: f4286284b7cbbec5324ce2ad26c6d6892cfcecae96e31c7324ff0a533f332301b4008e7083592b3b4b639be131758ab6fa5bc81ed46ac0903f2370f20a05fae0
7
+ data.tar.gz: e4e6bfdd5837dabfd9f5d031e5a1d905ca88b6c1d50b1f3d440fb3f3c6a23d0abbec1d8839a769536af52d67eed5657ec602786c824dd57156f1678026ec300e
@@ -2,7 +2,7 @@ name: Claude Code Review
2
2
 
3
3
  on:
4
4
  pull_request:
5
- types: [opened, synchronize]
5
+ types: [opened, synchronize, ready_for_review, reopened]
6
6
  # Optional: Only run on specific file changes
7
7
  # paths:
8
8
  # - "src/**/*.ts"
@@ -36,18 +36,9 @@ jobs:
36
36
  uses: anthropics/claude-code-action@v1
37
37
  with:
38
38
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39
- prompt: |
40
- Please review this pull request and provide feedback on:
41
- - Code quality and best practices
42
- - Potential bugs or issues
43
- - Performance considerations
44
- - Security concerns
45
- - Test coverage
46
-
47
- Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
48
-
49
- Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
50
-
39
+ plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
40
+ plugins: 'code-review@claude-code-plugins'
41
+ prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
51
42
  # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
52
- # or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options
53
- claude_args: '--model claude-sonnet-4-5-20250929 --allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
43
+ # or https://code.claude.com/docs/en/cli-reference for available options
44
+
@@ -45,5 +45,6 @@ jobs:
45
45
 
46
46
  # Optional: Add claude_args to customize behavior and configuration
47
47
  # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
48
- # or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options
49
- # claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
48
+ # or https://code.claude.com/docs/en/cli-reference for available options
49
+ # claude_args: '--allowed-tools Bash(gh pr:*)'
50
+
data/.prettierignore CHANGED
@@ -1,3 +1,7 @@
1
1
  # Template files that should maintain their original quote style
2
2
  lib/install/config/**/*.ts
3
3
  lib/install/config/**/*.js
4
+
5
+ # Temporarily ignore workflow files that cannot be modified in PRs due to claude-review validation
6
+ .github/workflows/claude.yml
7
+ .github/workflows/claude-code-review.yml
data/.rubocop.yml CHANGED
@@ -10,6 +10,7 @@ AllCops:
10
10
  - "node_modules/**/*"
11
11
  - "_actions/**/*"
12
12
  - "spec/dummy-rspack/**/*"
13
+ - "bin/conductor-exec" # Shell script, not Ruby
13
14
 
14
15
  # Prefer &&/|| over and/or.
15
16
  Style/AndOr:
data/CHANGELOG.md CHANGED
@@ -11,7 +11,38 @@
11
11
 
12
12
  Changes since the last non-beta release.
13
13
 
14
- _None yet._
14
+ ### Changed
15
+
16
+ - **BREAKING: sass-loader now defaults to modern Sass API**. [PR #879](https://github.com/shakacode/shakapacker/pull/879) by [justin808](https://github.com/justin808). The sass-loader configuration now uses `api: "modern"` instead of the deprecated legacy API. This improves compatibility with plugins like sass-resources-loader that require the modern API. If you experience issues after upgrading, you can revert to the legacy API by customizing your webpack config:
17
+
18
+ ```javascript
19
+ // config/webpack/webpack.config.js
20
+ const { generateWebpackConfig } = require("shakapacker")
21
+ const config = generateWebpackConfig()
22
+
23
+ // Find and modify sass-loader options
24
+ config.module.rules.forEach((rule) => {
25
+ if (rule.use) {
26
+ rule.use.forEach((loader) => {
27
+ if (loader.loader?.includes("sass-loader")) {
28
+ loader.options.api = "legacy"
29
+ }
30
+ })
31
+ }
32
+ })
33
+
34
+ module.exports = config
35
+ ```
36
+
37
+ ### Fixed
38
+
39
+ - **Fixed orphaned webpack/rspack processes when foreman receives SIGTERM**. [PR #888](https://github.com/shakacode/shakapacker/pull/888) by [jordan-brough](https://github.com/jordan-brough). When running under foreman, sending SIGTERM to foreman (e.g. `kill <pid>`) would kill the Ruby shakapacker process but leave the webpack/rspack child process running as an orphan. DevServerRunner now uses `exec` to replace the Ruby process entirely, and Runner uses `spawn` with SIGTERM forwarding to ensure the child process is properly terminated.
40
+ - **Fixed installer writing wrong shakapacker version in package.json**. [PR #899](https://github.com/shakacode/shakapacker/pull/899) by [justin808](https://github.com/justin808). The `shakapacker:install` generator now keeps the `package.json` dependency value in sync with the exact version or path that was requested, instead of relying on the post-install value which could differ.
41
+ - **Fixed NODE_ENV=test causing DefinePlugin warnings**. [PR #870](https://github.com/shakacode/shakapacker/pull/870) by [justin808](https://github.com/justin808). When RAILS_ENV=test, Shakapacker now sets NODE_ENV=development instead of NODE_ENV=test. This prevents webpack/rspack DefinePlugin conflicts since these bundlers only recognize "development" and "production" as valid NODE_ENV values.
42
+
43
+ ### Documentation
44
+
45
+ - **Added CDN limitation warnings for Early Hints feature**. [PR #878](https://github.com/shakacode/shakapacker/pull/878) by [justin808](https://github.com/justin808). The early hints documentation now prominently notes that most CDNs (Cloudflare, AWS CloudFront, AWS ALB) strip HTTP 103 responses before they reach end users. Debug mode also includes CDN warnings in HTML comments.
15
46
 
16
47
  ## [v9.5.0] - January 7, 2026
17
48
 
@@ -256,7 +287,6 @@ See the [v9 Upgrade Guide](https://github.com/shakacode/shakapacker/blob/main/do
256
287
  ### ⚠️ Breaking Changes
257
288
 
258
289
  1. **SWC is now the default JavaScript transpiler instead of Babel** ([PR 603](https://github.com/shakacode/shakapacker/pull/603) by [justin808](https://github.com/justin808))
259
-
260
290
  - Babel dependencies are no longer included as peer dependencies
261
291
  - Improves compilation speed by 20x
262
292
  - **Migration for existing projects:**
@@ -273,7 +303,6 @@ See the [v9 Upgrade Guide](https://github.com/shakacode/shakapacker/blob/main/do
273
303
  ```
274
304
 
275
305
  2. **CSS Modules now use named exports by default** ([PR 599](https://github.com/shakacode/shakapacker/pull/599))
276
-
277
306
  - **JavaScript:** Use named imports: `import { className } from './styles.module.css'`
278
307
  - **TypeScript:** Use namespace imports: `import * as styles from './styles.module.css'`
279
308
  - To keep the old behavior with default imports, see [CSS Modules Export Mode documentation](./docs/css-modules-export-mode.md) for configuration instructions
@@ -539,7 +568,6 @@ See the [v8 Upgrade Guide](https://github.com/shakacode/shakapacker/blob/main/do
539
568
 
540
569
  - Set `source_entry_path` to `packs` and `nested_entries` to `true` in`shakapacker.yml` [PR 284](https://github.com/shakacode/shakapacker/pull/284) by [ahangarha](https://github.com/ahangarha).
541
570
  - Dev server configuration is modified to follow [webpack recommended configurations](https://webpack.js.org/configuration/dev-server/) for dev server. [PR276](https://github.com/shakacode/shakapacker/pull/276) by [ahangarha](https://github.com/ahangarha):
542
-
543
571
  - Deprecated `https` entry is removed from the default configuration file, allowing to set `server` or `https` as per the project requirements. For more detail, check webpack documentation. The `https` entry can be effective only if there is no `server` entry in the config file.
544
572
  - `allowed_hosts` is now set to `auto` instead of `all` by default.
545
573
 
data/CLAUDE.md CHANGED
@@ -45,3 +45,11 @@
45
45
  - This gem supports both webpack and rspack configurations
46
46
  - Test changes with both bundlers when modifying core functionality
47
47
  - Be aware of the dual package.json/Gemfile dependency management
48
+
49
+ ## Conductor Environment
50
+
51
+ - **Version manager support**: The setup script detects mise, asdf, or direct PATH tools (rbenv/nvm/nodenv)
52
+ - **bin/conductor-exec**: Use this wrapper for commands when tool versions aren't detected correctly in Conductor's non-interactive shell
53
+ - Example: `bin/conductor-exec bundle exec rubocop`
54
+ - The wrapper uses `mise exec` if mise is available, otherwise falls back to direct execution
55
+ - **conductor.json scripts** already use this wrapper, so you typically don't need to use it manually
@@ -0,0 +1,24 @@
1
+ #!/bin/zsh
2
+ # Wrapper script to run commands in Conductor with proper tool versions
3
+ # Usage: bin/conductor-exec <command> [args...]
4
+ #
5
+ # This is needed because Conductor (and other non-interactive shells) don't
6
+ # source .zshrc, so version manager PATH configuration isn't active by default.
7
+ #
8
+ # - If mise is available: uses `mise exec` for correct tool versions
9
+ # - Otherwise: falls back to direct execution (for asdf/rbenv/nvm users)
10
+ #
11
+ # Examples:
12
+ # bin/conductor-exec ruby --version # Uses correct Ruby version
13
+ # bin/conductor-exec bundle exec rubocop # Correct Ruby for linting
14
+ # bin/conductor-exec git commit -m "msg" # Pre-commit hooks work correctly
15
+ # bin/conductor-exec yarn install # Uses correct Node version
16
+ #
17
+ # See: https://github.com/shakacode/react_on_rails-demos/issues/105
18
+
19
+ if command -v mise &> /dev/null; then
20
+ exec mise exec -- "$@"
21
+ else
22
+ # Fall back to direct execution for non-mise users
23
+ exec "$@"
24
+ fi
data/conductor-setup.sh CHANGED
@@ -1,43 +1,124 @@
1
- #!/usr/bin/env bash
1
+ #!/bin/zsh
2
2
  set -euo pipefail
3
3
 
4
4
  echo "🔧 Setting up Shakapacker workspace..."
5
5
 
6
- # Set up Ruby version if asdf is available
7
- if command -v asdf &> /dev/null; then
8
- echo "📝 Using asdf Ruby version management..."
9
- # Ensure we have the right Ruby version file
10
- echo "ruby 3.3.4" > .tool-versions
11
- # Use asdf exec to run commands with the right Ruby version
12
- BUNDLE_CMD="asdf exec bundle"
6
+ # Detect and initialize version manager
7
+ # Supports: mise, asdf, or direct PATH (rbenv/nvm/nodenv already in PATH)
8
+ VERSION_MANAGER="none"
9
+
10
+ echo "📋 Detecting version manager..."
11
+
12
+ if command -v mise &> /dev/null; then
13
+ VERSION_MANAGER="mise"
14
+ echo "✅ Found mise"
15
+ # Trust mise config for current directory only
16
+ mise trust 2>/dev/null || true
17
+ elif [[ -f ~/.asdf/asdf.sh ]]; then
18
+ VERSION_MANAGER="asdf"
19
+ source ~/.asdf/asdf.sh
20
+ echo "✅ Found asdf (from ~/.asdf/asdf.sh)"
21
+ elif command -v asdf &> /dev/null; then
22
+ VERSION_MANAGER="asdf"
23
+ # For homebrew-installed asdf
24
+ if [[ -f /opt/homebrew/opt/asdf/libexec/asdf.sh ]]; then
25
+ source /opt/homebrew/opt/asdf/libexec/asdf.sh
26
+ fi
27
+ echo "✅ Found asdf"
13
28
  else
14
- BUNDLE_CMD="bundle"
29
+ echo "ℹ️ No version manager detected, using system PATH"
30
+ echo " (Assuming rbenv/nvm/nodenv or system tools are already configured)"
31
+ fi
32
+
33
+ # Ensure version config exists for asdf/mise users
34
+ if [[ "$VERSION_MANAGER" != "none" ]] && [[ ! -f .tool-versions ]] && [[ ! -f .mise.toml ]]; then
35
+ echo "📝 Creating .tool-versions from project version files..."
36
+
37
+ # Read Ruby version from .ruby-version or use default
38
+ if [[ -f .ruby-version ]]; then
39
+ RUBY_VER=$(cat .ruby-version | tr -d '[:space:]')
40
+ else
41
+ RUBY_VER="3.3.4" # Default: recent stable Ruby
42
+ fi
43
+
44
+ # Read Node version from .node-version or use default
45
+ if [[ -f .node-version ]]; then
46
+ NODE_VER=$(cat .node-version | tr -d '[:space:]')
47
+ else
48
+ NODE_VER="20.18.0" # Default: LTS Node
49
+ fi
50
+
51
+ cat > .tool-versions << EOF
52
+ ruby $RUBY_VER
53
+ nodejs $NODE_VER
54
+ EOF
55
+ echo " Using Ruby $RUBY_VER, Node $NODE_VER"
15
56
  fi
16
57
 
17
- # Check for required tools
18
- if ! $BUNDLE_CMD --version &> /dev/null; then
19
- echo " Error: Ruby bundler is not installed"
20
- echo "Please install bundler first: gem install bundler"
58
+ # Install tools via mise (after .tool-versions exists)
59
+ if [[ "$VERSION_MANAGER" == "mise" ]]; then
60
+ echo "📦 Installing tools via mise..."
61
+ mise install
62
+ fi
63
+
64
+ # Helper function to run commands with the detected version manager
65
+ run_cmd() {
66
+ if [[ "$VERSION_MANAGER" == "mise" ]] && [[ -x "bin/conductor-exec" ]]; then
67
+ bin/conductor-exec "$@"
68
+ else
69
+ "$@"
70
+ fi
71
+ }
72
+
73
+ # Check required tools
74
+ echo "📋 Checking required tools..."
75
+ run_cmd ruby --version >/dev/null 2>&1 || { echo "❌ Error: Ruby is not installed or not in PATH."; exit 1; }
76
+ run_cmd node --version >/dev/null 2>&1 || { echo "❌ Error: Node.js is not installed or not in PATH."; exit 1; }
77
+
78
+ # Check Ruby version
79
+ RUBY_VERSION=$(run_cmd ruby -v | awk '{print $2}')
80
+ MIN_RUBY_VERSION="2.7.0"
81
+ if [[ $(echo -e "$MIN_RUBY_VERSION\n$RUBY_VERSION" | sort -V | head -n1) != "$MIN_RUBY_VERSION" ]]; then
82
+ echo "❌ Error: Ruby version $RUBY_VERSION is too old. Shakapacker requires Ruby >= 2.7.0"
83
+ echo " Please upgrade Ruby using your version manager or system package manager."
21
84
  exit 1
22
85
  fi
86
+ echo "✅ Ruby version: $RUBY_VERSION"
23
87
 
24
- if ! command -v yarn &> /dev/null; then
25
- echo "❌ Error: Yarn is not installed"
26
- echo "Please install yarn first"
88
+ # Check Node version
89
+ NODE_VERSION=$(run_cmd node -v | cut -d'v' -f2)
90
+ MIN_NODE_VERSION="14.0.0"
91
+ if [[ $(echo -e "$MIN_NODE_VERSION\n$NODE_VERSION" | sort -V | head -n1) != "$MIN_NODE_VERSION" ]]; then
92
+ echo "❌ Error: Node.js version v$NODE_VERSION is too old. Shakapacker requires Node.js >= 14.0.0"
93
+ echo " Please upgrade Node.js using your version manager or system package manager."
27
94
  exit 1
28
95
  fi
96
+ echo "✅ Node.js version: v$NODE_VERSION"
97
+
98
+ # Copy any environment files from root if they exist
99
+ if [ -n "${CONDUCTOR_ROOT_PATH:-}" ]; then
100
+ if [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
101
+ echo "📝 Copying .env file..."
102
+ cp "$CONDUCTOR_ROOT_PATH/.env" .env
103
+ fi
104
+
105
+ if [ -f "$CONDUCTOR_ROOT_PATH/.env.local" ]; then
106
+ echo "📝 Copying .env.local file..."
107
+ cp "$CONDUCTOR_ROOT_PATH/.env.local" .env.local
108
+ fi
109
+ fi
29
110
 
30
111
  # Install Ruby dependencies
31
- echo "📦 Installing Ruby dependencies..."
32
- $BUNDLE_CMD install
112
+ echo "💎 Installing Ruby dependencies..."
113
+ run_cmd bundle install
33
114
 
34
115
  # Install JavaScript dependencies
35
116
  echo "📦 Installing JavaScript dependencies..."
36
- yarn install --frozen-lockfile
117
+ run_cmd yarn install --frozen-lockfile
37
118
 
38
119
  # Set up Husky git hooks
39
120
  echo "🪝 Setting up Husky git hooks..."
40
- npx husky
121
+ run_cmd npx husky
41
122
  if [ ! -f .husky/pre-commit ]; then
42
123
  echo "Creating pre-commit hook..."
43
124
  cat > .husky/pre-commit << 'EOF'
@@ -47,24 +128,20 @@ EOF
47
128
  chmod +x .husky/pre-commit
48
129
  fi
49
130
 
50
- # Copy environment files if they exist in root
51
- if [ -n "${CONDUCTOR_ROOT_PATH:-}" ]; then
52
- if [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
53
- echo "📋 Copying .env file from root..."
54
- cp "$CONDUCTOR_ROOT_PATH/.env" .env
55
- fi
56
-
57
- if [ -f "$CONDUCTOR_ROOT_PATH/.env.local" ]; then
58
- echo "📋 Copying .env.local file from root..."
59
- cp "$CONDUCTOR_ROOT_PATH/.env.local" .env.local
60
- fi
61
- fi
131
+ # Verify linting tools are available
132
+ echo "✅ Verifying linting tools..."
133
+ run_cmd bundle exec rubocop --version
62
134
 
63
- echo " Workspace setup complete!"
135
+ echo " Workspace setup complete!"
64
136
  echo ""
65
- echo "Available commands:"
66
- echo " - Run tests: bundle exec rspec"
67
- echo " - Run specific test suites: bundle exec rake run_spec:gem"
68
- echo " - Run JavaScript tests: yarn test"
69
- echo " - Lint JavaScript: yarn lint"
70
- echo " - Lint Ruby: bundle exec rubocop"
137
+ echo "📚 Key commands:"
138
+ echo " bundle exec rspec - Run Ruby tests"
139
+ echo " bundle exec rake run_spec:gem - Run gem-specific tests"
140
+ echo " • yarn test - Run JavaScript tests"
141
+ echo " • yarn lint - Run JavaScript linting"
142
+ echo " • bundle exec rubocop - Run Ruby linting (required before commits)"
143
+ echo ""
144
+ if [[ "$VERSION_MANAGER" == "mise" ]]; then
145
+ echo "💡 Tip: Use 'bin/conductor-exec <command>' if tool versions aren't detected correctly."
146
+ fi
147
+ echo "⚠️ Remember: Always run 'bundle exec rubocop' before committing!"
data/conductor.json CHANGED
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "scripts": {
3
3
  "setup": "./conductor-setup.sh",
4
- "run": "bundle exec rspec",
4
+ "run": "bin/conductor-exec bundle exec rspec",
5
+ "test": "bin/conductor-exec bundle exec rspec",
6
+ "lint": "bin/conductor-exec bundle exec rubocop && bin/conductor-exec yarn lint",
5
7
  "archive": ""
6
8
  }
7
9
  }
data/docs/early_hints.md CHANGED
@@ -4,6 +4,8 @@ This guide shows you how to use HTTP 103 Early Hints with Shakapacker to optimiz
4
4
 
5
5
  > **📚 Related Documentation:** For advanced manual control using `send_pack_early_hints` in controllers before expensive work, see [early_hints_manual_api.md](early_hints_manual_api.md).
6
6
 
7
+ > ⚠️ **CDN Limitation**: Most CDNs (Cloudflare, AWS CloudFront, AWS ALB, etc.) strip HTTP 103 Early Hints before they reach end users. While Shakapacker sends early hints correctly, and your application server (Puma/Thruster) forwards them properly, **CDNs typically strip the 103 response**. End users will only receive the 200 response with Link headers (which arrive too late to provide early hints benefits). This is an industry-wide limitation—even major sites like GitHub, Google, and Shopify don't serve 103 in production through CDNs. For full early hints delivery, you need either direct connections without a CDN, or specific CDN configuration (e.g., Cloudflare's Early Hints feature on paid plans, which works differently by caching Link headers). See [Reverse proxy stripping 103 responses](#reverse-proxy-stripping-103-responses) for configuration details.
8
+
7
9
  ## What are Early Hints?
8
10
 
9
11
  HTTP 103 Early Hints is emitted **after** Rails has finished rendering but **before** the final response is sent, allowing browsers to begin fetching resources (JS, CSS) prior to receiving the full HTML response. This may significantly improve page load performance or cause an equally significant regression, depending on the page's content.
@@ -336,8 +338,12 @@ See the [Manual API Guide](early_hints_manual_api.md) for detailed examples and
336
338
  - **Modern browsers:**
337
339
  - Chrome/Edge/Firefox 103+ ✅
338
340
  - Safari 16.4+ ✅
341
+ - **Infrastructure that preserves 103 responses:**
342
+ - Direct connections (no CDN) ✅
343
+ - Cloudflare with Early Hints enabled (paid plans, works via Link header caching) ✅
344
+ - Most CDNs/load balancers ❌ (strip 103 responses - see [CDN Limitation](#troubleshooting) above)
339
345
 
340
- If requirements not met, feature gracefully degrades with no errors.
346
+ If requirements not met, feature gracefully degrades with no errors. The Link headers will still be present in the 200 response, which may provide some browser prefetching benefits even when 103 is stripped.
341
347
 
342
348
  ## Quick Reference
343
349
 
@@ -377,8 +383,9 @@ Debug mode adds HTML comments to your page showing:
377
383
  - What pack names were processed
378
384
  - What Link headers were sent
379
385
  - HTTP/2 support status
386
+ - CDN warning reminder (since most CDNs strip 103 responses)
380
387
 
381
- View page source and look for `<!-- Shakapacker Early Hints Debug -->` comments.
388
+ View page source and look for `<!-- Shakapacker Early Hints -->` comments.
382
389
 
383
390
  **Early hints not appearing:**
384
391
 
@@ -23,6 +23,52 @@ The precompile hook is especially useful when you need to run commands like:
23
23
  - **Development**: Runs before `bin/shakapacker --watch` or dev server starts
24
24
  - **Production**: Runs before `bundle exec rake assets:precompile`
25
25
 
26
+ ## Choosing an Approach
27
+
28
+ Use `precompile_hook` when your setup should always run preparatory commands right
29
+ before Shakapacker compiles. For React on Rails projects, this is often the
30
+ simplest default.
31
+
32
+ For projects with more custom startup needs (for example, additional build steps
33
+ or strict process ordering in `bin/dev`), you can run those commands explicitly
34
+ before launching long-running processes instead of using `precompile_hook`.
35
+
36
+ ### Comparison
37
+
38
+ | Aspect | `precompile_hook` | Explicit setup in `bin/dev` |
39
+ |--------|-------------------|-------------------------------|
40
+ | Best for | Default/consistent pre-build tasks | Custom multi-step dev boot flows |
41
+ | Runs when | Immediately before compilation starts | Wherever you place it in startup script |
42
+ | Production integration | Automatic via `assets:precompile` | Requires explicit production wiring |
43
+ | Process manager complexity | Lower | Higher (you own orchestration) |
44
+ | Debugging | Centralized hook command | Fully explicit command-by-command flow |
45
+
46
+ ### `shakapacker_precompile` Interaction
47
+
48
+ `shakapacker_precompile` controls whether Shakapacker compilation is included in
49
+ `assets:precompile`, while `precompile_hook` controls whether a preparatory command
50
+ runs before compilation.
51
+
52
+ ```yaml
53
+ # Option A: Default behavior
54
+ shakapacker_precompile: true
55
+ precompile_hook: "bin/shakapacker-precompile-hook"
56
+
57
+ # Option B: You manage compilation elsewhere
58
+ shakapacker_precompile: false
59
+ precompile_hook: "bin/shakapacker-precompile-hook"
60
+
61
+ # Option C: Fully explicit startup flow (no hook)
62
+ shakapacker_precompile: false
63
+ # precompile_hook: not set
64
+ ```
65
+
66
+ To temporarily skip only the hook, set:
67
+
68
+ ```bash
69
+ SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true
70
+ ```
71
+
26
72
  ## Configuration
27
73
 
28
74
  Add the `precompile_hook` option to your `config/shakapacker.yml`:
@@ -141,6 +141,9 @@ if (setup_path = Rails.root.join("bin/setup")).exist?
141
141
  end
142
142
 
143
143
  Dir.chdir(Rails.root) do
144
+ npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
145
+ shakapacker_dependency_value = nil
146
+
144
147
  # In CI, use the pre-packed tarball if available
145
148
  if ENV["SHAKAPACKER_NPM_PACKAGE"]
146
149
  package_path = ENV["SHAKAPACKER_NPM_PACKAGE"]
@@ -167,6 +170,7 @@ Dir.chdir(Rails.root) do
167
170
  say "📦 Installing shakapacker from local package: #{absolute_path}", :cyan
168
171
  begin
169
172
  @package_json.manager.add!([absolute_path], type: :production)
173
+ shakapacker_dependency_value = absolute_path
170
174
  rescue PackageJson::Error
171
175
  say "Shakapacker installation failed 😭 See above for details.", :red
172
176
  exit 1
@@ -174,9 +178,9 @@ Dir.chdir(Rails.root) do
174
178
  else
175
179
  say "⚠️ SHAKAPACKER_NPM_PACKAGE set but file not found: #{absolute_path}", :yellow
176
180
  say "Falling back to npm registry...", :yellow
177
- npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
178
181
  begin
179
182
  @package_json.manager.add!(["shakapacker@#{npm_version}"], type: :production)
183
+ shakapacker_dependency_value = npm_version
180
184
  rescue PackageJson::Error
181
185
  say "Shakapacker installation failed 😭 See above for details.", :red
182
186
  exit 1
@@ -187,10 +191,10 @@ Dir.chdir(Rails.root) do
187
191
  exit 1
188
192
  end
189
193
  else
190
- npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
191
194
  say "Installing shakapacker@#{npm_version}"
192
195
  begin
193
196
  @package_json.manager.add!(["shakapacker@#{npm_version}"], type: :production)
197
+ shakapacker_dependency_value = npm_version
194
198
  rescue PackageJson::Error
195
199
  say "Shakapacker installation failed 😭 See above for details.", :red
196
200
  exit 1
@@ -201,8 +205,8 @@ Dir.chdir(Rails.root) do
201
205
  if pj["dependencies"] && pj["dependencies"]["shakapacker"]
202
206
  {
203
207
  "dependencies" => pj["dependencies"].merge({
204
- # TODO: workaround for test suite - long-run need to actually account for diff pkg manager behaviour
205
- "shakapacker" => pj["dependencies"]["shakapacker"].delete_prefix("^")
208
+ # Keep package.json aligned with the exact source/version this installer requested.
209
+ "shakapacker" => shakapacker_dependency_value || pj["dependencies"]["shakapacker"].delete_prefix("^")
206
210
  })
207
211
  }
208
212
  else
@@ -189,7 +189,7 @@ class Shakapacker::Configuration
189
189
  # @return [Pathname, nil] the absolute private output path or nil
190
190
  def private_output_path
191
191
  private_path = fetch(:private_output_path)
192
- return nil unless private_path
192
+ return nil if private_path.blank?
193
193
  validate_output_paths!
194
194
  root_path.join(private_path)
195
195
  end
@@ -310,10 +310,8 @@ module Shakapacker
310
310
  cmd += @argv
311
311
 
312
312
  Dir.chdir(@app_path) do
313
- system(env, *cmd)
313
+ exec(env, *cmd)
314
314
  end
315
-
316
- exit($?.exitstatus || 1) unless $?.success?
317
315
  end
318
316
 
319
317
  def build_cmd
@@ -657,6 +657,8 @@ module Shakapacker::Helper
657
657
  end
658
658
  store[:debug_buffer] << "<!-- Note: Browsers only process the FIRST 103 response -->"
659
659
  store[:debug_buffer] << "<!-- Note: Puma only supports HTTP/1.1 Early Hints (not HTTP/2) -->"
660
+ store[:debug_buffer] << "<!-- CDN Warning: Most CDNs (Cloudflare, AWS CloudFront, AWS ALB) strip 103 responses. -->"
661
+ store[:debug_buffer] << "<!-- Link headers in the 200 response may still provide some browser hints. -->"
660
662
  end
661
663
  end
662
664
 
@@ -279,7 +279,18 @@ module Shakapacker
279
279
  start_time = Time.now unless watch_mode
280
280
 
281
281
  Dir.chdir(@app_path) do
282
- system(env, *cmd)
282
+ child_pid = nil
283
+ trap("TERM") do
284
+ if child_pid
285
+ Process.kill("TERM", child_pid)
286
+ else
287
+ raise SignalException, "TERM" # if there is no child_pid we never spawned the process and can quit as normal
288
+ end
289
+ rescue Errno::ESRCH
290
+ nil
291
+ end
292
+ child_pid = spawn(env, *cmd)
293
+ Process.wait(child_pid)
283
294
  end
284
295
 
285
296
  if !watch_mode && start_time
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.5.0".freeze
3
+ VERSION = "9.6.0.beta.0".freeze
4
4
  end
data/lib/shakapacker.rb CHANGED
@@ -42,9 +42,12 @@ module Shakapacker
42
42
 
43
43
  # Default environment when RAILS_ENV is not set
44
44
  DEFAULT_ENV = "development".freeze
45
- # Environments that use their RAILS_ENV value for NODE_ENV
45
+ # Environments that use "development" for NODE_ENV
46
46
  # All other environments (production, staging, etc.) use "production" for webpack optimizations
47
- DEV_TEST_ENVS = %w[development test].freeze
47
+ # Note: Both development and test RAILS_ENV use NODE_ENV=development because
48
+ # webpack/rspack only recognize "development" and "production" values for NODE_ENV.
49
+ # Using "test" causes DefinePlugin conflicts with optimization.nodeEnv.
50
+ DEV_ENVS = %w[development test].freeze
48
51
 
49
52
  # Sets the shared Shakapacker instance
50
53
  #
@@ -94,15 +97,19 @@ module Shakapacker
94
97
  # Sets NODE_ENV based on RAILS_ENV if not already set
95
98
  #
96
99
  # Environment mapping:
97
- # - +development+ and +test+ environments use their RAILS_ENV value for NODE_ENV
100
+ # - +development+ and +test+ environments use "development" for NODE_ENV
98
101
  # - All other environments (+production+, +staging+, etc.) use "production" for webpack optimizations
99
102
  #
103
+ # Note: We always use "development" (not "test") for test environments because
104
+ # webpack/rspack only recognize "development" and "production" as valid NODE_ENV values.
105
+ # Using "test" causes DefinePlugin conflicts with optimization.nodeEnv.
106
+ #
100
107
  # This method is typically called automatically during Rails initialization.
101
108
  #
102
109
  # @return [String] the NODE_ENV value that was set
103
110
  # @api private
104
111
  def ensure_node_env!
105
- ENV["NODE_ENV"] ||= DEV_TEST_ENVS.include?(ENV["RAILS_ENV"]) ? ENV["RAILS_ENV"] : "production"
112
+ ENV["NODE_ENV"] ||= DEV_ENVS.include?(ENV["RAILS_ENV"]) ? "development" : "production"
106
113
  end
107
114
 
108
115
  # Temporarily redirects Shakapacker logging to STDOUT
data/package/config.ts CHANGED
@@ -111,6 +111,10 @@ if (existsSync(configPath)) {
111
111
 
112
112
  config.outputPath = resolve(config.public_root_path, config.public_output_path)
113
113
 
114
+ if (config.private_output_path) {
115
+ config.privateOutputPath = resolve(config.private_output_path)
116
+ }
117
+
114
118
  // Ensure that the publicPath includes our asset host so dynamic imports
115
119
  // (code-splitting chunks and static assets) load from the CDN instead of a relative path.
116
120
  const getPublicPath = (): string => {
@@ -8,7 +8,7 @@ const {
8
8
  export = {
9
9
  test: /\.(bmp|gif|jpe?g|png|tiff|ico|avif|webp|eot|otf|ttf|woff|woff2|svg)$/,
10
10
  exclude: /\.(js|mjs|jsx|ts|tsx)$/,
11
- type: "asset/resource",
11
+ type: "asset",
12
12
  generator: {
13
13
  filename: (pathData: { filename?: string }) => {
14
14
  // Guard against null/undefined pathData or filename
@@ -9,6 +9,7 @@ export = canProcess("sass-loader", (resolvedPath: string) => {
9
9
  {
10
10
  loader: resolvedPath,
11
11
  options: {
12
+ api: "modern",
12
13
  sourceMap: true,
13
14
  sassOptions: {
14
15
  [optionKey]: extraPaths,
data/package/types.ts CHANGED
@@ -30,6 +30,7 @@ export interface Config {
30
30
  useContentHash: boolean
31
31
  compile: boolean
32
32
  outputPath: string
33
+ privateOutputPath?: string
33
34
  publicPath: string
34
35
  publicPathWithoutCDN: string
35
36
  manifestPath: string
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.5.0",
3
+ "version": "9.6.0-beta.0",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -55,6 +55,17 @@ describe("Config", () => {
55
55
  )
56
56
  })
57
57
 
58
+ test("should return privateOutputPath as absolute path", () => {
59
+ const config = require("../../package/config")
60
+ expect(config.privateOutputPath).toStrictEqual(resolve("ssr-generated"))
61
+ })
62
+
63
+ test("should not set privateOutputPath when not configured", () => {
64
+ process.env.SHAKAPACKER_CONFIG = "config/shakapacker_manifest_path.yml"
65
+ const config = require("../../package/config")
66
+ expect(config.privateOutputPath).toBeUndefined()
67
+ })
68
+
58
69
  test("should have integrity disabled by default", () => {
59
70
  const config = require("../../package/config")
60
71
  expect(config.integrity.enabled).toBe(false)
@@ -35,6 +35,10 @@ describe("file", () => {
35
35
  types.forEach((type) => expect(file.exclude.test(type)).toBe(true))
36
36
  })
37
37
 
38
+ test("uses webpack asset module type by default", () => {
39
+ expect(file.type).toBe("asset")
40
+ })
41
+
38
42
  test("correct generated output path is returned for top level files", () => {
39
43
  const pathData = {
40
44
  filename: "app/javascript/image.svg"
@@ -19,4 +19,8 @@ describe("sass rule", () => {
19
19
  )
20
20
  expect(typeof sass.use[2].options.sassOptions.loadPaths).toBe("object")
21
21
  })
22
+
23
+ test("uses modern API for better compatibility with sass plugins", () => {
24
+ expect(sass.use[2].options.api).toBe("modern")
25
+ })
22
26
  })
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shakapacker
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.5.0
4
+ version: 9.6.0.beta.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -181,6 +181,7 @@ files:
181
181
  - TODO_v9.md
182
182
  - __mocks__/nonexistent/package.json
183
183
  - __mocks__/sass-loader/package.json
184
+ - bin/conductor-exec
184
185
  - bin/shakapacker-config
185
186
  - conductor-setup.sh
186
187
  - conductor.json
@@ -415,7 +416,7 @@ homepage: https://github.com/shakacode/shakapacker
415
416
  licenses:
416
417
  - MIT
417
418
  metadata:
418
- source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.5.0
419
+ source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.6.0.beta.0
419
420
  rdoc_options: []
420
421
  require_paths:
421
422
  - lib