shakapacker 9.0.0.beta.6 → 9.0.0.beta.7

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.
@@ -125,13 +125,60 @@ if (setup_path = Rails.root.join("bin/setup")).exist?
125
125
  end
126
126
 
127
127
  Dir.chdir(Rails.root) do
128
- npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
129
- say "Installing shakapacker@#{npm_version}"
130
- begin
131
- @package_json.manager.add!(["shakapacker@#{npm_version}"], type: :production)
132
- rescue PackageJson::Error
133
- say "Shakapacker installation failed 😭 See above for details.", :red
134
- exit 1
128
+ # In CI, use the pre-packed tarball if available
129
+ if ENV["SHAKAPACKER_NPM_PACKAGE"]
130
+ package_path = ENV["SHAKAPACKER_NPM_PACKAGE"]
131
+
132
+ # Validate package path to prevent directory traversal and invalid file types
133
+ begin
134
+ # Resolve to absolute path
135
+ absolute_path = File.expand_path(package_path)
136
+
137
+ # Reject paths containing directory traversal
138
+ if package_path.include?("..") || absolute_path.include?("..")
139
+ say "❌ Security Error: Package path contains directory traversal: #{package_path}", :red
140
+ exit 1
141
+ end
142
+
143
+ # Ensure filename ends with .tgz or .tar.gz
144
+ unless absolute_path.end_with?(".tgz", ".tar.gz")
145
+ say "❌ Security Error: Package must be a .tgz or .tar.gz file: #{package_path}", :red
146
+ exit 1
147
+ end
148
+
149
+ # Check existence only after validation
150
+ if File.exist?(absolute_path)
151
+ say "📦 Installing shakapacker from local package: #{absolute_path}", :cyan
152
+ begin
153
+ @package_json.manager.add!([absolute_path], type: :production)
154
+ rescue PackageJson::Error
155
+ say "Shakapacker installation failed 😭 See above for details.", :red
156
+ exit 1
157
+ end
158
+ else
159
+ say "⚠️ SHAKAPACKER_NPM_PACKAGE set but file not found: #{absolute_path}", :yellow
160
+ say "Falling back to npm registry...", :yellow
161
+ npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
162
+ begin
163
+ @package_json.manager.add!(["shakapacker@#{npm_version}"], type: :production)
164
+ rescue PackageJson::Error
165
+ say "Shakapacker installation failed 😭 See above for details.", :red
166
+ exit 1
167
+ end
168
+ end
169
+ rescue => e
170
+ say "❌ Error validating package path: #{e.message}", :red
171
+ exit 1
172
+ end
173
+ else
174
+ npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
175
+ say "Installing shakapacker@#{npm_version}"
176
+ begin
177
+ @package_json.manager.add!(["shakapacker@#{npm_version}"], type: :production)
178
+ rescue PackageJson::Error
179
+ say "Shakapacker installation failed 😭 See above for details.", :red
180
+ exit 1
181
+ end
135
182
  end
136
183
 
137
184
  @package_json.merge! do |pj|
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.0.0.beta.6".freeze
3
+ VERSION = "9.0.0.beta.7".freeze
4
4
  end
@@ -0,0 +1,4 @@
1
+ # Exclude TypeScript source files from the package directory
2
+ *.ts
3
+ # But keep the TypeScript declaration files
4
+ !*.d.ts
data/package/config.ts CHANGED
@@ -125,33 +125,46 @@ if (config.integrity?.hash_functions) {
125
125
  config.integrity.hash_functions = [...new Set(config.integrity.hash_functions)]
126
126
  }
127
127
 
128
- // Allow ENV variable to override assets_bundler
128
+ // Ensure assets_bundler has a default value
129
+ if (!config.assets_bundler) {
130
+ config.assets_bundler = "webpack"
131
+ }
132
+
133
+ // Allow ENV variable to override assets_bundler
129
134
  if (process.env.SHAKAPACKER_ASSETS_BUNDLER) {
130
135
  config.assets_bundler = process.env.SHAKAPACKER_ASSETS_BUNDLER
131
136
  }
132
137
 
133
138
  // Define clear defaults
134
- const DEFAULT_JAVASCRIPT_TRANSPILER =
139
+ // Keep Babel as default for webpack to maintain backward compatibility
140
+ // Use SWC for rspack as it's a newer bundler where we can set modern defaults
141
+ const DEFAULT_JAVASCRIPT_TRANSPILER =
135
142
  config.assets_bundler === "rspack" ? "swc" : "babel"
136
143
 
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
144
+ // Backward compatibility: Check for webpack_loader using proper type guard
145
+ function hasWebpackLoader(obj: unknown): obj is Config & { webpack_loader: string } {
146
+ return (
147
+ typeof obj === 'object' &&
148
+ obj !== null &&
149
+ 'webpack_loader' in obj &&
150
+ typeof (obj as Record<string, unknown>).webpack_loader === 'string'
151
+ )
152
+ }
142
153
 
143
- if (webpackLoader && !config.javascript_transpiler) {
154
+ // Allow environment variable to override javascript_transpiler
155
+ if (process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER) {
156
+ config.javascript_transpiler = process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER
157
+ } else if (hasWebpackLoader(config) && !config.javascript_transpiler) {
144
158
  console.warn(
145
159
  "[SHAKAPACKER DEPRECATION] The 'webpack_loader' configuration option is deprecated.\n" +
146
160
  "Please use 'javascript_transpiler' instead as it better reflects its purpose of configuring JavaScript transpilation regardless of the bundler used."
147
161
  )
148
- config.javascript_transpiler = webpackLoader
162
+ config.javascript_transpiler = config.webpack_loader
149
163
  } else if (!config.javascript_transpiler) {
150
164
  config.javascript_transpiler = DEFAULT_JAVASCRIPT_TRANSPILER
151
165
  }
152
166
 
153
167
  // Ensure webpack_loader is always available for backward compatibility
154
- // Use property assignment instead of type assertion
155
168
  Object.defineProperty(config, 'webpack_loader', {
156
169
  value: config.javascript_transpiler,
157
170
  writable: true,
data/package/env.ts CHANGED
@@ -3,14 +3,27 @@ import { readFileSync } from "fs"
3
3
  const defaultConfigPath = require("./utils/defaultConfigPath")
4
4
  const configPath = require("./utils/configPath")
5
5
  const { isFileNotFoundError } = require("./utils/errorHelpers")
6
+ const { sanitizeEnvValue } = require("./utils/pathValidation")
6
7
 
7
8
  const NODE_ENVIRONMENTS = ["development", "production", "test"] as const
8
9
  const DEFAULT = "production"
9
10
 
10
- const initialRailsEnv = process.env.RAILS_ENV
11
- const rawNodeEnv = process.env.NODE_ENV
11
+ // Sanitize environment variables to prevent injection
12
+ const initialRailsEnv = sanitizeEnvValue(process.env.RAILS_ENV)
13
+ const rawNodeEnv = sanitizeEnvValue(process.env.NODE_ENV)
14
+
15
+ // Validate NODE_ENV strictly
12
16
  const nodeEnv =
13
17
  rawNodeEnv && NODE_ENVIRONMENTS.includes(rawNodeEnv as typeof NODE_ENVIRONMENTS[number]) ? rawNodeEnv : DEFAULT
18
+
19
+ // Log warning if NODE_ENV was invalid
20
+ if (rawNodeEnv && !NODE_ENVIRONMENTS.includes(rawNodeEnv as typeof NODE_ENVIRONMENTS[number])) {
21
+ console.warn(
22
+ `[SHAKAPACKER WARNING] Invalid NODE_ENV value: ${rawNodeEnv}. ` +
23
+ `Valid values are: ${NODE_ENVIRONMENTS.join(', ')}. Using default: ${DEFAULT}`
24
+ )
25
+ }
26
+
14
27
  const isProduction = nodeEnv === "production"
15
28
  const isDevelopment = nodeEnv === "development"
16
29
 
@@ -1,17 +1,35 @@
1
+ /**
2
+ * Development environment configuration for webpack and rspack bundlers
3
+ * @module environments/development
4
+ */
5
+
1
6
  const { merge } = require("webpack-merge")
2
7
  const config = require("../config")
3
8
  const baseConfig = require("./base")
4
9
  const webpackDevServerConfig = require("../webpackDevServerConfig")
5
10
  const { runningWebpackDevServer } = require("../env")
6
11
  const { moduleExists } = require("../utils/helpers")
12
+ import type {
13
+ WebpackConfigWithDevServer,
14
+ RspackConfigWithDevServer,
15
+ ReactRefreshWebpackPlugin,
16
+ ReactRefreshRspackPlugin
17
+ } from "./types"
7
18
 
19
+ /**
20
+ * Base development configuration shared between webpack and rspack
21
+ */
8
22
  const baseDevConfig = {
9
- mode: "development",
10
- devtool: "cheap-module-source-map"
23
+ mode: "development" as const,
24
+ devtool: "cheap-module-source-map" as const
11
25
  }
12
26
 
13
- const webpackDevConfig = () => {
14
- const webpackConfig = {
27
+ /**
28
+ * Generate webpack-specific development configuration
29
+ * @returns Webpack configuration with dev server settings
30
+ */
31
+ const webpackDevConfig = (): WebpackConfigWithDevServer => {
32
+ const webpackConfig: WebpackConfigWithDevServer = {
15
33
  ...baseDevConfig,
16
34
  ...(runningWebpackDevServer && { devServer: webpackDevServerConfig() })
17
35
  }
@@ -33,15 +51,19 @@ const webpackDevConfig = () => {
33
51
  return webpackConfig
34
52
  }
35
53
 
36
- const rspackDevConfig = () => {
54
+ /**
55
+ * Generate rspack-specific development configuration
56
+ * @returns Rspack configuration with dev server settings
57
+ */
58
+ const rspackDevConfig = (): RspackConfigWithDevServer => {
37
59
  const devServerConfig = webpackDevServerConfig()
38
- const rspackConfig = {
60
+ const rspackConfig: RspackConfigWithDevServer = {
39
61
  ...baseDevConfig,
40
62
  devServer: {
41
63
  ...devServerConfig,
42
64
  devMiddleware: {
43
- ...devServerConfig.devMiddleware,
44
- writeToDisk: (filePath) => !filePath.includes(".hot-update.")
65
+ ...(devServerConfig.devMiddleware || {}),
66
+ writeToDisk: (filePath: string) => !filePath.includes(".hot-update.")
45
67
  }
46
68
  }
47
69
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Production environment configuration for webpack and rspack bundlers
3
+ * @module environments/production
4
+ */
5
+
1
6
  /* eslint global-require: 0 */
2
7
  /* eslint import/no-dynamic-require: 0 */
3
8
 
@@ -6,6 +11,8 @@ const { merge } = require("webpack-merge")
6
11
  const baseConfig = require("./base")
7
12
  const { moduleExists } = require("../utils/helpers")
8
13
  const config = require("../config")
14
+ import type { Configuration as WebpackConfiguration, WebpackPluginInstance } from "webpack"
15
+ import type { CompressionPluginConstructor } from "./types"
9
16
 
10
17
  const optimizationPath = resolve(
11
18
  __dirname,
@@ -15,14 +22,18 @@ const optimizationPath = resolve(
15
22
  )
16
23
  const { getOptimization } = require(optimizationPath)
17
24
 
18
- let CompressionPlugin = null
25
+ let CompressionPlugin: CompressionPluginConstructor | null = null
19
26
  if (moduleExists("compression-webpack-plugin")) {
20
27
  // eslint-disable-next-line global-require
21
28
  CompressionPlugin = require("compression-webpack-plugin")
22
29
  }
23
30
 
24
- const getPlugins = () => {
25
- const plugins = []
31
+ /**
32
+ * Generate production plugins including compression
33
+ * @returns Array of webpack plugins for production
34
+ */
35
+ const getPlugins = (): WebpackPluginInstance[] => {
36
+ const plugins: WebpackPluginInstance[] = []
26
37
 
27
38
  if (CompressionPlugin) {
28
39
  plugins.push(
@@ -47,7 +58,10 @@ const getPlugins = () => {
47
58
  return plugins
48
59
  }
49
60
 
50
- const productionConfig = {
61
+ /**
62
+ * Production configuration with optimizations and compression
63
+ */
64
+ const productionConfig: Partial<WebpackConfiguration> = {
51
65
  devtool: "source-map",
52
66
  stats: "normal",
53
67
  bail: true,
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Test environment configuration for webpack and rspack bundlers
3
+ * @module environments/test
4
+ */
5
+
6
+ const { merge } = require("webpack-merge")
7
+ const config = require("../config")
8
+ const baseConfig = require("./base")
9
+ import type { Configuration as WebpackConfiguration } from "webpack"
10
+
11
+ interface TestConfig {
12
+ mode: "development" | "production" | "none"
13
+ devtool: string | false
14
+ watchOptions?: {
15
+ ignored: RegExp
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Shared test configuration for both webpack and rspack
21
+ * Ensures consistent test behavior across bundlers
22
+ */
23
+ const sharedTestConfig: TestConfig = {
24
+ mode: "development",
25
+ devtool: "cheap-module-source-map",
26
+ // Disable file watching in test mode
27
+ watchOptions: {
28
+ ignored: /node_modules/
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Generate rspack-specific test configuration
34
+ * @returns Rspack configuration optimized for testing
35
+ */
36
+ const rspackTestConfig = (): TestConfig => ({
37
+ ...sharedTestConfig
38
+ // Add any rspack-specific overrides here if needed
39
+ })
40
+
41
+ /**
42
+ * Generate webpack-specific test configuration
43
+ * @returns Webpack configuration for testing with same settings as rspack
44
+ */
45
+ const webpackTestConfig = (): Partial<WebpackConfiguration> => ({
46
+ ...sharedTestConfig
47
+ // Add any webpack-specific overrides here if needed
48
+ })
49
+
50
+ const bundlerConfig =
51
+ config.assets_bundler === "rspack" ? rspackTestConfig() : webpackTestConfig()
52
+
53
+ module.exports = merge(baseConfig, bundlerConfig)
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Type definitions for environment configurations
3
+ * These types are exported for consumer use
4
+ */
5
+
6
+ import type { Configuration as WebpackConfiguration, WebpackPluginInstance } from "webpack"
7
+ import type { Configuration as DevServerConfiguration } from "webpack-dev-server"
8
+
9
+ /**
10
+ * Webpack configuration extended with dev server support
11
+ */
12
+ export interface WebpackConfigWithDevServer extends WebpackConfiguration {
13
+ devServer?: DevServerConfiguration
14
+ plugins?: WebpackPluginInstance[]
15
+ }
16
+
17
+ /**
18
+ * Rspack plugin interface
19
+ * Rspack plugins follow a similar pattern to webpack but may have different internals
20
+ */
21
+ export interface RspackPlugin {
22
+ new(...args: any[]): {
23
+ apply(compiler: any): void
24
+ [key: string]: any
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Rspack dev server configuration
30
+ * Similar to webpack-dev-server but with some rspack-specific options
31
+ */
32
+ export interface RspackDevServerConfig {
33
+ port?: number | string | "auto"
34
+ host?: string
35
+ hot?: boolean | "only"
36
+ historyApiFallback?: boolean | Record<string, unknown>
37
+ headers?: Record<string, string | string[]>
38
+ proxy?: unknown
39
+ static?: boolean | string | Array<string | Record<string, unknown>>
40
+ devMiddleware?: {
41
+ writeToDisk?: boolean | ((filePath: string) => boolean)
42
+ publicPath?: string
43
+ [key: string]: unknown
44
+ }
45
+ [key: string]: unknown
46
+ }
47
+
48
+ /**
49
+ * Rspack configuration with dev server support
50
+ */
51
+ export interface RspackConfigWithDevServer {
52
+ mode?: "development" | "production" | "none"
53
+ devtool?: string | false
54
+ devServer?: RspackDevServerConfig
55
+ plugins?: RspackPlugin[]
56
+ module?: WebpackConfiguration["module"]
57
+ resolve?: WebpackConfiguration["resolve"]
58
+ entry?: WebpackConfiguration["entry"]
59
+ output?: WebpackConfiguration["output"]
60
+ optimization?: WebpackConfiguration["optimization"]
61
+ [key: string]: unknown
62
+ }
63
+
64
+ /**
65
+ * Compression plugin options interface
66
+ */
67
+ export interface CompressionPluginOptions {
68
+ filename: string
69
+ algorithm: string | "gzip" | "brotliCompress"
70
+ test: RegExp
71
+ threshold?: number
72
+ minRatio?: number
73
+ deleteOriginalAssets?: boolean
74
+ }
75
+
76
+ /**
77
+ * Compression plugin constructor type
78
+ */
79
+ export type CompressionPluginConstructor = new (options: CompressionPluginOptions) => WebpackPluginInstance
80
+
81
+ /**
82
+ * React Refresh plugin types
83
+ */
84
+ export interface ReactRefreshWebpackPlugin {
85
+ new(options?: Record<string, unknown>): WebpackPluginInstance
86
+ }
87
+
88
+ export interface ReactRefreshRspackPlugin {
89
+ new(options?: Record<string, unknown>): RspackPlugin
90
+ }
@@ -0,0 +1,87 @@
1
+ # Shakapacker TypeScript Types
2
+
3
+ This directory exports all TypeScript types used in Shakapacker for easier consumer imports.
4
+
5
+ ## Usage
6
+
7
+ Instead of importing types from deep paths:
8
+
9
+ ```typescript
10
+ // ❌ Old way - importing from multiple deep paths
11
+ import type { Config } from 'shakapacker/package/types'
12
+ import type { WebpackConfigWithDevServer } from 'shakapacker/package/environments/types'
13
+ ```
14
+
15
+ You can now import all types from a single location:
16
+
17
+ ```typescript
18
+ // ✅ New way - single import path
19
+ import type {
20
+ Config,
21
+ WebpackConfigWithDevServer,
22
+ RspackConfigWithDevServer
23
+ } from 'shakapacker/types'
24
+ ```
25
+
26
+ ## Available Types
27
+
28
+ ### Core Configuration Types
29
+ - `Config` - Main Shakapacker configuration interface
30
+ - `YamlConfig` - YAML configuration structure
31
+ - `LegacyConfig` - Legacy configuration with deprecated options
32
+ - `Env` - Environment variables interface
33
+ - `DevServerConfig` - Development server configuration
34
+
35
+ ### Loader Types
36
+ - `ShakapackerLoader` - Loader interface
37
+ - `ShakapackerLoaderOptions` - Loader options interface
38
+ - `LoaderResolver` - Function type for resolving loaders
39
+ - `LoaderConfig` - Loader configuration interface
40
+
41
+ ### Webpack/Rspack Types
42
+ - `WebpackConfigWithDevServer` - Webpack config with dev server
43
+ - `RspackConfigWithDevServer` - Rspack config with dev server
44
+ - `RspackPlugin` - Rspack plugin interface
45
+ - `RspackDevServerConfig` - Rspack dev server configuration
46
+ - `CompressionPluginOptions` - Options for compression plugin
47
+ - `CompressionPluginConstructor` - Constructor type for compression plugin
48
+ - `ReactRefreshWebpackPlugin` - React refresh plugin for Webpack
49
+ - `ReactRefreshRspackPlugin` - React refresh plugin for Rspack
50
+
51
+ ### Webpack-Specific Types
52
+ - `ShakapackerWebpackConfig` - Extended Webpack configuration
53
+ - `ShakapackerRule` - Extended Webpack rule
54
+ - `LoaderType` - String or loader object type
55
+ - `LoaderUtils` - Loader utility functions
56
+
57
+ ### Re-exported Types
58
+ - `WebpackConfiguration` - From 'webpack'
59
+ - `WebpackPluginInstance` - From 'webpack'
60
+ - `RuleSetRule` - From 'webpack'
61
+ - `NodeJSError` - Node.js error exception type
62
+
63
+ ## Example Usage
64
+
65
+ ```typescript
66
+ import type {
67
+ Config,
68
+ WebpackConfigWithDevServer
69
+ } from 'shakapacker/types'
70
+
71
+ const config: Config = {
72
+ source_path: 'app/javascript',
73
+ source_entry_path: 'packs',
74
+ public_root_path: 'public',
75
+ public_output_path: 'packs',
76
+ // ... other config
77
+ }
78
+
79
+ const webpackConfig: WebpackConfigWithDevServer = {
80
+ mode: 'development',
81
+ devServer: {
82
+ hot: true,
83
+ port: 3035
84
+ }
85
+ // ... other webpack config
86
+ }
87
+ ```
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Central type exports for Shakapacker
3
+ * This file re-exports all public TypeScript types for easier consumer imports
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * import type { Config, WebpackConfigWithDevServer } from 'shakapacker/types'
8
+ * ```
9
+ *
10
+ * @module shakapacker/types
11
+ */
12
+
13
+ // Core configuration types
14
+ export type {
15
+ Config,
16
+ YamlConfig,
17
+ LegacyConfig,
18
+ Env,
19
+ DevServerConfig
20
+ } from '../types'
21
+
22
+ // Loader types
23
+ export type {
24
+ ShakapackerLoader,
25
+ ShakapackerLoaderOptions,
26
+ LoaderResolver,
27
+ LoaderConfig
28
+ } from '../loaders'
29
+
30
+ // Webpack-specific types
31
+ export type {
32
+ ShakapackerWebpackConfig,
33
+ ShakapackerRule,
34
+ ShakapackerLoaderOptions as WebpackLoaderOptions,
35
+ ShakapackerLoader as WebpackLoader,
36
+ LoaderType,
37
+ LoaderUtils
38
+ } from '../webpack-types'
39
+
40
+ // Environment configuration types
41
+ export type {
42
+ WebpackConfigWithDevServer,
43
+ RspackPlugin,
44
+ RspackDevServerConfig,
45
+ RspackConfigWithDevServer,
46
+ CompressionPluginOptions,
47
+ CompressionPluginConstructor,
48
+ ReactRefreshWebpackPlugin,
49
+ ReactRefreshRspackPlugin
50
+ } from '../environments/types'
51
+
52
+ // Node.js error type (re-exported for convenience)
53
+ export type NodeJSError = NodeJS.ErrnoException
54
+
55
+ // Re-export commonly used webpack types for convenience
56
+ export type {
57
+ Configuration as WebpackConfiguration,
58
+ WebpackPluginInstance,
59
+ RuleSetRule
60
+ } from 'webpack'