shakapacker 9.3.0.beta.1 → 9.3.0.beta.2

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: c503694d35261163ce77ef2efb38c85b5365c7e0c817f06d0c3a9b7e9cd8ce0e
4
- data.tar.gz: 6bea7b663bdb25fc2428bba429ec2f773a4298e91c2fdf2af9b8d0be5f089dd1
3
+ metadata.gz: d196f7ff4fd6270c0a4739f62ed17ee202eef663222018dc0b031b7dfb8ec015
4
+ data.tar.gz: cb645bae3cb677c7b7f7e35164d99b779dfb92d7d6a975204ac0fcae71b56f40
5
5
  SHA512:
6
- metadata.gz: dca52dbb54ea99fd8ad7b3e5cc9de4f0c5446a5c7a2f855032355d5b224fc91e74055784e92733ac6b41548e9676b4ed76c1bf8f4e88d024d6984ddae79f49dd
7
- data.tar.gz: 702f88a69f5116ab0bc2bbfbff2d650cc5d542dd71a4716e4083e1ef79785a0a1a3eeb734924f1753ecafbe633a9bc21d29d86c60911caa6fe6e14acc4531d95
6
+ metadata.gz: ee177b62cc41abd5fbd460d34f8f8dfe387385152cc683aed5034fe3fd67050043dd3cd2fe32f09f06735ff28a4e71ec6f1f4f945e6ea582dbba06e0883dbb1d
7
+ data.tar.gz: dcb85543ec034ede52cf91ed5e36ab5a987e8fb485d54c857fd1762f133252a827d760ac92dd36b9973897e30c3ab1d89f64bacd5761ffe6815b75c990c2c2f9
@@ -0,0 +1,46 @@
1
+ name: ESLint Validation
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - "eslint.config.js"
7
+ - "package.json"
8
+ - "package/**/*.js"
9
+ - "test/**/*.js"
10
+ - ".github/workflows/eslint-validation.yml"
11
+
12
+ jobs:
13
+ validate:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Setup Node
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: "20"
22
+ cache: "yarn"
23
+
24
+ - name: Install dependencies
25
+ run: yarn install --frozen-lockfile
26
+
27
+ - name: Validate ESLint config
28
+ run: |
29
+ echo "Validating ESLint configuration..."
30
+ node -e "const config = require('./eslint.config.js'); console.log('✓ Config is valid with', config.length, 'rule sets')"
31
+
32
+ - name: Run ESLint
33
+ run: |
34
+ echo "Running ESLint on allowed files..."
35
+ yarn eslint . --max-warnings 5
36
+
37
+ - name: Check warning count
38
+ run: |
39
+ echo "Checking warning count..."
40
+ WARNING_COUNT=$(yarn eslint . 2>&1 | grep -E "^✖.*warning" | grep -oE "[0-9]+ warning" | cut -d' ' -f1)
41
+ echo "Current warning count: $WARNING_COUNT"
42
+ if [ "$WARNING_COUNT" -gt "5" ]; then
43
+ echo "❌ Too many warnings: $WARNING_COUNT (max allowed: 5)"
44
+ exit 1
45
+ fi
46
+ echo "✓ Warning count is acceptable"
@@ -0,0 +1,160 @@
1
+ # ESLint Technical Debt Documentation
2
+
3
+ This document tracks the ESLint errors currently suppressed in the codebase and outlines the plan to address them.
4
+
5
+ ## Current Approach
6
+
7
+ **As of 2025-10-14**: All TypeScript files in `package/` directory are temporarily excluded from linting via the ignore pattern `package/**/*.ts` in `eslint.config.js`. This allows the project to adopt ESLint configuration without requiring immediate fixes to all existing issues.
8
+
9
+ **Latest Update**: Fixed all `class-methods-use-this` violations by converting FileWriter methods to static methods (4 violations resolved).
10
+
11
+ ## Current Linting Status
12
+
13
+ **Files currently linted** (`test/**/*.js`, `scripts/*.js`):
14
+
15
+ - ✅ **0 errors** (CI passing)
16
+ - ⚠️ **3 warnings** (acceptable, won't block CI)
17
+ - 1x unused eslint-disable directive in `scripts/remove-use-strict.js`
18
+ - 2x jest/no-disabled-tests in test files (expected for conditional test skipping)
19
+
20
+ **TypeScript files** (currently ignored via `package/**/*.ts`):
21
+
22
+ - **Estimated suppressed errors: ~172** (from sample analysis)
23
+ - TypeScript type-safety issues: ~114 (66%)
24
+ - Style/convention issues: ~58 (34%)
25
+
26
+ **Target**: Reduce suppressed errors by 50% within Q1 2025
27
+ **Last Updated**: 2025-10-14
28
+
29
+ ## Priority Matrix
30
+
31
+ | Category | Impact | Effort | Priority | Count |
32
+ | ------------------------------------ | ------ | ------ | -------- | ----- |
33
+ | `@typescript-eslint/no-explicit-any` | High | High | P1 | 22 |
34
+ | `@typescript-eslint/no-unsafe-*` | High | High | P1 | 85 |
35
+ | `config.ts` type safety | High | Medium | P1 | 7 |
36
+ | `no-param-reassign` | Medium | Low | P2 | 7 |
37
+ | `class-methods-use-this` | Low | Low | P3 | 0 |
38
+ | `no-nested-ternary` | Low | Low | P3 | 3 |
39
+ | `import/prefer-default-export` | Low | Medium | P3 | 9 |
40
+ | `global-require` | Medium | High | P2 | 3 |
41
+ | Other style issues | Low | Low | P3 | 31 |
42
+
43
+ ## Categories of Suppressed Errors
44
+
45
+ ### 1. TypeScript Type Safety (Requires Major Refactoring)
46
+
47
+ #### `@typescript-eslint/no-explicit-any` (22 instances)
48
+
49
+ **Files affected:** `configExporter/`, `config.ts`, `utils/`
50
+ **Why suppressed:** These require careful type definitions and potentially breaking API changes
51
+ **Fix strategy:** Create proper type definitions for configuration objects and YAML parsing
52
+
53
+ #### `@typescript-eslint/no-unsafe-*` (85 instances)
54
+
55
+ - `no-unsafe-assignment`: 47 instances
56
+ - `no-unsafe-member-access`: 20 instances
57
+ - `no-unsafe-call`: 8 instances
58
+ - `no-unsafe-return`: 8 instances
59
+ - `no-unsafe-argument`: 7 instances
60
+ **Why suppressed:** These stem from `any` types and dynamic property access
61
+ **Fix strategy:** Requires comprehensive type refactoring alongside `no-explicit-any` fixes
62
+
63
+ ### 2. Module System (Potential Breaking Changes)
64
+
65
+ #### `global-require` (3 instances)
66
+
67
+ **Files affected:** `configExporter/cli.ts`
68
+ **Why suppressed:** Dynamic require calls are needed for conditional module loading
69
+ **Fix strategy:** Would require converting to ES modules with dynamic imports
70
+
71
+ #### `import/prefer-default-export` (9 instances)
72
+
73
+ **Files affected:** Multiple single-export modules
74
+ **Why suppressed:** Adding default exports alongside named exports could break consumers
75
+ **Fix strategy:** Can be fixed non-breaking by adding default exports that match named exports
76
+
77
+ ### 3. Code Style (Can Be Fixed)
78
+
79
+ #### `class-methods-use-this` (0 instances)
80
+
81
+ ✅ **FIXED** - All FileWriter methods that didn't use instance state have been converted to static methods
82
+
83
+ #### `no-nested-ternary` (3 instances)
84
+
85
+ **Fix strategy:** Refactor to if-else statements
86
+
87
+ #### `no-param-reassign` (7 instances)
88
+
89
+ **Files affected:** `configExporter/cli.ts`
90
+ **Why suppressed:** Common pattern for option objects
91
+ **Fix strategy:** Create new objects instead of mutating parameters
92
+
93
+ #### `no-underscore-dangle` (2 instances)
94
+
95
+ **Fix strategy:** Rename variables or add exceptions for Node internals
96
+
97
+ ### 4. Control Flow
98
+
99
+ #### `no-await-in-loop` (1 instance)
100
+
101
+ **Fix strategy:** Use `Promise.all()` for parallel execution
102
+
103
+ #### `no-continue` (1 instance)
104
+
105
+ **Fix strategy:** Refactor loop logic
106
+
107
+ ## Recommended Approach
108
+
109
+ ### Phase 1: Non-Breaking Fixes
110
+
111
+ ✅ Completed:
112
+
113
+ - Fixed `no-use-before-define` by reordering functions
114
+ - Fixed redundant type constituents with `string & {}` pattern
115
+ - Added proper type annotations for `requireOrError` calls
116
+ - Configured appropriate global rule disables (`no-console`, `no-restricted-syntax`)
117
+ - ✅ **Fixed `class-methods-use-this`** - Converted FileWriter methods to static methods
118
+
119
+ 🔧 Could still fix (low risk):
120
+
121
+ - `no-nested-ternary` - Refactor conditionals
122
+ - `no-useless-escape` - Remove unnecessary escapes
123
+ - Unused variables - Remove or prefix with underscore
124
+
125
+ ### Phase 2: Follow-up PRs (Non-Breaking)
126
+
127
+ - Systematic type safety improvements file by file
128
+ - Add explicit type definitions for configuration objects
129
+ - Replace `any` with `unknown` where possible
130
+
131
+ ### Phase 3: Future Major Version (Breaking Changes)
132
+
133
+ - Convert `export =` to `export default`
134
+ - Convert `require()` to ES6 imports
135
+ - Full TypeScript strict mode compliance
136
+ - Provide codemod for automatic migration
137
+
138
+ ## Configuration Strategy
139
+
140
+ The current approach uses file-specific overrides to suppress errors in affected files while maintaining strict checking elsewhere. This allows:
141
+
142
+ 1. New code to follow strict standards
143
+ 2. Gradual refactoring of existing code
144
+ 3. Clear visibility of technical debt
145
+
146
+ ## Issue Tracking
147
+
148
+ GitHub issues should be created for each category:
149
+
150
+ 1. [ ] Issue: Type safety refactoring for configExporter module
151
+ 2. [ ] Issue: Type safety for dynamic config loading
152
+ 3. [ ] Issue: Convert class methods to static where appropriate
153
+ 4. [ ] Issue: Module system modernization (ES6 modules)
154
+ 5. [ ] Issue: Create codemod for breaking changes migration
155
+
156
+ ## Notes
157
+
158
+ - All suppressed errors are documented in `eslint.config.js` with TODO comments
159
+ - The suppressions are scoped to specific files to prevent spreading technical debt
160
+ - New code should not add to these suppressions
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shakapacker (9.3.0.beta.1)
4
+ shakapacker (9.3.0.beta.2)
5
5
  activesupport (>= 5.2)
6
6
  package_json
7
7
  rack-proxy (>= 0.6.1)
@@ -6,6 +6,7 @@ This document provides step-by-step instructions for the most common upgrade sce
6
6
 
7
7
  ## Table of Contents
8
8
 
9
+ - [Upgrading Shakapacker](#upgrading-shakapacker)
9
10
  - [Migrating Package Managers](#migrating-package-managers)
10
11
  - [Yarn to npm](#yarn-to-npm)
11
12
  - [npm to Yarn](#npm-to-yarn)
@@ -15,6 +16,85 @@ This document provides step-by-step instructions for the most common upgrade sce
15
16
 
16
17
  ---
17
18
 
19
+ ## Upgrading Shakapacker
20
+
21
+ > **⚠️ Important:** Shakapacker is both a Ruby gem AND an npm package. **You must update BOTH** when upgrading.
22
+
23
+ Shakapacker consists of two components that must be updated together:
24
+
25
+ 1. **Ruby gem** - provides Rails integration and view helpers
26
+ 2. **npm package** - provides webpack/rspack configuration and build tools
27
+
28
+ ### Upgrade Steps
29
+
30
+ #### 1. Update `Gemfile`
31
+
32
+ ```ruby
33
+ gem "shakapacker", "9.3.0" # or the version you want to upgrade to
34
+ ```
35
+
36
+ **Pre-release versions:** Ruby gems use dot notation (e.g., `"9.3.0.beta.1"`)
37
+
38
+ #### 2. Update `package.json`
39
+
40
+ ```json
41
+ {
42
+ "dependencies": {
43
+ "shakapacker": "9.3.0"
44
+ }
45
+ }
46
+ ```
47
+
48
+ **Pre-release versions:** npm uses hyphen notation (e.g., `"9.3.0-beta.1"`)
49
+
50
+ #### 3. Run bundler and package manager
51
+
52
+ ```bash
53
+ bundle update shakapacker
54
+ yarn install # or npm install, pnpm install, bun install
55
+ ```
56
+
57
+ #### 4. Test your build
58
+
59
+ ```bash
60
+ bin/shakapacker
61
+ bin/shakapacker-dev-server
62
+ ```
63
+
64
+ ### Why Both Must Be Updated
65
+
66
+ - **Mismatched versions can cause build failures** - The Ruby gem expects specific configuration formats from the npm package
67
+ - **Feature compatibility** - New features in the gem require corresponding npm package updates
68
+ - **Bug fixes** - Fixes often span both Ruby and JavaScript code
69
+
70
+ ### Version Format Differences
71
+
72
+ Note that pre-release versions use different formats:
73
+
74
+ | Component | Stable Version | Pre-release Version |
75
+ | ------------ | -------------- | ------------------- |
76
+ | Gemfile | `"9.3.0"` | `"9.3.0.beta.1"` |
77
+ | package.json | `"9.3.0"` | `"9.3.0-beta.1"` |
78
+
79
+ ### Finding the Latest Version
80
+
81
+ - **Ruby gem:** Check [RubyGems.org](https://rubygems.org/gems/shakapacker)
82
+ - **npm package:** Check [npmjs.com](https://www.npmjs.com/package/shakapacker)
83
+ - **Releases:** See [GitHub Releases](https://github.com/shakacode/shakapacker/releases)
84
+
85
+ ### Major Version Upgrades
86
+
87
+ For major version upgrades, always consult the version-specific upgrade guides for breaking changes and new features:
88
+
89
+ - [V9 Upgrade Guide](./v9_upgrade.md) - Upgrading from v8 to v9 (includes CSS Modules changes, SWC defaults, and more)
90
+ - [V8 Upgrade Guide](./v8_upgrade.md) - Upgrading from v7 to v8
91
+ - [V7 Upgrade Guide](./v7_upgrade.md) - Upgrading from v6 to v7
92
+ - [V6 Upgrade Guide](./v6_upgrade.md) - Upgrading from v5 to v6
93
+
94
+ > **💡 Note:** Major version upgrades may include breaking changes. The steps above cover the basic gem/package updates that apply to all versions, but you should always review the version-specific guide for additional migration steps.
95
+
96
+ ---
97
+
18
98
  ## Migrating Package Managers
19
99
 
20
100
  ### Yarn to npm
data/docs/v9_upgrade.md CHANGED
@@ -4,6 +4,15 @@ This guide outlines new features, breaking changes, and migration steps for upgr
4
4
 
5
5
  **📖 For detailed configuration options, see the [Configuration Guide](./configuration.md)**
6
6
 
7
+ > **⚠️ Important:** Shakapacker is both a Ruby gem AND an npm package. **You must update BOTH**:
8
+ >
9
+ > - Update the version in `Gemfile`
10
+ > - Update the version in `package.json`
11
+ > - Run `bundle update shakapacker`
12
+ > - Run your package manager install command (`yarn install`, `npm install`, or `pnpm install`)
13
+ >
14
+ > See [Migration Steps](#migration-steps) below for detailed instructions including version format differences and testing.
15
+
7
16
  > **⚠️ Important for v9.1.0 Users:** If you're upgrading to v9.1.0 or later, please note the [SWC Configuration Breaking Change](#swc-loose-mode-breaking-change-v910) below. This affects users who previously configured SWC in v9.0.0.
8
17
 
9
18
  ## New Features
@@ -264,15 +273,49 @@ You won't get warnings about missing Babel, Rspack, or esbuild packages.
264
273
 
265
274
  ## Migration Steps
266
275
 
267
- ### Step 1: Update Dependencies
276
+ > **💡 Tip:** For general upgrade instructions applicable to all Shakapacker versions, see [Upgrading Shakapacker](./common-upgrades.md#upgrading-shakapacker) in the Common Upgrades guide.
277
+
278
+ ### Step 1: Update Gemfile
279
+
280
+ Update the shakapacker version in your `Gemfile`:
281
+
282
+ ```ruby
283
+ # Gemfile
284
+ gem "shakapacker", "9.3.0" # or latest version
285
+ ```
286
+
287
+ **Note:** Ruby gems use dot notation for pre-release versions (e.g., `9.3.0.beta.1`), while npm uses hyphen notation (e.g., `9.3.0-beta.1`). See [Version Format Differences](#version-format-differences) below.
288
+
289
+ ### Step 2: Update package.json
290
+
291
+ Update the shakapacker version in your `package.json`:
292
+
293
+ ```json
294
+ {
295
+ "dependencies": {
296
+ "shakapacker": "9.3.0"
297
+ }
298
+ }
299
+ ```
300
+
301
+ **Note:** For pre-release versions, npm uses hyphen notation (e.g., `"shakapacker": "9.3.0-beta.1"`).
302
+
303
+ ### Step 3: Install Updates
304
+
305
+ Run both bundler and your package manager:
268
306
 
269
307
  ```bash
270
- npm update shakapacker@^9.0.0
271
- # or
272
- yarn upgrade shakapacker@^9.0.0
308
+ # Update Ruby gem
309
+ bundle update shakapacker
310
+
311
+ # Update npm package (choose one based on your package manager)
312
+ yarn install # if using Yarn
313
+ npm install # if using npm
314
+ pnpm install # if using pnpm
315
+ bun install # if using Bun
273
316
  ```
274
317
 
275
- ### Step 2: Update CSS Module Imports
318
+ ### Step 4: Update CSS Module Imports
276
319
 
277
320
  #### For each CSS module import:
278
321
 
@@ -299,7 +342,7 @@ declare module "*.module.css" {
299
342
  }
300
343
  ```
301
344
 
302
- ### Step 3: Handle Kebab-Case Class Names
345
+ ### Step 5: Handle Kebab-Case Class Names
303
346
 
304
347
  v9 automatically converts kebab-case to camelCase with `exportLocalsConvention: 'camelCaseOnly'`:
305
348
 
@@ -340,7 +383,7 @@ const buttonClass = styles['my-button'];
340
383
 
341
384
  **Note:** With `'camelCaseOnly'` (default) or `'dashesOnly'`, only one version is exported. If you need both the original and camelCase versions, you would need to use `'camelCase'` instead, but this requires `namedExport: false` (v8 behavior). See the [CSS Modules Export Mode documentation](./css-modules-export-mode.md) for details on reverting to v8 behavior.
342
385
 
343
- ### Step 4: Update Configuration Files
386
+ ### Step 6: Update Configuration Files
344
387
 
345
388
  If you have `webpack_loader` in your configuration:
346
389
 
@@ -353,7 +396,7 @@ If you have `webpack_loader` in your configuration:
353
396
  javascript_transpiler: "babel"
354
397
  ```
355
398
 
356
- ### Step 5: Run Tests
399
+ ### Step 7: Run Tests
357
400
 
358
401
  ```bash
359
402
  # Run your test suite
@@ -366,6 +409,45 @@ bin/shakapacker
366
409
  bin/shakapacker-dev-server
367
410
  ```
368
411
 
412
+ ## Version Format Differences
413
+
414
+ Shakapacker version numbers differ between the Ruby gem and npm package for pre-release versions:
415
+
416
+ | Version Type | Ruby Gem (Gemfile) | npm Package (package.json) |
417
+ | ------------ | ------------------ | -------------------------- |
418
+ | Stable | `"9.3.0"` | `"9.3.0"` |
419
+ | Pre-release | `"9.3.0.beta.1"` | `"9.3.0-beta.1"` |
420
+
421
+ **Examples:**
422
+
423
+ ```ruby
424
+ # Gemfile - uses dots for pre-release versions
425
+ gem "shakapacker", "9.3.0" # stable
426
+ gem "shakapacker", "9.3.0.beta.1" # pre-release
427
+ ```
428
+
429
+ Stable version in package.json:
430
+
431
+ ```json
432
+ {
433
+ "dependencies": {
434
+ "shakapacker": "9.3.0"
435
+ }
436
+ }
437
+ ```
438
+
439
+ Pre-release version in package.json:
440
+
441
+ ```json
442
+ {
443
+ "dependencies": {
444
+ "shakapacker": "9.3.0-beta.1"
445
+ }
446
+ }
447
+ ```
448
+
449
+ This is due to different versioning conventions between RubyGems (which uses dots) and npm (which follows semantic versioning with hyphens for pre-release identifiers).
450
+
369
451
  ## Troubleshooting
370
452
 
371
453
  ### CSS Classes Not Applying
data/eslint.config.js CHANGED
@@ -14,11 +14,16 @@ module.exports = [
14
14
  // Global ignores (replaces .eslintignore)
15
15
  {
16
16
  ignores: [
17
- "lib/**",
18
- "**/node_modules/**",
19
- "vendor/**",
20
- "spec/**",
21
- "package/**" // TODO: Remove after issue #644 is resolved (lints package/ TS source files)
17
+ "lib/**", // Ruby files, not JavaScript
18
+ "**/node_modules/**", // Third-party dependencies
19
+ "vendor/**", // Vendored dependencies
20
+ "spec/**", // Ruby specs, not JavaScript
21
+ "package/**/*.js", // Generated/compiled JavaScript from TypeScript
22
+ "package/**/*.d.ts", // Generated TypeScript declaration files
23
+ // Temporarily ignore TypeScript files until technical debt is resolved
24
+ // See ESLINT_TECHNICAL_DEBT.md for tracking
25
+ // TODO: Remove this once ESLint issues are fixed (tracked in #723)
26
+ "package/**/*.ts"
22
27
  ]
23
28
  },
24
29
 
@@ -52,7 +57,11 @@ module.exports = [
52
57
  "import/no-extraneous-dependencies": "off",
53
58
  // TypeScript handles extensions, not needed for JS imports
54
59
  "import/extensions": "off",
55
- indent: ["error", 2]
60
+ indent: ["error", 2],
61
+ // Allow for...of loops - modern JS syntax, won't pollute client code
62
+ "no-restricted-syntax": "off",
63
+ // Allow console statements - used for debugging/logging throughout
64
+ "no-console": "off"
56
65
  },
57
66
  settings: {
58
67
  react: {
@@ -131,7 +140,114 @@ module.exports = [
131
140
  // Strict: no 'any' types allowed - use 'unknown' or specific types instead
132
141
  "@typescript-eslint/no-explicit-any": "error",
133
142
  // Allow implicit return types - TypeScript can infer them
134
- "@typescript-eslint/explicit-module-boundary-types": "off"
143
+ "@typescript-eslint/explicit-module-boundary-types": "off",
144
+ // Disable no-undef for TypeScript - TypeScript compiler handles this
145
+ // This prevents false positives for ambient types like NodeJS.ProcessEnv
146
+ "no-undef": "off"
147
+ }
148
+ },
149
+
150
+ // Temporary overrides for files with remaining errors
151
+ // See ESLINT_TECHNICAL_DEBT.md for detailed documentation
152
+ //
153
+ // These overrides suppress ~172 errors that require either:
154
+ // 1. Major type refactoring (any/unsafe-* rules)
155
+ // 2. Potential breaking changes (module system)
156
+ // 3. Significant code restructuring
157
+ //
158
+ // GitHub Issues tracking this technical debt:
159
+ // - #707: TypeScript: Refactor configExporter module for type safety
160
+ // - #708: Module System: Modernize to ES6 modules with codemod
161
+ // - #709: Code Style: Fix remaining ESLint style issues
162
+ {
163
+ // Consolidated override for package/config.ts and package/babel/preset.ts
164
+ // Combines rules from both previous override blocks to avoid duplication
165
+ files: ["package/babel/preset.ts", "package/config.ts"],
166
+ rules: {
167
+ // From first override block
168
+ "@typescript-eslint/no-require-imports": "off",
169
+ "@typescript-eslint/no-unused-vars": "off",
170
+ "@typescript-eslint/no-unsafe-call": "off",
171
+ "import/order": "off",
172
+ "import/newline-after-import": "off",
173
+ "import/first": "off",
174
+ // Additional rules that were in the second override for config.ts
175
+ "@typescript-eslint/no-unsafe-assignment": "off",
176
+ "@typescript-eslint/no-unsafe-member-access": "off",
177
+ "@typescript-eslint/no-unsafe-argument": "off",
178
+ "@typescript-eslint/no-explicit-any": "off",
179
+ "no-useless-escape": "off",
180
+ "no-continue": "off",
181
+ "no-nested-ternary": "off"
182
+ }
183
+ },
184
+ {
185
+ files: ["package/configExporter/**/*.ts"],
186
+ rules: {
187
+ "@typescript-eslint/no-explicit-any": "off",
188
+ "@typescript-eslint/no-unsafe-assignment": "off",
189
+ "@typescript-eslint/no-unsafe-member-access": "off",
190
+ "@typescript-eslint/no-unsafe-call": "off",
191
+ "@typescript-eslint/no-unsafe-return": "off",
192
+ "@typescript-eslint/no-unsafe-argument": "off",
193
+ "@typescript-eslint/no-unsafe-function-type": "off",
194
+ "@typescript-eslint/no-unused-vars": "off",
195
+ "@typescript-eslint/require-await": "off",
196
+ "no-param-reassign": "off",
197
+ "no-await-in-loop": "off",
198
+ "no-nested-ternary": "off",
199
+ "import/prefer-default-export": "off",
200
+ "global-require": "off",
201
+ "no-underscore-dangle": "off"
202
+ }
203
+ },
204
+ {
205
+ // Remaining utils files (removed package/config.ts from this block)
206
+ files: [
207
+ "package/utils/inliningCss.ts",
208
+ "package/utils/errorCodes.ts",
209
+ "package/utils/errorHelpers.ts",
210
+ "package/utils/pathValidation.ts"
211
+ ],
212
+ rules: {
213
+ "@typescript-eslint/no-unsafe-assignment": "off",
214
+ "@typescript-eslint/no-unsafe-member-access": "off",
215
+ "@typescript-eslint/no-unsafe-argument": "off",
216
+ "@typescript-eslint/no-explicit-any": "off",
217
+ "no-useless-escape": "off",
218
+ "no-continue": "off",
219
+ "no-nested-ternary": "off"
220
+ }
221
+ },
222
+ {
223
+ files: ["package/plugins/**/*.ts", "package/optimization/**/*.ts"],
224
+ rules: {
225
+ "@typescript-eslint/no-unsafe-assignment": "off",
226
+ "@typescript-eslint/no-unsafe-call": "off",
227
+ "@typescript-eslint/no-redundant-type-constituents": "off",
228
+ "import/prefer-default-export": "off"
229
+ }
230
+ },
231
+ {
232
+ files: [
233
+ "package/environments/**/*.ts",
234
+ "package/index.ts",
235
+ "package/rspack/index.ts",
236
+ "package/rules/**/*.ts",
237
+ "package/swc/index.ts",
238
+ "package/esbuild/index.ts",
239
+ "package/dev_server.ts",
240
+ "package/env.ts"
241
+ ],
242
+ rules: {
243
+ "@typescript-eslint/no-unsafe-assignment": "off",
244
+ "@typescript-eslint/no-unsafe-call": "off",
245
+ "@typescript-eslint/no-unsafe-return": "off",
246
+ "@typescript-eslint/no-redundant-type-constituents": "off",
247
+ "@typescript-eslint/no-unused-vars": "off",
248
+ "@typescript-eslint/no-unsafe-function-type": "off",
249
+ "import/prefer-default-export": "off",
250
+ "no-underscore-dangle": "off"
135
251
  }
136
252
  },
137
253
 
@@ -379,7 +379,7 @@ module Shakapacker
379
379
  return rspack_path
380
380
  end
381
381
 
382
- # Fallback to webpack config with deprecation warning
382
+ # Fallback to webpack config in the configured directory
383
383
  webpack_paths = %w[ts js].map do |ext|
384
384
  File.join(@app_path, config_dir, "webpack.config.#{ext}")
385
385
  end
@@ -393,6 +393,29 @@ module Shakapacker
393
393
  return webpack_path
394
394
  end
395
395
 
396
+ # Backward compatibility: Check config/webpack/ if we were looking in config/rspack/
397
+ # This supports upgrades from versions where rspack used config/webpack/
398
+ if config_dir == "config/rspack"
399
+ webpack_dir_paths = %w[ts js].map do |ext|
400
+ File.join(@app_path, "config/webpack", "webpack.config.#{ext}")
401
+ end
402
+
403
+ puts "[Shakapacker] Checking config/webpack/ for backward compatibility..."
404
+ webpack_dir_path = webpack_dir_paths.find { |f| File.exist?(f) }
405
+ if webpack_dir_path
406
+ $stderr.puts "⚠️ DEPRECATION WARNING: Found webpack config in config/webpack/ but assets_bundler is set to 'rspack'."
407
+ $stderr.puts " For rspack, configs should be in config/rspack/ directory."
408
+ $stderr.puts " "
409
+ $stderr.puts " To fix this, either:"
410
+ $stderr.puts " 1. Move your config: mv config/webpack config/rspack"
411
+ $stderr.puts " 2. Set assets_bundler_config_path in config/shakapacker.yml:"
412
+ $stderr.puts " assets_bundler_config_path: config/webpack"
413
+ $stderr.puts " "
414
+ $stderr.puts " Using: #{webpack_dir_path}"
415
+ return webpack_dir_path
416
+ end
417
+ end
418
+
396
419
  # No config found
397
420
  print_config_not_found_error("rspack", rspack_paths.last, config_dir)
398
421
  exit(1)
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.3.0.beta.1".freeze
3
+ VERSION = "9.3.0.beta.2".freeze
4
4
  end
@@ -592,7 +592,6 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
592
592
  `\n📦 Exporting ${buildNames.length} builds from config file...\n`
593
593
  )
594
594
 
595
- const fileWriter = new FileWriter()
596
595
  const targetDir = options.saveDir! // Set by applyDefaults
597
596
  const createdFiles: string[] = []
598
597
 
@@ -610,7 +609,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
610
609
 
611
610
  for (const { config: cfg, metadata } of configs) {
612
611
  const output = formatConfig(cfg, metadata, options, appRoot)
613
- const filename = fileWriter.generateFilename(
612
+ const filename = FileWriter.generateFilename(
614
613
  metadata.bundler,
615
614
  metadata.environment,
616
615
  metadata.configType,
@@ -619,7 +618,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
619
618
  )
620
619
 
621
620
  const fullPath = resolve(targetDir, filename)
622
- fileWriter.writeSingleFile(fullPath, output)
621
+ FileWriter.writeSingleFile(fullPath, output)
623
622
  createdFiles.push(fullPath)
624
623
  }
625
624
  }
@@ -659,7 +658,6 @@ async function runDoctorMode(
659
658
  console.log("🔍 Config Exporter - Doctor Mode")
660
659
  console.log("=".repeat(80))
661
660
 
662
- const fileWriter = new FileWriter()
663
661
  const targetDir = options.saveDir! // Set by applyDefaults
664
662
 
665
663
  const createdFiles: string[] = []
@@ -693,7 +691,7 @@ async function runDoctorMode(
693
691
 
694
692
  for (const { config, metadata } of configs) {
695
693
  const output = formatConfig(config, metadata, options, appRoot)
696
- const filename = fileWriter.generateFilename(
694
+ const filename = FileWriter.generateFilename(
697
695
  metadata.bundler,
698
696
  metadata.environment,
699
697
  metadata.configType,
@@ -701,7 +699,7 @@ async function runDoctorMode(
701
699
  metadata.buildName
702
700
  )
703
701
  const fullPath = resolve(targetDir, filename)
704
- fileWriter.writeSingleFile(fullPath, output)
702
+ FileWriter.writeSingleFile(fullPath, output)
705
703
  createdFiles.push(fullPath)
706
704
  }
707
705
  }
@@ -761,7 +759,7 @@ async function runDoctorMode(
761
759
  * - Filename uses "client" type and "development-hmr" build name to
762
760
  * distinguish it from regular development client bundle
763
761
  */
764
- filename = fileWriter.generateFilename(
762
+ filename = FileWriter.generateFilename(
765
763
  metadata.bundler,
766
764
  metadata.environment,
767
765
  "client",
@@ -769,7 +767,7 @@ async function runDoctorMode(
769
767
  "development-hmr"
770
768
  )
771
769
  } else {
772
- filename = fileWriter.generateFilename(
770
+ filename = FileWriter.generateFilename(
773
771
  metadata.bundler,
774
772
  metadata.environment,
775
773
  metadata.configType,
@@ -780,7 +778,7 @@ async function runDoctorMode(
780
778
 
781
779
  const fullPath = resolve(targetDir, filename)
782
780
  const fileOutput: FileOutput = { filename, content: output, metadata }
783
- fileWriter.writeSingleFile(fullPath, output)
781
+ FileWriter.writeSingleFile(fullPath, output)
784
782
  createdFiles.push(fullPath)
785
783
  }
786
784
  }
@@ -834,7 +832,6 @@ async function runSaveMode(
834
832
  const env = options.env || "development"
835
833
  console.log(`[Config Exporter] Exporting ${env} configs`)
836
834
 
837
- const fileWriter = new FileWriter()
838
835
  const targetDir = options.saveDir! // Set by applyDefaults
839
836
  const configs = await loadConfigsForEnv(options.env, options, appRoot)
840
837
  const createdFiles: string[] = []
@@ -852,13 +849,13 @@ async function runSaveMode(
852
849
  appRoot
853
850
  )
854
851
  const fullPath = resolve(options.output)
855
- fileWriter.writeSingleFile(fullPath, output)
852
+ FileWriter.writeSingleFile(fullPath, output)
856
853
  createdFiles.push(fullPath)
857
854
  } else {
858
855
  // Multi-file output (one per config)
859
856
  for (const { config, metadata } of configs) {
860
857
  const output = formatConfig(config, metadata, options, appRoot)
861
- const filename = fileWriter.generateFilename(
858
+ const filename = FileWriter.generateFilename(
862
859
  metadata.bundler,
863
860
  metadata.environment,
864
861
  metadata.configType,
@@ -866,7 +863,7 @@ async function runSaveMode(
866
863
  metadata.buildName
867
864
  )
868
865
  const fullPath = resolve(targetDir, filename)
869
- fileWriter.writeSingleFile(fullPath, output)
866
+ FileWriter.writeSingleFile(fullPath, output)
870
867
  createdFiles.push(fullPath)
871
868
  }
872
869
  }
@@ -906,9 +903,8 @@ async function runSingleFileMode(
906
903
  const config = combined.length === 1 ? combined[0] : combined
907
904
  const output = formatConfig(config, metadata, options, appRoot)
908
905
 
909
- const fileWriter = new FileWriter()
910
906
  const filePath = resolve(process.cwd(), options.output!)
911
- fileWriter.writeSingleFile(filePath, output)
907
+ FileWriter.writeSingleFile(filePath, output)
912
908
  }
913
909
 
914
910
  async function loadConfigsForEnv(
@@ -11,16 +11,16 @@ export class FileWriter {
11
11
  /**
12
12
  * Write multiple config files (one per config in array)
13
13
  */
14
- writeMultipleFiles(outputs: FileOutput[], targetDir: string): void {
14
+ static writeMultipleFiles(outputs: FileOutput[], targetDir: string): void {
15
15
  // Ensure directory exists
16
- this.ensureDirectory(targetDir)
16
+ FileWriter.ensureDirectory(targetDir)
17
17
 
18
18
  // Write each file
19
19
  outputs.forEach((output) => {
20
20
  const safeName = basename(output.filename)
21
21
  const filePath = resolve(targetDir, safeName)
22
- this.validateOutputPath(filePath)
23
- this.writeFile(filePath, output.content)
22
+ FileWriter.validateOutputPath(filePath)
23
+ FileWriter.writeFile(filePath, output.content)
24
24
  console.log(`[Config Exporter] Created: ${filePath}`)
25
25
  })
26
26
 
@@ -32,13 +32,13 @@ export class FileWriter {
32
32
  /**
33
33
  * Write a single file
34
34
  */
35
- writeSingleFile(filePath: string, content: string): void {
35
+ static writeSingleFile(filePath: string, content: string): void {
36
36
  // Ensure parent directory exists
37
37
  const dir = dirname(filePath)
38
- this.ensureDirectory(dir)
38
+ FileWriter.ensureDirectory(dir)
39
39
 
40
- this.validateOutputPath(filePath)
41
- this.writeFile(filePath, content)
40
+ FileWriter.validateOutputPath(filePath)
41
+ FileWriter.writeFile(filePath, content)
42
42
  console.log(`[Config Exporter] Created: ${filePath}`)
43
43
  }
44
44
 
@@ -54,7 +54,7 @@ export class FileWriter {
54
54
  * webpack-dev-client.yaml (with build name)
55
55
  * rspack-cypress-dev-server.yaml (with build name)
56
56
  */
57
- generateFilename(
57
+ static generateFilename(
58
58
  bundler: string,
59
59
  env: string,
60
60
  configType: "client" | "server" | "all" | "client-hmr",
@@ -66,11 +66,11 @@ export class FileWriter {
66
66
  return `${bundler}-${name}-${configType}.${ext}`
67
67
  }
68
68
 
69
- private writeFile(filePath: string, content: string): void {
69
+ private static writeFile(filePath: string, content: string): void {
70
70
  writeFileSync(filePath, content, "utf8")
71
71
  }
72
72
 
73
- private ensureDirectory(dir: string): void {
73
+ private static ensureDirectory(dir: string): void {
74
74
  if (!existsSync(dir)) {
75
75
  mkdirSync(dir, { recursive: true })
76
76
  }
@@ -79,7 +79,7 @@ export class FileWriter {
79
79
  /**
80
80
  * Validate output path and warn if writing outside cwd
81
81
  */
82
- validateOutputPath(outputPath: string): void {
82
+ private static validateOutputPath(outputPath: string): void {
83
83
  const absPath = resolve(outputPath)
84
84
  const cwd = process.cwd()
85
85
 
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.3.0-beta.1",
3
+ "version": "9.3.0-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "shakapacker",
9
- "version": "9.3.0-beta.1",
9
+ "version": "9.3.0-beta.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "js-yaml": "^4.1.0",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.3.0-beta.1",
3
+ "version": "9.3.0-beta.2",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -154,41 +154,44 @@ describe("BuildValidator", () => {
154
154
  writeFileSync(configPath, "module.exports = {}")
155
155
 
156
156
  const originalPath = process.env.PATH
157
- process.env.PATH = "/test/path"
158
157
 
159
- const mockChild = {
160
- stdout: { on: jest.fn(), removeAllListeners: jest.fn() },
161
- stderr: { on: jest.fn(), removeAllListeners: jest.fn() },
162
- on: jest.fn(),
163
- once: jest.fn(),
164
- kill: jest.fn(),
165
- removeAllListeners: jest.fn()
166
- }
158
+ try {
159
+ process.env.PATH = "/test/path"
160
+
161
+ const mockChild = {
162
+ stdout: { on: jest.fn(), removeAllListeners: jest.fn() },
163
+ stderr: { on: jest.fn(), removeAllListeners: jest.fn() },
164
+ on: jest.fn(),
165
+ once: jest.fn(),
166
+ kill: jest.fn(),
167
+ removeAllListeners: jest.fn()
168
+ }
167
169
 
168
- mockSpawn.mockReturnValue(mockChild)
170
+ mockSpawn.mockReturnValue(mockChild)
169
171
 
170
- const build = {
171
- name: "test",
172
- bundler: "webpack",
173
- environment: { NODE_ENV: "production" },
174
- configFile: configPath,
175
- outputs: ["client"]
176
- }
177
-
178
- const validationPromise = validator.validateBuild(build, testDir)
172
+ const build = {
173
+ name: "test",
174
+ bundler: "webpack",
175
+ environment: { NODE_ENV: "production" },
176
+ configFile: configPath,
177
+ outputs: ["client"]
178
+ }
179
179
 
180
- const exitHandler = mockChild.on.mock.calls.find(
181
- ([event]) => event === "exit"
182
- )[1]
183
- exitHandler(0)
180
+ const validationPromise = validator.validateBuild(build, testDir)
184
181
 
185
- await validationPromise
182
+ const exitHandler = mockChild.on.mock.calls.find(
183
+ ([event]) => event === "exit"
184
+ )[1]
185
+ exitHandler(0)
186
186
 
187
- const spawnCall = mockSpawn.mock.calls[0]
188
- const { env } = spawnCall[2]
189
- expect(env.PATH).toBe("/test/path")
187
+ await validationPromise
190
188
 
191
- process.env.PATH = originalPath
189
+ const spawnCall = mockSpawn.mock.calls[0]
190
+ const { env } = spawnCall[2]
191
+ expect(env.PATH).toBe("/test/path")
192
+ } finally {
193
+ process.env.PATH = originalPath
194
+ }
192
195
  })
193
196
  })
194
197
 
@@ -17,8 +17,7 @@ describe("configExporter", () => {
17
17
  describe("fileWriter", () => {
18
18
  test("generates correct filename for client config", () => {
19
19
  const { FileWriter } = require("../../package/configExporter/fileWriter")
20
- const writer = new FileWriter()
21
- const filename = writer.generateFilename(
20
+ const filename = FileWriter.generateFilename(
22
21
  "webpack",
23
22
  "development",
24
23
  "client",
@@ -29,8 +28,7 @@ describe("configExporter", () => {
29
28
 
30
29
  test("generates correct filename for server config", () => {
31
30
  const { FileWriter } = require("../../package/configExporter/fileWriter")
32
- const writer = new FileWriter()
33
- const filename = writer.generateFilename(
31
+ const filename = FileWriter.generateFilename(
34
32
  "webpack",
35
33
  "production",
36
34
  "server",
@@ -41,8 +39,7 @@ describe("configExporter", () => {
41
39
 
42
40
  test("generates correct filename for client-hmr config", () => {
43
41
  const { FileWriter } = require("../../package/configExporter/fileWriter")
44
- const writer = new FileWriter()
45
- const filename = writer.generateFilename(
42
+ const filename = FileWriter.generateFilename(
46
43
  "webpack",
47
44
  "development",
48
45
  "client-hmr",
@@ -53,8 +50,7 @@ describe("configExporter", () => {
53
50
 
54
51
  test("generates correct filename for json format", () => {
55
52
  const { FileWriter } = require("../../package/configExporter/fileWriter")
56
- const writer = new FileWriter()
57
- const filename = writer.generateFilename(
53
+ const filename = FileWriter.generateFilename(
58
54
  "rspack",
59
55
  "production",
60
56
  "client",
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.3.0.beta.1
4
+ version: 9.3.0.beta.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -141,6 +141,7 @@ files:
141
141
  - ".github/workflows/claude-code-review.yml"
142
142
  - ".github/workflows/claude.yml"
143
143
  - ".github/workflows/dummy.yml"
144
+ - ".github/workflows/eslint-validation.yml"
144
145
  - ".github/workflows/generator.yml"
145
146
  - ".github/workflows/node.yml"
146
147
  - ".github/workflows/ruby.yml"
@@ -156,6 +157,7 @@ files:
156
157
  - CHANGELOG.md
157
158
  - CLAUDE.md
158
159
  - CONTRIBUTING.md
160
+ - ESLINT_TECHNICAL_DEBT.md
159
161
  - Gemfile
160
162
  - Gemfile.development_dependencies
161
163
  - Gemfile.lock
@@ -378,7 +380,7 @@ homepage: https://github.com/shakacode/shakapacker
378
380
  licenses:
379
381
  - MIT
380
382
  metadata:
381
- source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.3.0.beta.1
383
+ source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.3.0.beta.2
382
384
  rdoc_options: []
383
385
  require_paths:
384
386
  - lib