shakapacker 9.0.0.beta.4 → 9.0.0.beta.5

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dummy.yml +4 -0
  3. data/.github/workflows/generator.yml +7 -0
  4. data/.github/workflows/node.yml +22 -0
  5. data/.github/workflows/ruby.yml +11 -0
  6. data/.github/workflows/test-bundlers.yml +18 -0
  7. data/.gitignore +20 -0
  8. data/.yalcignore +26 -0
  9. data/CHANGELOG.md +58 -40
  10. data/Gemfile.lock +1 -1
  11. data/README.md +3 -1
  12. data/docs/typescript.md +99 -0
  13. data/docs/v9_upgrade.md +14 -1
  14. data/lib/install/template.rb +8 -1
  15. data/lib/shakapacker/configuration.rb +58 -1
  16. data/lib/shakapacker/doctor.rb +752 -0
  17. data/lib/shakapacker/swc_migrator.rb +292 -0
  18. data/lib/shakapacker/version.rb +1 -1
  19. data/lib/shakapacker.rb +1 -0
  20. data/lib/tasks/shakapacker/doctor.rake +8 -0
  21. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  22. data/lib/tasks/shakapacker.rake +1 -0
  23. data/package/config.ts +162 -0
  24. data/package/{dev_server.js → dev_server.ts} +8 -5
  25. data/package/env.ts +67 -0
  26. data/package/environments/base.js +21 -31
  27. data/package/environments/base.ts +137 -0
  28. data/package/index.d.ts +3 -150
  29. data/package/{index.js → index.ts} +17 -8
  30. data/package/loaders.d.ts +27 -0
  31. data/package/types.ts +108 -0
  32. data/package/utils/configPath.ts +6 -0
  33. data/package/utils/{debug.js → debug.ts} +7 -7
  34. data/package/utils/defaultConfigPath.ts +4 -0
  35. data/package/utils/errorHelpers.ts +77 -0
  36. data/package/utils/{getStyleRule.js → getStyleRule.ts} +17 -20
  37. data/package/utils/helpers.ts +85 -0
  38. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  39. data/package/utils/{requireOrError.js → requireOrError.ts} +2 -2
  40. data/package/utils/snakeToCamelCase.ts +5 -0
  41. data/package/utils/typeGuards.ts +228 -0
  42. data/package/utils/{validateDependencies.js → validateDependencies.ts} +4 -4
  43. data/package/webpack-types.d.ts +32 -0
  44. data/package/webpackDevServerConfig.ts +117 -0
  45. data/package.json +6 -2
  46. data/test/typescript/build.test.js +117 -0
  47. data/tsconfig.json +39 -0
  48. data/yarn.lock +1 -1
  49. metadata +31 -17
  50. data/package/config.js +0 -80
  51. data/package/env.js +0 -48
  52. data/package/utils/configPath.js +0 -4
  53. data/package/utils/defaultConfigPath.js +0 -2
  54. data/package/utils/helpers.js +0 -127
  55. data/package/utils/snakeToCamelCase.js +0 -5
  56. data/package/utils/validateCssModulesConfig.js +0 -91
  57. data/package/webpackDevServerConfig.js +0 -73
@@ -0,0 +1,292 @@
1
+ require "yaml"
2
+ require "json"
3
+ require "fileutils"
4
+ require "logger"
5
+ require "pathname"
6
+
7
+ module Shakapacker
8
+ class SwcMigrator
9
+ attr_reader :root_path, :logger
10
+
11
+ BABEL_PACKAGES = [
12
+ "@babel/core",
13
+ "@babel/plugin-proposal-class-properties",
14
+ "@babel/plugin-proposal-object-rest-spread",
15
+ "@babel/plugin-syntax-dynamic-import",
16
+ "@babel/plugin-transform-destructuring",
17
+ "@babel/plugin-transform-regenerator",
18
+ "@babel/plugin-transform-runtime",
19
+ "@babel/preset-env",
20
+ "@babel/preset-react",
21
+ "@babel/preset-typescript",
22
+ "@babel/runtime",
23
+ "babel-loader",
24
+ "babel-plugin-macros",
25
+ "babel-plugin-transform-react-remove-prop-types"
26
+ ].freeze
27
+
28
+ SWC_PACKAGES = {
29
+ "@swc/core" => "^1.7.39",
30
+ "swc-loader" => "^0.2.6"
31
+ }.freeze
32
+
33
+ DEFAULT_SWCRC_CONFIG = {
34
+ "jsc" => {
35
+ "parser" => {
36
+ "syntax" => "ecmascript",
37
+ "jsx" => true,
38
+ "dynamicImport" => true
39
+ },
40
+ "transform" => {
41
+ "react" => {
42
+ "runtime" => "automatic"
43
+ }
44
+ },
45
+ "target" => "es2015"
46
+ },
47
+ "module" => {
48
+ "type" => "es6"
49
+ }
50
+ }.freeze
51
+
52
+ def initialize(root_path, logger: nil)
53
+ @root_path = Pathname.new(root_path)
54
+ @logger = logger || Logger.new($stdout)
55
+ end
56
+
57
+ def migrate_to_swc(run_installer: true)
58
+ logger.info "🔄 Starting migration from Babel to SWC..."
59
+
60
+ results = {
61
+ config_updated: update_shakapacker_config,
62
+ packages_installed: install_swc_packages,
63
+ swcrc_created: create_swcrc,
64
+ babel_packages_found: find_babel_packages
65
+ }
66
+
67
+ logger.info "🎉 Migration to SWC complete!"
68
+ logger.info " Note: SWC is approximately 20x faster than Babel for transpilation."
69
+ logger.info " Please test your application thoroughly after migration."
70
+
71
+ # Show cleanup recommendations if babel packages found
72
+ if results[:babel_packages_found].any?
73
+ logger.info "\n🧹 Cleanup Recommendations:"
74
+ logger.info " Found the following Babel packages in your package.json:"
75
+ results[:babel_packages_found].each do |package|
76
+ logger.info " - #{package}"
77
+ end
78
+ logger.info "\n To remove them, run:"
79
+ logger.info " rails shakapacker:clean_babel_packages"
80
+ end
81
+
82
+ # Suggest running doctor to verify configuration
83
+ logger.info "\n🩺 Run 'rails shakapacker:doctor' to verify your configuration"
84
+
85
+ # Run package manager install if packages were added
86
+ if run_installer && results[:packages_installed].any?
87
+ run_package_manager_install
88
+ end
89
+
90
+ results
91
+ end
92
+
93
+ def clean_babel_packages(run_installer: true)
94
+ logger.info "🧹 Removing Babel packages..."
95
+
96
+ package_json_path = root_path.join("package.json")
97
+ unless package_json_path.exist?
98
+ logger.error "❌ No package.json found"
99
+ return { removed_packages: [], config_files_deleted: [] }
100
+ end
101
+
102
+ removed_packages = remove_babel_from_package_json(package_json_path)
103
+ deleted_files = delete_babel_config_files
104
+
105
+ if removed_packages.any?
106
+ logger.info "✅ Babel packages removed successfully!"
107
+ run_package_manager_install if run_installer
108
+ else
109
+ logger.info "ℹ️ No Babel packages found to remove"
110
+ end
111
+
112
+ { removed_packages: removed_packages, config_files_deleted: deleted_files }
113
+ end
114
+
115
+ def find_babel_packages
116
+ package_json_path = root_path.join("package.json")
117
+ return [] unless package_json_path.exist?
118
+
119
+ begin
120
+ package_json = JSON.parse(File.read(package_json_path))
121
+ dependencies = package_json["dependencies"] || {}
122
+ dev_dependencies = package_json["devDependencies"] || {}
123
+ all_deps = dependencies.merge(dev_dependencies)
124
+
125
+ found_packages = BABEL_PACKAGES.select { |pkg| all_deps.key?(pkg) }
126
+ found_packages
127
+ rescue JSON::ParserError => e
128
+ logger.error "Failed to parse package.json: #{e.message}"
129
+ []
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def update_shakapacker_config
136
+ config_path = root_path.join("config/shakapacker.yml")
137
+ return false unless config_path.exist?
138
+
139
+ logger.info "📝 Updating shakapacker.yml..."
140
+ config = begin
141
+ YAML.load_file(config_path, aliases: true)
142
+ rescue ArgumentError
143
+ YAML.load_file(config_path)
144
+ end
145
+
146
+ config.each do |env, settings|
147
+ next unless settings.is_a?(Hash)
148
+
149
+ if settings["babel"]
150
+ logger.info " - Removing babel config from #{env} environment"
151
+ settings.delete("babel")
152
+ end
153
+
154
+ settings["swc"] = true
155
+ logger.info " - Enabled SWC for #{env} environment"
156
+ end
157
+
158
+ File.write(config_path, config.to_yaml)
159
+ logger.info "✅ shakapacker.yml updated"
160
+ true
161
+ rescue StandardError => e
162
+ logger.error "Failed to update config: #{e.message}"
163
+ false
164
+ end
165
+
166
+ def install_swc_packages
167
+ package_json_path = root_path.join("package.json")
168
+ return {} unless package_json_path.exist?
169
+
170
+ logger.info "📦 Installing SWC dependencies..."
171
+ package_json = JSON.parse(File.read(package_json_path))
172
+
173
+ dependencies = package_json["dependencies"] || {}
174
+ dev_dependencies = package_json["devDependencies"] || {}
175
+ installed = {}
176
+
177
+ SWC_PACKAGES.each do |package, version|
178
+ unless dependencies[package] || dev_dependencies[package]
179
+ logger.info " - Adding #{package}@#{version}"
180
+ dev_dependencies[package] = version
181
+ installed[package] = version
182
+ else
183
+ logger.info " - #{package} already installed"
184
+ end
185
+ end
186
+
187
+ if installed.any?
188
+ package_json["devDependencies"] = dev_dependencies
189
+ File.write(package_json_path, JSON.pretty_generate(package_json) + "\n")
190
+ logger.info "✅ package.json updated with SWC dependencies"
191
+ end
192
+
193
+ installed
194
+ rescue StandardError => e
195
+ logger.error "Failed to install packages: #{e.message}"
196
+ {}
197
+ end
198
+
199
+ def create_swcrc
200
+ swcrc_path = root_path.join(".swcrc")
201
+ if swcrc_path.exist?
202
+ logger.info "ℹ️ .swcrc already exists"
203
+ return false
204
+ end
205
+
206
+ logger.info "📄 Creating .swcrc configuration..."
207
+ File.write(swcrc_path, JSON.pretty_generate(DEFAULT_SWCRC_CONFIG) + "\n")
208
+ logger.info "✅ .swcrc created"
209
+ true
210
+ rescue StandardError => e
211
+ logger.error "Failed to create .swcrc: #{e.message}"
212
+ false
213
+ end
214
+
215
+ def remove_babel_from_package_json(package_json_path)
216
+ package_json = JSON.parse(File.read(package_json_path))
217
+ dependencies = package_json["dependencies"] || {}
218
+ dev_dependencies = package_json["devDependencies"] || {}
219
+ removed_packages = []
220
+
221
+ BABEL_PACKAGES.each do |package|
222
+ if dependencies.delete(package)
223
+ removed_packages << package
224
+ logger.info " - Removed #{package} from dependencies"
225
+ end
226
+ if dev_dependencies.delete(package)
227
+ removed_packages << package
228
+ logger.info " - Removed #{package} from devDependencies"
229
+ end
230
+ end
231
+
232
+ if removed_packages.any?
233
+ package_json["dependencies"] = dependencies
234
+ package_json["devDependencies"] = dev_dependencies
235
+ File.write(package_json_path, JSON.pretty_generate(package_json) + "\n")
236
+ logger.info "✅ package.json updated"
237
+ end
238
+
239
+ removed_packages.uniq
240
+ rescue StandardError => e
241
+ logger.error "Failed to remove packages: #{e.message}"
242
+ []
243
+ end
244
+
245
+ def delete_babel_config_files
246
+ deleted_files = []
247
+ babel_config_files = [".babelrc", "babel.config.js", ".babelrc.js", "babel.config.json"]
248
+
249
+ babel_config_files.each do |file|
250
+ file_path = root_path.join(file)
251
+ if file_path.exist?
252
+ logger.info " - Removing #{file}"
253
+ File.delete(file_path)
254
+ deleted_files << file
255
+ end
256
+ end
257
+
258
+ deleted_files
259
+ rescue StandardError => e
260
+ logger.error "Failed to delete config files: #{e.message}"
261
+ []
262
+ end
263
+
264
+ def run_package_manager_install
265
+ logger.info "\n🔧 Running npm/yarn install..."
266
+
267
+ yarn_lock = root_path.join("yarn.lock")
268
+ pnpm_lock = root_path.join("pnpm-lock.yaml")
269
+
270
+ if yarn_lock.exist?
271
+ system("yarn install")
272
+ elsif pnpm_lock.exist?
273
+ system("pnpm install")
274
+ else
275
+ system("npm install")
276
+ end
277
+ end
278
+
279
+ def package_manager
280
+ yarn_lock = root_path.join("yarn.lock")
281
+ pnpm_lock = root_path.join("pnpm-lock.yaml")
282
+
283
+ if yarn_lock.exist?
284
+ "yarn"
285
+ elsif pnpm_lock.exist?
286
+ "pnpm"
287
+ else
288
+ "npm"
289
+ end
290
+ end
291
+ end
292
+ end
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.0.0.beta.4".freeze
3
+ VERSION = "9.0.0.beta.5".freeze
4
4
  end
data/lib/shakapacker.rb CHANGED
@@ -44,6 +44,7 @@ require_relative "shakapacker/manifest"
44
44
  require_relative "shakapacker/compiler"
45
45
  require_relative "shakapacker/commands"
46
46
  require_relative "shakapacker/dev_server"
47
+ require_relative "shakapacker/doctor"
47
48
  require_relative "shakapacker/deprecation_helper"
48
49
 
49
50
  require_relative "shakapacker/railtie" if defined?(Rails)
@@ -0,0 +1,8 @@
1
+ require "shakapacker/doctor"
2
+
3
+ namespace :shakapacker do
4
+ desc "Checks for common Shakapacker configuration issues and missing dependencies"
5
+ task doctor: :environment do
6
+ Shakapacker::Doctor.new.run
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ require "shakapacker/swc_migrator"
2
+
3
+ namespace :shakapacker do
4
+ desc "Migrate from Babel to SWC transpiler"
5
+ task :migrate_to_swc do
6
+ Shakapacker::SwcMigrator.new(Rails.root).migrate_to_swc
7
+ end
8
+
9
+ desc "Remove Babel packages after migrating to SWC"
10
+ task :clean_babel_packages do
11
+ Shakapacker::SwcMigrator.new(Rails.root).clean_babel_packages
12
+ end
13
+ end
@@ -9,6 +9,7 @@ tasks = {
9
9
  "shakapacker:check_binstubs" => "Verifies that bin/shakapacker is present",
10
10
  "shakapacker:binstubs" => "Installs Shakapacker binstubs in this application",
11
11
  "shakapacker:verify_install" => "Verifies if Shakapacker is installed",
12
+ "shakapacker:doctor" => "Checks for configuration issues and missing dependencies"
12
13
  }.freeze
13
14
 
14
15
  desc "Lists all available tasks in Shakapacker"
data/package/config.ts ADDED
@@ -0,0 +1,162 @@
1
+ import { resolve } from "path"
2
+ import { load } from "js-yaml"
3
+ import { existsSync, readFileSync } from "fs"
4
+ import { merge } from "webpack-merge"
5
+ const { ensureTrailingSlash } = require("./utils/helpers")
6
+ const { railsEnv } = require("./env")
7
+ const configPath = require("./utils/configPath")
8
+ const defaultConfigPath = require("./utils/defaultConfigPath")
9
+ import { Config, YamlConfig, LegacyConfig } from "./types"
10
+ const { isValidYamlConfig, createConfigValidationError, isPartialConfig, isValidConfig } = require("./utils/typeGuards")
11
+ const { isFileNotFoundError, createFileOperationError } = require("./utils/errorHelpers")
12
+
13
+ const loadAndValidateYaml = (path: string): YamlConfig => {
14
+ const fileContent = readFileSync(path, "utf8")
15
+ const yamlContent = load(fileContent)
16
+
17
+ if (!isValidYamlConfig(yamlContent)) {
18
+ throw createConfigValidationError(path, railsEnv, "Invalid YAML structure")
19
+ }
20
+
21
+ return yamlContent as YamlConfig
22
+ }
23
+
24
+ const getDefaultConfig = (): Partial<Config> => {
25
+ try {
26
+ const defaultConfig = loadAndValidateYaml(defaultConfigPath)
27
+ return defaultConfig[railsEnv] || defaultConfig.production || {}
28
+ } catch (error) {
29
+ if (isFileNotFoundError(error)) {
30
+ throw createFileOperationError(
31
+ 'read',
32
+ defaultConfigPath,
33
+ `Default configuration not found at ${defaultConfigPath}. Please ensure Shakapacker is properly installed. You may need to run 'yarn add shakapacker' or 'npm install shakapacker'.`
34
+ )
35
+ }
36
+ throw error
37
+ }
38
+ }
39
+
40
+ const defaults = getDefaultConfig()
41
+ let config: Config
42
+
43
+ if (existsSync(configPath)) {
44
+ try {
45
+ const appYmlObject = loadAndValidateYaml(configPath)
46
+
47
+ const envAppConfig = appYmlObject[railsEnv]
48
+
49
+ if (!envAppConfig) {
50
+ /* eslint no-console:0 */
51
+ console.warn(
52
+ `[SHAKAPACKER WARNING] Environment '${railsEnv}' not found in ${configPath}\n` +
53
+ `Available environments: ${Object.keys(appYmlObject).join(', ')}\n` +
54
+ `Using 'production' configuration as fallback.\n\n` +
55
+ `To fix this, either:\n` +
56
+ ` - Add a '${railsEnv}' section to your shakapacker.yml\n` +
57
+ ` - Set RAILS_ENV to one of the available environments\n` +
58
+ ` - Copy settings from another environment as a starting point`
59
+ )
60
+ }
61
+
62
+ // Merge returns the merged type
63
+ const mergedConfig = merge(defaults, envAppConfig || {})
64
+
65
+ // Validate merged config before type assertion
66
+ if (!isPartialConfig(mergedConfig)) {
67
+ throw createConfigValidationError(
68
+ configPath,
69
+ railsEnv,
70
+ `Invalid configuration structure in ${configPath}. Please check your shakapacker.yml syntax and ensure all required fields are properly defined.`
71
+ )
72
+ }
73
+
74
+ // After merging with defaults, config should be complete
75
+ // Use type assertion only after validation
76
+ config = mergedConfig as Config
77
+ } catch (error) {
78
+ if (isFileNotFoundError(error)) {
79
+ // File not found is OK, use defaults
80
+ if (!isPartialConfig(defaults)) {
81
+ throw createConfigValidationError(
82
+ defaultConfigPath,
83
+ railsEnv,
84
+ `Invalid default configuration. This may indicate a corrupted Shakapacker installation. Try reinstalling with 'yarn add shakapacker --force'.`
85
+ )
86
+ }
87
+ // Using defaults only, might be partial
88
+ config = defaults as Config
89
+ } else {
90
+ throw error
91
+ }
92
+ }
93
+ } else {
94
+ // No user config, use defaults
95
+ if (!isPartialConfig(defaults)) {
96
+ throw createConfigValidationError(
97
+ defaultConfigPath,
98
+ railsEnv,
99
+ `Invalid default configuration. This may indicate a corrupted Shakapacker installation. Try reinstalling with 'yarn add shakapacker --force'.`
100
+ )
101
+ }
102
+ // Using defaults only, might be partial
103
+ config = defaults as Config
104
+ }
105
+
106
+ config.outputPath = resolve(config.public_root_path, config.public_output_path)
107
+
108
+ // Ensure that the publicPath includes our asset host so dynamic imports
109
+ // (code-splitting chunks and static assets) load from the CDN instead of a relative path.
110
+ const getPublicPath = (): string => {
111
+ const rootUrl = ensureTrailingSlash(process.env.SHAKAPACKER_ASSET_HOST || "/")
112
+ return `${rootUrl}${config.public_output_path}/`
113
+ }
114
+
115
+ config.publicPath = getPublicPath()
116
+ config.publicPathWithoutCDN = `/${config.public_output_path}/`
117
+
118
+ if (config.manifest_path) {
119
+ config.manifestPath = resolve(config.manifest_path)
120
+ } else {
121
+ config.manifestPath = resolve(config.outputPath, "manifest.json")
122
+ }
123
+ // Ensure no duplicate hash functions exist in the returned config object
124
+ if (config.integrity?.hash_functions) {
125
+ config.integrity.hash_functions = [...new Set(config.integrity.hash_functions)]
126
+ }
127
+
128
+ // Allow ENV variable to override assets_bundler
129
+ if (process.env.SHAKAPACKER_ASSETS_BUNDLER) {
130
+ config.assets_bundler = process.env.SHAKAPACKER_ASSETS_BUNDLER
131
+ }
132
+
133
+ // Define clear defaults
134
+ const DEFAULT_JAVASCRIPT_TRANSPILER =
135
+ config.assets_bundler === "rspack" ? "swc" : "babel"
136
+
137
+ // Backward compatibility: Add webpack_loader property that maps to javascript_transpiler
138
+ // Show deprecation warning if webpack_loader is used
139
+ // Use type-safe property check
140
+ const configWithLegacy = config as Config & { webpack_loader?: string }
141
+ const webpackLoader = configWithLegacy.webpack_loader
142
+
143
+ if (webpackLoader && !config.javascript_transpiler) {
144
+ console.warn(
145
+ "[SHAKAPACKER DEPRECATION] The 'webpack_loader' configuration option is deprecated.\n" +
146
+ "Please use 'javascript_transpiler' instead as it better reflects its purpose of configuring JavaScript transpilation regardless of the bundler used."
147
+ )
148
+ config.javascript_transpiler = webpackLoader
149
+ } else if (!config.javascript_transpiler) {
150
+ config.javascript_transpiler = DEFAULT_JAVASCRIPT_TRANSPILER
151
+ }
152
+
153
+ // Ensure webpack_loader is always available for backward compatibility
154
+ // Use property assignment instead of type assertion
155
+ Object.defineProperty(config, 'webpack_loader', {
156
+ value: config.javascript_transpiler,
157
+ writable: true,
158
+ enumerable: true,
159
+ configurable: true
160
+ })
161
+
162
+ export = config
@@ -1,23 +1,26 @@
1
1
  // These are the raw shakapacker dev server config settings from the YML file with ENV overrides applied.
2
2
  const { isBoolean } = require("./utils/helpers")
3
3
  const config = require("./config")
4
+ import { DevServerConfig } from "./types"
4
5
 
5
- const envFetch = (key) => {
6
+ const envFetch = (key: string): string | boolean | undefined => {
6
7
  const value = process.env[key]
8
+ if (!value) return undefined
7
9
  return isBoolean(value) ? JSON.parse(value) : value
8
10
  }
9
11
 
10
- const devServerConfig = config.dev_server
12
+ const devServerConfig: DevServerConfig | undefined = config.dev_server
11
13
 
12
14
  if (devServerConfig) {
13
- const envPrefix = config.dev_server.env_prefix || "SHAKAPACKER_DEV_SERVER"
15
+ const envPrefix = devServerConfig.env_prefix || "SHAKAPACKER_DEV_SERVER"
14
16
 
15
17
  Object.keys(devServerConfig).forEach((key) => {
16
18
  const envValue = envFetch(`${envPrefix}_${key.toUpperCase()}`)
17
19
  if (envValue !== undefined) {
18
- devServerConfig[key] = envValue
20
+ // Use bracket notation to avoid ASI issues
21
+ (devServerConfig as Record<string, unknown>)[key] = envValue
19
22
  }
20
23
  })
21
24
  }
22
25
 
23
- module.exports = devServerConfig || {}
26
+ export = devServerConfig || {}
data/package/env.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { load } from "js-yaml"
2
+ import { readFileSync } from "fs"
3
+ const defaultConfigPath = require("./utils/defaultConfigPath")
4
+ const configPath = require("./utils/configPath")
5
+ const { isFileNotFoundError } = require("./utils/errorHelpers")
6
+
7
+ const NODE_ENVIRONMENTS = ["development", "production", "test"] as const
8
+ const DEFAULT = "production"
9
+
10
+ const initialRailsEnv = process.env.RAILS_ENV
11
+ const rawNodeEnv = process.env.NODE_ENV
12
+ const nodeEnv =
13
+ rawNodeEnv && NODE_ENVIRONMENTS.includes(rawNodeEnv as typeof NODE_ENVIRONMENTS[number]) ? rawNodeEnv : DEFAULT
14
+ const isProduction = nodeEnv === "production"
15
+ const isDevelopment = nodeEnv === "development"
16
+
17
+ interface ConfigFile {
18
+ [environment: string]: Record<string, unknown>
19
+ }
20
+
21
+ let config: ConfigFile
22
+ try {
23
+ config = load(readFileSync(configPath, "utf8")) as ConfigFile
24
+ } catch (error: unknown) {
25
+ if (isFileNotFoundError(error)) {
26
+ // File not found, use default configuration
27
+ try {
28
+ config = load(readFileSync(defaultConfigPath, "utf8")) as ConfigFile
29
+ } catch (defaultError) {
30
+ throw new Error(
31
+ `Failed to load Shakapacker configuration.\n` +
32
+ `Neither user config (${configPath}) nor default config (${defaultConfigPath}) could be loaded.\n\n` +
33
+ `To fix this issue:\n` +
34
+ `1. Create a config/shakapacker.yml file in your project\n` +
35
+ `2. Or set the SHAKAPACKER_CONFIG environment variable to point to your config file\n` +
36
+ `3. Or reinstall Shakapacker to restore the default configuration:\n` +
37
+ ` npm install shakapacker --force\n` +
38
+ ` yarn add shakapacker --force`
39
+ )
40
+ }
41
+ } else {
42
+ throw error
43
+ }
44
+ }
45
+
46
+ const availableEnvironments = Object.keys(config).join("|")
47
+ const regex = new RegExp(`^(${availableEnvironments})$`, "g")
48
+
49
+ const runningWebpackDevServer = process.env.WEBPACK_SERVE === "true"
50
+
51
+ const validatedRailsEnv = initialRailsEnv && initialRailsEnv.match(regex) ? initialRailsEnv : DEFAULT
52
+
53
+ if (initialRailsEnv && validatedRailsEnv !== initialRailsEnv) {
54
+ /* eslint no-console:0 */
55
+ console.warn(
56
+ `[SHAKAPACKER WARNING] Environment '${initialRailsEnv}' not found in the configuration.\n` +
57
+ `Using '${DEFAULT}' configuration as a fallback.`
58
+ )
59
+ }
60
+
61
+ export = {
62
+ railsEnv: validatedRailsEnv,
63
+ nodeEnv,
64
+ isProduction,
65
+ isDevelopment,
66
+ runningWebpackDevServer
67
+ }