shakapacker 9.0.0.beta.8 → 9.0.0.beta.10

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.
@@ -30,6 +30,15 @@ module Shakapacker
30
30
  "swc-loader" => "^0.2.6"
31
31
  }.freeze
32
32
 
33
+ ESLINT_CONFIG_FILES = %w[
34
+ .eslintrc
35
+ .eslintrc.js
36
+ .eslintrc.cjs
37
+ .eslintrc.yaml
38
+ .eslintrc.yml
39
+ .eslintrc.json
40
+ ].freeze
41
+
33
42
  DEFAULT_SWCRC_CONFIG = {
34
43
  "jsc" => {
35
44
  "parser" => {
@@ -41,8 +50,7 @@ module Shakapacker
41
50
  "react" => {
42
51
  "runtime" => "automatic"
43
52
  }
44
- },
45
- "target" => "es2015"
53
+ }
46
54
  },
47
55
  "module" => {
48
56
  "type" => "es6"
@@ -67,6 +75,10 @@ module Shakapacker
67
75
  logger.info "šŸŽ‰ Migration to SWC complete!"
68
76
  logger.info " Note: SWC is approximately 20x faster than Babel for transpilation."
69
77
  logger.info " Please test your application thoroughly after migration."
78
+ logger.info "\nšŸ“ Configuration Info:"
79
+ logger.info " - .swcrc provides base configuration for all environments"
80
+ logger.info " - The SWC loader adds automatic environment targeting (via 'env' setting)"
81
+ logger.info " - You can customize .swcrc, but avoid setting 'jsc.target' as it conflicts with 'env'"
70
82
 
71
83
  # Show cleanup recommendations if babel packages found
72
84
  if results[:babel_packages_found].any?
@@ -99,6 +111,16 @@ module Shakapacker
99
111
  return { removed_packages: [], config_files_deleted: [] }
100
112
  end
101
113
 
114
+ # Check if ESLint uses Babel parser
115
+ if eslint_uses_babel?
116
+ logger.info "\nāš ļø WARNING: ESLint configuration detected that may use Babel"
117
+ logger.info " If you use @babel/eslint-parser or babel-eslint, you may need to:"
118
+ logger.info " 1. Keep @babel/core and related Babel packages for ESLint"
119
+ logger.info " 2. Or switch to @typescript-eslint/parser for TypeScript files"
120
+ logger.info " 3. Or use espree (ESLint's default parser) for JavaScript files"
121
+ logger.info "\n Proceeding with Babel package removal. Check your ESLint config after."
122
+ end
123
+
102
124
  removed_packages = remove_babel_from_package_json(package_json_path)
103
125
  deleted_files = delete_babel_config_files
104
126
 
@@ -132,6 +154,42 @@ module Shakapacker
132
154
 
133
155
  private
134
156
 
157
+ def eslint_uses_babel?
158
+ # Check for ESLint config files
159
+ # Note: This is a heuristic check that may have false positives (e.g., in comments),
160
+ # but false positives only result in an extra warning, which is safer than silently
161
+ # breaking ESLint configurations.
162
+ ESLINT_CONFIG_FILES.each do |config_file|
163
+ config_path = root_path.join(config_file)
164
+ next unless config_path.exist?
165
+
166
+ content = File.read(config_path)
167
+ # Check for Babel parser references
168
+ return true if content.match?(/@babel\/eslint-parser|babel-eslint/)
169
+ end
170
+
171
+ # Check package.json for eslintConfig
172
+ package_json_path = root_path.join("package.json")
173
+ if package_json_path.exist?
174
+ begin
175
+ package_json = JSON.parse(File.read(package_json_path))
176
+ if package_json["eslintConfig"]
177
+ return true if package_json["eslintConfig"].to_json.match?(/@babel\/eslint-parser|babel-eslint/)
178
+ end
179
+
180
+ # Check if Babel ESLint packages are installed
181
+ dependencies = package_json["dependencies"] || {}
182
+ dev_dependencies = package_json["devDependencies"] || {}
183
+ all_deps = dependencies.merge(dev_dependencies)
184
+ return true if all_deps.key?("@babel/eslint-parser") || all_deps.key?("babel-eslint")
185
+ rescue JSON::ParserError => e
186
+ logger.debug "Could not parse package.json for ESLint detection: #{e.message}"
187
+ end
188
+ end
189
+
190
+ false
191
+ end
192
+
135
193
  def update_shakapacker_config
136
194
  config_path = root_path.join("config/shakapacker.yml")
137
195
  return false unless config_path.exist?
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.0.0.beta.8".freeze
3
+ VERSION = "9.0.0.beta.10".freeze
4
4
  end
data/lib/shakapacker.rb CHANGED
@@ -6,7 +6,7 @@ require "active_support/tagged_logging"
6
6
  module Shakapacker
7
7
  extend self
8
8
 
9
- DEFAULT_ENV = "production".freeze
9
+ DEFAULT_ENV = "development".freeze
10
10
 
11
11
  def instance=(instance)
12
12
  @instance = instance
data/package/env.ts CHANGED
@@ -6,21 +6,32 @@ const { isFileNotFoundError } = require("./utils/errorHelpers")
6
6
  const { sanitizeEnvValue } = require("./utils/pathValidation")
7
7
 
8
8
  const NODE_ENVIRONMENTS = ["development", "production", "test"] as const
9
- const DEFAULT = "production"
10
9
 
11
10
  // Sanitize environment variables to prevent injection
12
11
  const initialRailsEnv = sanitizeEnvValue(process.env.RAILS_ENV)
13
12
  const rawNodeEnv = sanitizeEnvValue(process.env.NODE_ENV)
14
13
 
14
+ // Default NODE_ENV based on RAILS_ENV to match bin/shakapacker behavior (see lib/shakapacker/runner.rb:27)
15
+ // - RAILS_ENV=production → DEFAULT="production" (safe for production builds)
16
+ // - RAILS_ENV=development, test, staging, or unset → DEFAULT="development" (good DX for dev server)
17
+ // This ensures the dev server works out of the box without requiring NODE_ENV to be set explicitly
18
+ const DEFAULT = initialRailsEnv === "production" ? "production" : "development"
19
+
15
20
  // Validate NODE_ENV strictly
16
21
  const nodeEnv =
17
- rawNodeEnv && NODE_ENVIRONMENTS.includes(rawNodeEnv as typeof NODE_ENVIRONMENTS[number]) ? rawNodeEnv : DEFAULT
22
+ rawNodeEnv &&
23
+ NODE_ENVIRONMENTS.includes(rawNodeEnv as (typeof NODE_ENVIRONMENTS)[number])
24
+ ? rawNodeEnv
25
+ : DEFAULT
18
26
 
19
27
  // Log warning if NODE_ENV was invalid
20
- if (rawNodeEnv && !NODE_ENVIRONMENTS.includes(rawNodeEnv as typeof NODE_ENVIRONMENTS[number])) {
28
+ if (
29
+ rawNodeEnv &&
30
+ !NODE_ENVIRONMENTS.includes(rawNodeEnv as (typeof NODE_ENVIRONMENTS)[number])
31
+ ) {
21
32
  console.warn(
22
33
  `[SHAKAPACKER WARNING] Invalid NODE_ENV value: ${rawNodeEnv}. ` +
23
- `Valid values are: ${NODE_ENVIRONMENTS.join(', ')}. Using default: ${DEFAULT}`
34
+ `Valid values are: ${NODE_ENVIRONMENTS.join(", ")}. Using default: ${DEFAULT}`
24
35
  )
25
36
  }
26
37
 
@@ -42,13 +53,13 @@ try {
42
53
  } catch (defaultError) {
43
54
  throw new Error(
44
55
  `Failed to load Shakapacker configuration.\n` +
45
- `Neither user config (${configPath}) nor default config (${defaultConfigPath}) could be loaded.\n\n` +
46
- `To fix this issue:\n` +
47
- `1. Create a config/shakapacker.yml file in your project\n` +
48
- `2. Or set the SHAKAPACKER_CONFIG environment variable to point to your config file\n` +
49
- `3. Or reinstall Shakapacker to restore the default configuration:\n` +
50
- ` npm install shakapacker --force\n` +
51
- ` yarn add shakapacker --force`
56
+ `Neither user config (${configPath}) nor default config (${defaultConfigPath}) could be loaded.\n\n` +
57
+ `To fix this issue:\n` +
58
+ `1. Create a config/shakapacker.yml file in your project\n` +
59
+ `2. Or set the SHAKAPACKER_CONFIG environment variable to point to your config file\n` +
60
+ `3. Or reinstall Shakapacker to restore the default configuration:\n` +
61
+ ` npm install shakapacker --force\n` +
62
+ ` yarn add shakapacker --force`
52
63
  )
53
64
  }
54
65
  } else {
@@ -61,13 +72,14 @@ const regex = new RegExp(`^(${availableEnvironments})$`, "g")
61
72
 
62
73
  const runningWebpackDevServer = process.env.WEBPACK_SERVE === "true"
63
74
 
64
- const validatedRailsEnv = initialRailsEnv && initialRailsEnv.match(regex) ? initialRailsEnv : DEFAULT
75
+ const validatedRailsEnv =
76
+ initialRailsEnv && initialRailsEnv.match(regex) ? initialRailsEnv : DEFAULT
65
77
 
66
78
  if (initialRailsEnv && validatedRailsEnv !== initialRailsEnv) {
67
79
  /* eslint no-console:0 */
68
80
  console.warn(
69
81
  `[SHAKAPACKER WARNING] Environment '${initialRailsEnv}' not found in the configuration.\n` +
70
- `Using '${DEFAULT}' configuration as a fallback.`
82
+ `Using '${DEFAULT}' configuration as a fallback.`
71
83
  )
72
84
  }
73
85
 
@@ -33,7 +33,12 @@ const getStyleRule = (test: RegExp, preprocessors: any[] = []): StyleRule | null
33
33
  sourceMap: true,
34
34
  importLoaders: 2,
35
35
  modules: {
36
- auto: true
36
+ auto: true,
37
+ // v9 defaults: Use named exports with camelCase conversion
38
+ // Note: css-loader requires 'camelCaseOnly' or 'dashesOnly' when namedExport is true
39
+ // Using 'camelCase' with namedExport: true causes a build error
40
+ namedExport: true,
41
+ exportLocalsConvention: 'camelCaseOnly'
37
42
  }
38
43
  }
39
44
  },
@@ -56,4 +61,4 @@ const getStyleRule = (test: RegExp, preprocessors: any[] = []): StyleRule | null
56
61
  return null
57
62
  }
58
63
 
59
- export = { getStyleRule }
64
+ export = { getStyleRule }
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.0.0-beta.8",
3
+ "version": "9.0.0-beta.10",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -71,6 +71,7 @@ describe("Config", () => {
71
71
  })
72
72
 
73
73
  test("should allow enabling integrity", () => {
74
+ process.env.RAILS_ENV = "production"
74
75
  process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
75
76
  const config = require("../../package/config")
76
77
 
@@ -78,6 +79,7 @@ describe("Config", () => {
78
79
  })
79
80
 
80
81
  test("should allow configuring hash functions", () => {
82
+ process.env.RAILS_ENV = "production"
81
83
  process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
82
84
  const config = require("../../package/config")
83
85
 
@@ -89,6 +91,7 @@ describe("Config", () => {
89
91
  })
90
92
 
91
93
  test("should allow configuring crossorigin", () => {
94
+ process.env.RAILS_ENV = "production"
92
95
  process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
93
96
  const config = require("../../package/config")
94
97
 
@@ -24,6 +24,18 @@ describe("Env", () => {
24
24
  delete process.env.NODE_ENV
25
25
  expect(require("../../package/env")).toStrictEqual({
26
26
  railsEnv: "development",
27
+ nodeEnv: "development",
28
+ isProduction: false,
29
+ isDevelopment: true,
30
+ runningWebpackDevServer: false
31
+ })
32
+ })
33
+
34
+ test("with undefined NODE_ENV and RAILS_ENV set to production", () => {
35
+ process.env.RAILS_ENV = "production"
36
+ delete process.env.NODE_ENV
37
+ expect(require("../../package/env")).toStrictEqual({
38
+ railsEnv: "production",
27
39
  nodeEnv: "production",
28
40
  isProduction: true,
29
41
  isDevelopment: false,
@@ -35,10 +47,10 @@ describe("Env", () => {
35
47
  delete process.env.NODE_ENV
36
48
  delete process.env.RAILS_ENV
37
49
  expect(require("../../package/env")).toStrictEqual({
38
- railsEnv: "production",
39
- nodeEnv: "production",
40
- isProduction: true,
41
- isDevelopment: false,
50
+ railsEnv: "development",
51
+ nodeEnv: "development",
52
+ isProduction: false,
53
+ isDevelopment: true,
42
54
  runningWebpackDevServer: false
43
55
  })
44
56
  })
@@ -48,10 +60,33 @@ describe("Env", () => {
48
60
  process.env.NODE_ENV = "staging"
49
61
  expect(require("../../package/env")).toStrictEqual({
50
62
  railsEnv: "staging",
51
- nodeEnv: "production",
52
- isProduction: true,
53
- isDevelopment: false,
63
+ nodeEnv: "development",
64
+ isProduction: false,
65
+ isDevelopment: true,
66
+ runningWebpackDevServer: false
67
+ })
68
+ })
69
+
70
+ test("rejects malicious NODE_ENV values and uses default", () => {
71
+ process.env.RAILS_ENV = "development"
72
+ process.env.NODE_ENV = "../../../etc/passwd"
73
+ expect(require("../../package/env")).toStrictEqual({
74
+ railsEnv: "development",
75
+ nodeEnv: "development",
76
+ isProduction: false,
77
+ isDevelopment: true,
54
78
  runningWebpackDevServer: false
55
79
  })
56
80
  })
81
+
82
+ test("warns when NODE_ENV is invalid", () => {
83
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation()
84
+ process.env.NODE_ENV = "invalid"
85
+ delete process.env.RAILS_ENV
86
+ require("../../package/env")
87
+ expect(consoleSpy).toHaveBeenCalledWith(
88
+ expect.stringContaining("Invalid NODE_ENV value: invalid")
89
+ )
90
+ consoleSpy.mockRestore()
91
+ })
57
92
  })
@@ -7,6 +7,10 @@ const { chdirTestApp, resetEnv } = require("../../helpers")
7
7
  const rootPath = process.cwd()
8
8
  chdirTestApp()
9
9
 
10
+ // Set NODE_ENV before requiring modules to ensure contenthash is enabled
11
+ // Base config tests expect production-like behavior with contenthash
12
+ process.env.NODE_ENV = "production"
13
+
10
14
  const baseConfig = require("../../../package/environments/base")
11
15
  const config = require("../../../package/config")
12
16
 
@@ -19,7 +19,7 @@ describe("Custom environment", () => {
19
19
  describe("generateWebpackConfig", () => {
20
20
  beforeEach(() => jest.resetModules())
21
21
 
22
- test("should use staging config and default production environment", () => {
22
+ test("should use staging config and default development environment", () => {
23
23
  process.env.RAILS_ENV = "staging"
24
24
  delete process.env.NODE_ENV
25
25
 
@@ -31,9 +31,10 @@ describe("Custom environment", () => {
31
31
  resolve("public", "packs-staging")
32
32
  )
33
33
  expect(webpackConfig.output.publicPath).toBe("/packs-staging/")
34
+ // With the NODE_ENV fix, staging now defaults to development environment
35
+ // instead of production, providing better DX for staging environments
34
36
  expect(webpackConfig).toMatchObject({
35
- devtool: "source-map",
36
- stats: "normal"
37
+ devtool: "cheap-module-source-map"
37
38
  })
38
39
  })
39
40
  })
data/tools/README.md CHANGED
@@ -104,7 +104,7 @@ const Button: React.FC = () => {
104
104
 
105
105
  ### Notes
106
106
 
107
- 1. **Kebab-case conversion**: CSS classes with kebab-case (e.g., `my-button`) are automatically converted to camelCase (`myButton`) for JavaScript files, matching css-loader's `exportLocalsConvention: 'camelCase'` setting.
107
+ 1. **Kebab-case conversion**: CSS classes with kebab-case (e.g., `my-button`) are automatically converted to camelCase (`myButton`) for JavaScript files, matching css-loader's `exportLocalsConvention: 'camelCaseOnly'` setting.
108
108
 
109
109
  2. **Unused imports**: The codemod only imports CSS classes that are actually used in JavaScript files. If you pass the entire styles object to a component, it will convert to namespace import for safety.
110
110
 
@@ -121,4 +121,4 @@ const Button: React.FC = () => {
121
121
  **Solution**: Ensure your TypeScript definitions are updated as shown in the [v9 Upgrade Guide](../docs/v9_upgrade.md).
122
122
 
123
123
  **Issue**: Runtime errors about missing CSS classes
124
- **Solution**: Check if you have kebab-case class names that need camelCase conversion.
124
+ **Solution**: Check if you have kebab-case class names that need camelCase conversion.
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.0.0.beta.8
4
+ version: 9.0.0.beta.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -147,6 +147,7 @@ files:
147
147
  - ".github/workflows/ruby.yml"
148
148
  - ".github/workflows/test-bundlers.yml"
149
149
  - ".gitignore"
150
+ - ".husky/pre-commit"
150
151
  - ".node-version"
151
152
  - ".npmignore"
152
153
  - ".rspec"
@@ -346,7 +347,7 @@ homepage: https://github.com/shakacode/shakapacker
346
347
  licenses:
347
348
  - MIT
348
349
  metadata:
349
- source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.0.0.beta.8
350
+ source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.0.0.beta.10
350
351
  rdoc_options: []
351
352
  require_paths:
352
353
  - lib