shakapacker 8.4.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintignore +1 -0
  3. data/.eslintrc.fast.js +40 -0
  4. data/.eslintrc.js +48 -0
  5. data/.github/STATUS.md +1 -0
  6. data/.github/workflows/claude-code-review.yml +54 -0
  7. data/.github/workflows/claude.yml +50 -0
  8. data/.github/workflows/dummy.yml +8 -4
  9. data/.github/workflows/generator.yml +17 -14
  10. data/.github/workflows/node.yml +23 -1
  11. data/.github/workflows/ruby.yml +11 -0
  12. data/.github/workflows/test-bundlers.yml +170 -0
  13. data/.gitignore +17 -0
  14. data/.husky/pre-commit +2 -0
  15. data/.npmignore +56 -0
  16. data/.prettierignore +3 -0
  17. data/.rubocop.yml +1 -0
  18. data/.yalcignore +26 -0
  19. data/CHANGELOG.md +156 -18
  20. data/CLAUDE.md +29 -0
  21. data/CONTRIBUTING.md +138 -20
  22. data/Gemfile.lock +3 -3
  23. data/README.md +130 -5
  24. data/Rakefile +39 -4
  25. data/TODO.md +50 -0
  26. data/TODO_v9.md +87 -0
  27. data/conductor-setup.sh +70 -0
  28. data/conductor.json +7 -0
  29. data/docs/cdn_setup.md +379 -0
  30. data/docs/css-modules-export-mode.md +512 -0
  31. data/docs/deployment.md +10 -1
  32. data/docs/optional-peer-dependencies.md +198 -0
  33. data/docs/peer-dependencies.md +60 -0
  34. data/docs/rspack.md +190 -0
  35. data/docs/rspack_migration_guide.md +202 -0
  36. data/docs/transpiler-migration.md +188 -0
  37. data/docs/transpiler-performance.md +179 -0
  38. data/docs/troubleshooting.md +5 -0
  39. data/docs/typescript-migration.md +378 -0
  40. data/docs/typescript.md +99 -0
  41. data/docs/using_esbuild_loader.md +3 -3
  42. data/docs/using_swc_loader.md +5 -3
  43. data/docs/v6_upgrade.md +10 -0
  44. data/docs/v9_upgrade.md +413 -0
  45. data/lib/install/bin/shakapacker +3 -5
  46. data/lib/install/config/rspack/rspack.config.js +6 -0
  47. data/lib/install/config/rspack/rspack.config.ts +7 -0
  48. data/lib/install/config/shakapacker.yml +12 -2
  49. data/lib/install/config/webpack/webpack.config.ts +7 -0
  50. data/lib/install/package.json +38 -0
  51. data/lib/install/template.rb +194 -44
  52. data/lib/shakapacker/configuration.rb +141 -0
  53. data/lib/shakapacker/dev_server_runner.rb +25 -5
  54. data/lib/shakapacker/doctor.rb +844 -0
  55. data/lib/shakapacker/manifest.rb +4 -2
  56. data/lib/shakapacker/rspack_runner.rb +19 -0
  57. data/lib/shakapacker/runner.rb +144 -4
  58. data/lib/shakapacker/swc_migrator.rb +376 -0
  59. data/lib/shakapacker/utils/manager.rb +2 -0
  60. data/lib/shakapacker/version.rb +1 -1
  61. data/lib/shakapacker/version_checker.rb +1 -1
  62. data/lib/shakapacker/webpack_runner.rb +4 -42
  63. data/lib/shakapacker.rb +2 -1
  64. data/lib/tasks/shakapacker/doctor.rake +8 -0
  65. data/lib/tasks/shakapacker/install.rake +12 -2
  66. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  67. data/lib/tasks/shakapacker.rake +1 -0
  68. data/package/.npmignore +4 -0
  69. data/package/babel/preset.ts +56 -0
  70. data/package/config.ts +175 -0
  71. data/package/{dev_server.js → dev_server.ts} +8 -5
  72. data/package/env.ts +92 -0
  73. data/package/environments/base.ts +138 -0
  74. data/package/environments/development.ts +90 -0
  75. data/package/environments/production.ts +80 -0
  76. data/package/environments/test.ts +53 -0
  77. data/package/environments/types.ts +90 -0
  78. data/package/esbuild/index.ts +42 -0
  79. data/package/index.d.ts +3 -97
  80. data/package/index.ts +52 -0
  81. data/package/loaders.d.ts +28 -0
  82. data/package/optimization/rspack.ts +36 -0
  83. data/package/optimization/webpack.ts +57 -0
  84. data/package/plugins/rspack.ts +103 -0
  85. data/package/plugins/webpack.ts +62 -0
  86. data/package/rspack/index.ts +64 -0
  87. data/package/rules/{babel.js → babel.ts} +2 -2
  88. data/package/rules/{coffee.js → coffee.ts} +1 -1
  89. data/package/rules/css.ts +3 -0
  90. data/package/rules/{erb.js → erb.ts} +1 -1
  91. data/package/rules/esbuild.ts +10 -0
  92. data/package/rules/file.ts +40 -0
  93. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  94. data/package/rules/{less.js → less.ts} +4 -4
  95. data/package/rules/raw.ts +25 -0
  96. data/package/rules/rspack.ts +176 -0
  97. data/package/rules/{sass.js → sass.ts} +7 -3
  98. data/package/rules/{stylus.js → stylus.ts} +4 -8
  99. data/package/rules/swc.ts +10 -0
  100. data/package/rules/{index.js → webpack.ts} +1 -1
  101. data/package/swc/index.ts +54 -0
  102. data/package/types/README.md +87 -0
  103. data/package/types/index.ts +60 -0
  104. data/package/types.ts +108 -0
  105. data/package/utils/configPath.ts +6 -0
  106. data/package/utils/debug.ts +49 -0
  107. data/package/utils/defaultConfigPath.ts +4 -0
  108. data/package/utils/errorCodes.ts +219 -0
  109. data/package/utils/errorHelpers.ts +143 -0
  110. data/package/utils/getStyleRule.ts +64 -0
  111. data/package/utils/helpers.ts +85 -0
  112. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  113. data/package/utils/pathValidation.ts +139 -0
  114. data/package/utils/requireOrError.ts +15 -0
  115. data/package/utils/snakeToCamelCase.ts +5 -0
  116. data/package/utils/typeGuards.ts +342 -0
  117. data/package/utils/validateDependencies.ts +61 -0
  118. data/package/webpack-types.d.ts +33 -0
  119. data/package/webpackDevServerConfig.ts +117 -0
  120. data/package.json +134 -9
  121. data/scripts/remove-use-strict.js +45 -0
  122. data/scripts/type-check-no-emit.js +27 -0
  123. data/test/package/config.test.js +3 -0
  124. data/test/package/env.test.js +42 -7
  125. data/test/package/environments/base.test.js +5 -1
  126. data/test/package/rules/babel.test.js +16 -0
  127. data/test/package/rules/esbuild.test.js +1 -1
  128. data/test/package/rules/raw.test.js +40 -7
  129. data/test/package/rules/swc.test.js +1 -1
  130. data/test/package/rules/webpack.test.js +35 -0
  131. data/test/package/staging.test.js +4 -3
  132. data/test/package/transpiler-defaults.test.js +127 -0
  133. data/test/peer-dependencies.sh +85 -0
  134. data/test/scripts/remove-use-strict.test.js +125 -0
  135. data/test/typescript/build.test.js +118 -0
  136. data/test/typescript/environments.test.js +107 -0
  137. data/test/typescript/pathValidation.test.js +142 -0
  138. data/test/typescript/securityValidation.test.js +182 -0
  139. data/tools/README.md +124 -0
  140. data/tools/css-modules-v9-codemod.js +179 -0
  141. data/tsconfig.eslint.json +16 -0
  142. data/tsconfig.json +38 -0
  143. data/yarn.lock +2704 -767
  144. metadata +111 -41
  145. data/package/babel/preset.js +0 -48
  146. data/package/config.js +0 -56
  147. data/package/env.js +0 -48
  148. data/package/environments/base.js +0 -171
  149. data/package/environments/development.js +0 -13
  150. data/package/environments/production.js +0 -88
  151. data/package/environments/test.js +0 -3
  152. data/package/esbuild/index.js +0 -40
  153. data/package/index.js +0 -40
  154. data/package/rules/css.js +0 -3
  155. data/package/rules/esbuild.js +0 -10
  156. data/package/rules/file.js +0 -29
  157. data/package/rules/raw.js +0 -5
  158. data/package/rules/swc.js +0 -10
  159. data/package/swc/index.js +0 -50
  160. data/package/utils/configPath.js +0 -4
  161. data/package/utils/defaultConfigPath.js +0 -2
  162. data/package/utils/getStyleRule.js +0 -40
  163. data/package/utils/helpers.js +0 -62
  164. data/package/utils/snakeToCamelCase.js +0 -5
  165. data/package/webpackDevServerConfig.js +0 -71
  166. data/test/package/rules/index.test.js +0 -16
@@ -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'
data/package/types.ts ADDED
@@ -0,0 +1,108 @@
1
+ import * as https from "node:https"
2
+
3
+ // Type for the raw YAML config file
4
+ export interface YamlConfig {
5
+ [environment: string]: Partial<Config>
6
+ }
7
+
8
+ // Type for backward compatibility
9
+ export interface LegacyConfig extends Config {
10
+ webpack_loader?: string
11
+ }
12
+
13
+ export interface Config {
14
+ source_path: string
15
+ source_entry_path: string
16
+ nested_entries: boolean
17
+ css_extract_ignore_order_warnings: boolean
18
+ public_root_path: string
19
+ public_output_path: string
20
+ private_output_path?: string
21
+ cache_path: string
22
+ webpack_compile_output: boolean
23
+ shakapacker_precompile: boolean
24
+ additional_paths: string[]
25
+ cache_manifest: boolean
26
+ javascript_transpiler: string
27
+ ensure_consistent_versioning: boolean
28
+ compiler_strategy: string
29
+ useContentHash: boolean
30
+ compile: boolean
31
+ outputPath: string
32
+ publicPath: string
33
+ publicPathWithoutCDN: string
34
+ manifestPath: string
35
+ manifest_path?: string
36
+ assets_bundler?: string
37
+ dev_server?: DevServerConfig
38
+ integrity?: {
39
+ enabled: boolean
40
+ cross_origin: string
41
+ hash_functions?: string[]
42
+ }
43
+ }
44
+
45
+ export interface Env {
46
+ railsEnv: string
47
+ nodeEnv: string
48
+ isProduction: boolean
49
+ isDevelopment: boolean
50
+ runningWebpackDevServer: boolean
51
+ }
52
+
53
+ type Header =
54
+ | Array<{ key: string; value: string }>
55
+ | Record<string, string | string[]>
56
+ type ServerType = "http" | "https" | "spdy"
57
+ type WebSocketType = "sockjs" | "ws"
58
+
59
+ /**
60
+ * This has the same keys and behavior as https://webpack.js.org/configuration/dev-server/ except:
61
+ * 1. `hot` is replaced by `hmr`;
62
+ * 2. Camel-cased properties are replaced by snake-cased ones.
63
+ * @see {import('webpack-dev-server').Configuration}
64
+ */
65
+ export interface DevServerConfig {
66
+ allowed_hosts?: "all" | "auto" | string | string[]
67
+ bonjour?: boolean | Record<string, unknown> // bonjour.BonjourOptions
68
+ client?: Record<string, unknown> // Client
69
+ compress?: boolean
70
+ dev_middleware?: Record<string, unknown> // webpackDevMiddleware.Options
71
+ headers?: Header | (() => Header)
72
+ history_api_fallback?: boolean | Record<string, unknown> // HistoryApiFallbackOptions
73
+ hmr?: "only" | boolean
74
+ host?: "local-ip" | "local-ipv4" | "local-ipv6" | string
75
+ http2?: boolean
76
+ https?: boolean | https.ServerOptions
77
+ ipc?: boolean | string
78
+ magic_html?: boolean
79
+ live_reload?: boolean
80
+ inline_css?: boolean
81
+ env_prefix?: string
82
+ open?:
83
+ | boolean
84
+ | string
85
+ | string[]
86
+ | Record<string, unknown>
87
+ | Record<string, unknown>[]
88
+ port?: "auto" | string | number
89
+ proxy?: unknown // ProxyConfigMap | ProxyConfigArray
90
+ setup_exit_signals?: boolean
91
+ static?: boolean | string | unknown // Static | Array<string | Static>
92
+ watch_files?: string | string[] | unknown // WatchFiles | Array<WatchFiles | string>
93
+ web_socket_server?:
94
+ | string
95
+ | boolean
96
+ | WebSocketType
97
+ | {
98
+ type?: string | boolean | WebSocketType
99
+ options?: Record<string, unknown>
100
+ }
101
+ server?:
102
+ | string
103
+ | boolean
104
+ | ServerType
105
+ | { type?: string | boolean | ServerType; options?: https.ServerOptions }
106
+ [otherWebpackDevServerConfigKey: string]: unknown
107
+ }
108
+
@@ -0,0 +1,6 @@
1
+ import { resolve } from "path"
2
+
3
+ const configPath: string =
4
+ process.env.SHAKAPACKER_CONFIG || resolve("config", "shakapacker.yml")
5
+
6
+ export = configPath
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Debug utility for Shakapacker
3
+ * Provides conditional logging based on environment variables
4
+ */
5
+
6
+ const isDebugMode = (): boolean => {
7
+ // Explicitly check for debug mode being disabled
8
+ if (process.env.SHAKAPACKER_DEBUG === "false") {
9
+ return false
10
+ }
11
+
12
+ // Support both SHAKAPACKER_DEBUG (new) and DEBUG_SHAKAPACKER (legacy) for backwards compatibility
13
+ return (
14
+ process.env.SHAKAPACKER_DEBUG === "true" ||
15
+ process.env.DEBUG_SHAKAPACKER === "true"
16
+ )
17
+ }
18
+
19
+ const debug = (message: string, ...args: any[]): void => {
20
+ if (isDebugMode()) {
21
+ // eslint-disable-next-line no-console
22
+ console.log(`[Shakapacker] ${message}`, ...args)
23
+ }
24
+ }
25
+
26
+ const warn = (message: string, ...args: any[]): void => {
27
+ // eslint-disable-next-line no-console
28
+ console.warn(`[Shakapacker] WARNING: ${message}`, ...args)
29
+ }
30
+
31
+ const error = (message: string, ...args: any[]): void => {
32
+ // eslint-disable-next-line no-console
33
+ console.error(`[Shakapacker] ERROR: ${message}`, ...args)
34
+ }
35
+
36
+ const info = (message: string, ...args: any[]): void => {
37
+ if (isDebugMode()) {
38
+ // eslint-disable-next-line no-console
39
+ console.info(`[Shakapacker] INFO: ${message}`, ...args)
40
+ }
41
+ }
42
+
43
+ export = {
44
+ debug,
45
+ warn,
46
+ error,
47
+ info,
48
+ isDebugMode
49
+ }
@@ -0,0 +1,4 @@
1
+ import { resolve } from "path"
2
+
3
+ const path: string = resolve(__dirname, "../../lib/install/config/shakapacker.yml")
4
+ export = path
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Error codes for programmatic error handling in Shakapacker
3
+ * These codes allow consumers to handle specific errors programmatically
4
+ * @module shakapacker/utils/errorCodes
5
+ */
6
+
7
+ /**
8
+ * Error code enumeration for Shakapacker errors
9
+ */
10
+ export enum ErrorCode {
11
+ // Configuration errors (1xxx)
12
+ CONFIG_NOT_FOUND = 'SHAKAPACKER_1001',
13
+ CONFIG_INVALID_YAML = 'SHAKAPACKER_1002',
14
+ CONFIG_MISSING_REQUIRED = 'SHAKAPACKER_1003',
15
+ CONFIG_VALIDATION_FAILED = 'SHAKAPACKER_1004',
16
+ CONFIG_MERGE_FAILED = 'SHAKAPACKER_1005',
17
+ CONFIG_TYPE_MISMATCH = 'SHAKAPACKER_1006',
18
+
19
+ // File system errors (2xxx)
20
+ FILE_NOT_FOUND = 'SHAKAPACKER_2001',
21
+ FILE_READ_ERROR = 'SHAKAPACKER_2002',
22
+ FILE_WRITE_ERROR = 'SHAKAPACKER_2003',
23
+ FILE_PERMISSION_DENIED = 'SHAKAPACKER_2004',
24
+ PATH_TRAVERSAL_DETECTED = 'SHAKAPACKER_2005',
25
+ INVALID_PATH = 'SHAKAPACKER_2006',
26
+
27
+ // Module errors (3xxx)
28
+ MODULE_NOT_FOUND = 'SHAKAPACKER_3001',
29
+ MODULE_LOAD_FAILED = 'SHAKAPACKER_3002',
30
+ MODULE_INVALID_EXPORT = 'SHAKAPACKER_3003',
31
+ LOADER_NOT_FOUND = 'SHAKAPACKER_3004',
32
+ PLUGIN_NOT_FOUND = 'SHAKAPACKER_3005',
33
+ PLUGIN_INVALID = 'SHAKAPACKER_3006',
34
+
35
+ // Environment errors (4xxx)
36
+ ENV_INVALID_NODE_ENV = 'SHAKAPACKER_4001',
37
+ ENV_MISSING_REQUIRED = 'SHAKAPACKER_4002',
38
+ ENV_INVALID_VALUE = 'SHAKAPACKER_4003',
39
+ ENV_SANITIZATION_REQUIRED = 'SHAKAPACKER_4004',
40
+
41
+ // Bundler errors (5xxx)
42
+ BUNDLER_UNSUPPORTED = 'SHAKAPACKER_5001',
43
+ BUNDLER_CONFIG_INVALID = 'SHAKAPACKER_5002',
44
+ WEBPACK_CONFIG_INVALID = 'SHAKAPACKER_5003',
45
+ RSPACK_CONFIG_INVALID = 'SHAKAPACKER_5004',
46
+ TRANSPILER_NOT_FOUND = 'SHAKAPACKER_5005',
47
+ TRANSPILER_CONFIG_INVALID = 'SHAKAPACKER_5006',
48
+
49
+ // Dev server errors (6xxx)
50
+ DEVSERVER_CONFIG_INVALID = 'SHAKAPACKER_6001',
51
+ DEVSERVER_PORT_INVALID = 'SHAKAPACKER_6002',
52
+ DEVSERVER_PORT_IN_USE = 'SHAKAPACKER_6003',
53
+ DEVSERVER_START_FAILED = 'SHAKAPACKER_6004',
54
+
55
+ // Security errors (7xxx)
56
+ SECURITY_PATH_TRAVERSAL = 'SHAKAPACKER_7001',
57
+ SECURITY_INVALID_INPUT = 'SHAKAPACKER_7002',
58
+ SECURITY_CONTROL_CHARS = 'SHAKAPACKER_7003',
59
+ SECURITY_INJECTION_ATTEMPT = 'SHAKAPACKER_7004',
60
+
61
+ // Validation errors (8xxx)
62
+ VALIDATION_FAILED = 'SHAKAPACKER_8001',
63
+ VALIDATION_TYPE_ERROR = 'SHAKAPACKER_8002',
64
+ VALIDATION_RANGE_ERROR = 'SHAKAPACKER_8003',
65
+ VALIDATION_FORMAT_ERROR = 'SHAKAPACKER_8004',
66
+ VALIDATION_CONSTRAINT_ERROR = 'SHAKAPACKER_8005',
67
+
68
+ // Generic errors (9xxx)
69
+ UNKNOWN_ERROR = 'SHAKAPACKER_9000',
70
+ INTERNAL_ERROR = 'SHAKAPACKER_9001',
71
+ DEPRECATED_FEATURE = 'SHAKAPACKER_9002',
72
+ NOT_IMPLEMENTED = 'SHAKAPACKER_9003',
73
+ OPERATION_FAILED = 'SHAKAPACKER_9004'
74
+ }
75
+
76
+ /**
77
+ * Error message templates for each error code
78
+ */
79
+ export const ErrorMessages: Record<ErrorCode, string> = {
80
+ // Configuration errors
81
+ [ErrorCode.CONFIG_NOT_FOUND]: 'Configuration file not found: {path}',
82
+ [ErrorCode.CONFIG_INVALID_YAML]: 'Invalid YAML in configuration file: {path}',
83
+ [ErrorCode.CONFIG_MISSING_REQUIRED]: 'Missing required configuration field: {field}',
84
+ [ErrorCode.CONFIG_VALIDATION_FAILED]: 'Configuration validation failed: {reason}',
85
+ [ErrorCode.CONFIG_MERGE_FAILED]: 'Failed to merge configurations: {reason}',
86
+ [ErrorCode.CONFIG_TYPE_MISMATCH]: 'Configuration type mismatch for {field}: expected {expected}, got {actual}',
87
+
88
+ // File system errors
89
+ [ErrorCode.FILE_NOT_FOUND]: 'File not found: {path}',
90
+ [ErrorCode.FILE_READ_ERROR]: 'Error reading file: {path}',
91
+ [ErrorCode.FILE_WRITE_ERROR]: 'Error writing file: {path}',
92
+ [ErrorCode.FILE_PERMISSION_DENIED]: 'Permission denied accessing: {path}',
93
+ [ErrorCode.PATH_TRAVERSAL_DETECTED]: 'Path traversal attempt detected: {path}',
94
+ [ErrorCode.INVALID_PATH]: 'Invalid path: {path}',
95
+
96
+ // Module errors
97
+ [ErrorCode.MODULE_NOT_FOUND]: 'Module not found: {module}',
98
+ [ErrorCode.MODULE_LOAD_FAILED]: 'Failed to load module: {module}',
99
+ [ErrorCode.MODULE_INVALID_EXPORT]: 'Invalid export from module: {module}',
100
+ [ErrorCode.LOADER_NOT_FOUND]: 'Loader not found: {loader}',
101
+ [ErrorCode.PLUGIN_NOT_FOUND]: 'Plugin not found: {plugin}',
102
+ [ErrorCode.PLUGIN_INVALID]: 'Invalid plugin: {plugin}',
103
+
104
+ // Environment errors
105
+ [ErrorCode.ENV_INVALID_NODE_ENV]: 'Invalid NODE_ENV value: {value}. Valid values are: {valid}',
106
+ [ErrorCode.ENV_MISSING_REQUIRED]: 'Missing required environment variable: {variable}',
107
+ [ErrorCode.ENV_INVALID_VALUE]: 'Invalid value for environment variable {variable}: {value}',
108
+ [ErrorCode.ENV_SANITIZATION_REQUIRED]: 'Environment variable {variable} contained unsafe characters and was sanitized',
109
+
110
+ // Bundler errors
111
+ [ErrorCode.BUNDLER_UNSUPPORTED]: 'Unsupported bundler: {bundler}',
112
+ [ErrorCode.BUNDLER_CONFIG_INVALID]: 'Invalid bundler configuration: {reason}',
113
+ [ErrorCode.WEBPACK_CONFIG_INVALID]: 'Invalid webpack configuration: {reason}',
114
+ [ErrorCode.RSPACK_CONFIG_INVALID]: 'Invalid rspack configuration: {reason}',
115
+ [ErrorCode.TRANSPILER_NOT_FOUND]: 'Transpiler not found: {transpiler}',
116
+ [ErrorCode.TRANSPILER_CONFIG_INVALID]: 'Invalid transpiler configuration: {reason}',
117
+
118
+ // Dev server errors
119
+ [ErrorCode.DEVSERVER_CONFIG_INVALID]: 'Invalid dev server configuration: {reason}',
120
+ [ErrorCode.DEVSERVER_PORT_INVALID]: 'Invalid port: {port}',
121
+ [ErrorCode.DEVSERVER_PORT_IN_USE]: 'Port {port} is already in use',
122
+ [ErrorCode.DEVSERVER_START_FAILED]: 'Failed to start dev server: {reason}',
123
+
124
+ // Security errors
125
+ [ErrorCode.SECURITY_PATH_TRAVERSAL]: 'Security: Path traversal attempt blocked: {path}',
126
+ [ErrorCode.SECURITY_INVALID_INPUT]: 'Security: Invalid input detected: {input}',
127
+ [ErrorCode.SECURITY_CONTROL_CHARS]: 'Security: Control characters detected and removed from: {field}',
128
+ [ErrorCode.SECURITY_INJECTION_ATTEMPT]: 'Security: Potential injection attempt blocked: {details}',
129
+
130
+ // Validation errors
131
+ [ErrorCode.VALIDATION_FAILED]: 'Validation failed: {reason}',
132
+ [ErrorCode.VALIDATION_TYPE_ERROR]: 'Type validation error: {field} should be {type}',
133
+ [ErrorCode.VALIDATION_RANGE_ERROR]: 'Value out of range: {field} must be between {min} and {max}',
134
+ [ErrorCode.VALIDATION_FORMAT_ERROR]: 'Format error: {field} does not match expected format',
135
+ [ErrorCode.VALIDATION_CONSTRAINT_ERROR]: 'Constraint violation: {constraint}',
136
+
137
+ // Generic errors
138
+ [ErrorCode.UNKNOWN_ERROR]: 'An unknown error occurred',
139
+ [ErrorCode.INTERNAL_ERROR]: 'Internal error: {details}',
140
+ [ErrorCode.DEPRECATED_FEATURE]: 'Deprecated feature: {feature}. Use {alternative} instead',
141
+ [ErrorCode.NOT_IMPLEMENTED]: 'Feature not yet implemented: {feature}',
142
+ [ErrorCode.OPERATION_FAILED]: 'Operation failed: {operation}'
143
+ }
144
+
145
+ /**
146
+ * Shakapacker error class with error code support
147
+ */
148
+ export class ShakapackerError extends Error {
149
+ public readonly code: ErrorCode
150
+ public readonly details?: Record<string, any>
151
+
152
+ constructor(code: ErrorCode, details?: Record<string, any>, customMessage?: string) {
153
+ const template = ErrorMessages[code] || 'An error occurred'
154
+ const message = customMessage || ShakapackerError.formatMessage(template, details)
155
+
156
+ super(message)
157
+ this.name = 'ShakapackerError'
158
+ this.code = code
159
+ this.details = details
160
+
161
+ // Maintain proper stack trace for where error was thrown
162
+ if (Error.captureStackTrace) {
163
+ Error.captureStackTrace(this, ShakapackerError)
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Format error message with template values
169
+ */
170
+ private static formatMessage(template: string, details?: Record<string, any>): string {
171
+ if (!details) return template
172
+
173
+ return template.replace(/{(\w+)}/g, (match, key) => {
174
+ const value = details[key]
175
+ if (value === undefined) return match
176
+ if (typeof value === 'object') {
177
+ return JSON.stringify(value)
178
+ }
179
+ return String(value)
180
+ })
181
+ }
182
+
183
+ /**
184
+ * Convert error to JSON for logging or API responses
185
+ */
186
+ toJSON(): Record<string, any> {
187
+ return {
188
+ name: this.name,
189
+ code: this.code,
190
+ message: this.message,
191
+ details: this.details,
192
+ stack: this.stack
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Helper function to create a Shakapacker error
199
+ */
200
+ export function createError(code: ErrorCode, details?: Record<string, any>): ShakapackerError {
201
+ return new ShakapackerError(code, details)
202
+ }
203
+
204
+ /**
205
+ * Helper function to check if an error is a Shakapacker error
206
+ */
207
+ export function isShakapackerError(error: unknown): error is ShakapackerError {
208
+ return error instanceof ShakapackerError
209
+ }
210
+
211
+ /**
212
+ * Helper function to get error code from any error
213
+ */
214
+ export function getErrorCode(error: unknown): ErrorCode | null {
215
+ if (isShakapackerError(error)) {
216
+ return error.code
217
+ }
218
+ return null
219
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Error handling utilities for consistent error management
3
+ */
4
+
5
+ import { ErrorCode, ShakapackerError } from './errorCodes'
6
+
7
+ /**
8
+ * Checks if an error is a file not found error (ENOENT)
9
+ */
10
+ export function isFileNotFoundError(error: unknown): boolean {
11
+ return (
12
+ error !== null &&
13
+ typeof error === 'object' &&
14
+ 'code' in error &&
15
+ (error as NodeJS.ErrnoException).code === 'ENOENT'
16
+ )
17
+ }
18
+
19
+ /**
20
+ * Checks if an error is a module not found error
21
+ */
22
+ export function isModuleNotFoundError(error: unknown): boolean {
23
+ return (
24
+ error !== null &&
25
+ typeof error === 'object' &&
26
+ 'code' in error &&
27
+ (error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND'
28
+ )
29
+ }
30
+
31
+ /**
32
+ * Creates a consistent error message for file operations
33
+ */
34
+ export function createFileOperationError(
35
+ operation: 'read' | 'write' | 'delete',
36
+ filePath: string,
37
+ details?: string
38
+ ): ShakapackerError {
39
+ const errorCode = operation === 'read'
40
+ ? ErrorCode.FILE_READ_ERROR
41
+ : operation === 'write'
42
+ ? ErrorCode.FILE_WRITE_ERROR
43
+ : ErrorCode.FILE_NOT_FOUND
44
+
45
+ return new ShakapackerError(errorCode, {
46
+ path: filePath,
47
+ operation,
48
+ details
49
+ })
50
+ }
51
+
52
+ /**
53
+ * Creates a consistent error message for file operations (backward compatibility)
54
+ */
55
+ export function createFileOperationErrorLegacy(
56
+ operation: 'read' | 'write' | 'delete',
57
+ filePath: string,
58
+ details?: string
59
+ ): Error {
60
+ const baseMessage = `Failed to ${operation} file at path '${filePath}'`
61
+ const errorDetails = details ? ` - ${details}` : ''
62
+ const suggestion = operation === 'read'
63
+ ? ' (check if file exists and permissions are correct)'
64
+ : operation === 'write'
65
+ ? ' (check write permissions and disk space)'
66
+ : ' (check permissions)'
67
+ return new Error(`${baseMessage}${errorDetails}${suggestion}`)
68
+ }
69
+
70
+ /**
71
+ * Safely gets error message from unknown error type
72
+ */
73
+ export function getErrorMessage(error: unknown): string {
74
+ if (error instanceof Error) {
75
+ // Include stack trace for better debugging in development
76
+ const isDev = process.env.NODE_ENV === 'development'
77
+ return isDev && error.stack ? `${error.message}\n${error.stack}` : error.message
78
+ }
79
+ if (typeof error === 'string') {
80
+ return error
81
+ }
82
+ if (error && typeof error === 'object' && 'message' in error) {
83
+ return String((error as { message: unknown }).message)
84
+ }
85
+ // Provide more context for truly unknown errors
86
+ return `Unknown error occurred (type: ${typeof error}, value: ${JSON.stringify(error)})`
87
+ }
88
+
89
+ /**
90
+ * Type guard for NodeJS errors with errno
91
+ */
92
+ export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
93
+ return (
94
+ error instanceof Error &&
95
+ 'code' in error &&
96
+ typeof (error as NodeJS.ErrnoException).code === 'string'
97
+ )
98
+ }
99
+
100
+ /**
101
+ * Creates a configuration validation error
102
+ */
103
+ export function createConfigValidationErrorWithCode(
104
+ configPath: string,
105
+ environment: string,
106
+ reason: string
107
+ ): ShakapackerError {
108
+ return new ShakapackerError(ErrorCode.CONFIG_VALIDATION_FAILED, {
109
+ path: configPath,
110
+ environment,
111
+ reason
112
+ })
113
+ }
114
+
115
+ /**
116
+ * Creates a module not found error
117
+ */
118
+ export function createModuleNotFoundError(moduleName: string, details?: string): ShakapackerError {
119
+ return new ShakapackerError(ErrorCode.MODULE_NOT_FOUND, {
120
+ module: moduleName,
121
+ details
122
+ })
123
+ }
124
+
125
+ /**
126
+ * Creates a path traversal security error
127
+ */
128
+ export function createPathTraversalError(path: string): ShakapackerError {
129
+ return new ShakapackerError(ErrorCode.SECURITY_PATH_TRAVERSAL, {
130
+ path
131
+ })
132
+ }
133
+
134
+ /**
135
+ * Creates a port validation error
136
+ */
137
+ export function createPortValidationError(port: unknown): ShakapackerError {
138
+ return new ShakapackerError(ErrorCode.DEVSERVER_PORT_INVALID, {
139
+ port: String(port)
140
+ })
141
+ }
142
+
143
+