shakapacker 9.0.0.beta.6 → 9.0.0.beta.8
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 +4 -4
- data/.eslintrc.fast.js +40 -0
- data/.eslintrc.js +48 -0
- data/.github/workflows/generator.yml +6 -0
- data/.gitignore +1 -4
- data/.npmignore +56 -0
- data/CHANGELOG.md +64 -1
- data/CONTRIBUTING.md +75 -21
- data/Gemfile.lock +1 -1
- data/README.md +4 -0
- data/TODO.md +15 -16
- data/docs/transpiler-migration.md +191 -0
- data/docs/typescript-migration.md +378 -0
- data/lib/install/template.rb +54 -7
- data/lib/shakapacker/version.rb +1 -1
- data/package/.npmignore +4 -0
- data/package/babel/preset.ts +56 -0
- data/package/config.ts +23 -10
- data/package/env.ts +15 -2
- data/package/environments/{development.js → development.ts} +30 -8
- data/package/environments/{production.js → production.ts} +18 -4
- data/package/environments/test.ts +53 -0
- data/package/environments/types.ts +90 -0
- data/package/esbuild/index.ts +42 -0
- data/package/optimization/rspack.ts +36 -0
- data/package/optimization/{webpack.js → webpack.ts} +12 -4
- data/package/plugins/{rspack.js → rspack.ts} +20 -5
- data/package/plugins/{webpack.js → webpack.ts} +2 -2
- data/package/rspack/{index.js → index.ts} +17 -10
- data/package/rules/{babel.js → babel.ts} +1 -1
- data/package/rules/{coffee.js → coffee.ts} +1 -1
- data/package/rules/{css.js → css.ts} +1 -1
- data/package/rules/{erb.js → erb.ts} +1 -1
- data/package/rules/{esbuild.js → esbuild.ts} +2 -2
- data/package/rules/{file.js → file.ts} +11 -6
- data/package/rules/{jscommon.js → jscommon.ts} +4 -4
- data/package/rules/{less.js → less.ts} +3 -3
- data/package/rules/raw.ts +25 -0
- data/package/rules/{rspack.js → rspack.ts} +21 -11
- data/package/rules/{sass.js → sass.ts} +1 -1
- data/package/rules/{stylus.js → stylus.ts} +3 -7
- data/package/rules/{swc.js → swc.ts} +2 -2
- data/package/rules/{webpack.js → webpack.ts} +1 -1
- data/package/swc/index.ts +54 -0
- data/package/types/README.md +87 -0
- data/package/types/index.ts +60 -0
- data/package/utils/errorCodes.ts +219 -0
- data/package/utils/errorHelpers.ts +68 -2
- data/package/utils/pathValidation.ts +139 -0
- data/package/utils/typeGuards.ts +161 -47
- data/package.json +26 -4
- data/scripts/remove-use-strict.js +45 -0
- data/scripts/type-check-no-emit.js +27 -0
- data/test/package/rules/raw.test.js +40 -7
- data/test/package/rules/webpack.test.js +21 -2
- data/test/package/transpiler-defaults.test.js +127 -0
- data/test/scripts/remove-use-strict.test.js +125 -0
- data/test/typescript/build.test.js +3 -2
- data/test/typescript/environments.test.js +107 -0
- data/test/typescript/pathValidation.test.js +142 -0
- data/test/typescript/securityValidation.test.js +182 -0
- data/tsconfig.eslint.json +16 -0
- data/tsconfig.json +9 -10
- data/yarn.lock +415 -6
- metadata +50 -28
- data/package/babel/preset.js +0 -48
- data/package/environments/base.js +0 -103
- data/package/environments/test.js +0 -19
- data/package/esbuild/index.js +0 -40
- data/package/optimization/rspack.js +0 -29
- data/package/rules/raw.js +0 -15
- data/package/swc/index.js +0 -50
@@ -1,21 +1,25 @@
|
|
1
|
-
|
1
|
+
import { dirname, sep, normalize } from "path"
|
2
2
|
const {
|
3
3
|
additional_paths: additionalPaths,
|
4
4
|
source_path: sourcePath
|
5
5
|
} = require("../config")
|
6
6
|
|
7
|
-
|
7
|
+
export = {
|
8
8
|
test: /\.(bmp|gif|jpe?g|png|tiff|ico|avif|webp|eot|otf|ttf|woff|woff2|svg)$/,
|
9
9
|
exclude: /\.(js|mjs|jsx|ts|tsx)$/,
|
10
10
|
type: "asset/resource",
|
11
11
|
generator: {
|
12
|
-
filename: (pathData) => {
|
12
|
+
filename: (pathData: { filename?: string }) => {
|
13
|
+
// Guard against null/undefined pathData or filename
|
14
|
+
if (!pathData || !pathData.filename) {
|
15
|
+
return `static/[name]-[hash][ext][query]`
|
16
|
+
}
|
13
17
|
const path = normalize(dirname(pathData.filename))
|
14
|
-
const stripPaths = [...additionalPaths, sourcePath].map((p) =>
|
18
|
+
const stripPaths = [...additionalPaths, sourcePath].map((p: string) =>
|
15
19
|
normalize(p)
|
16
20
|
)
|
17
21
|
|
18
|
-
const selectedStripPath = stripPaths.find((includePath) =>
|
22
|
+
const selectedStripPath = stripPaths.find((includePath: string) =>
|
19
23
|
path.startsWith(includePath)
|
20
24
|
)
|
21
25
|
|
@@ -23,9 +27,10 @@ module.exports = {
|
|
23
27
|
return `static/[name]-[hash][ext][query]`
|
24
28
|
}
|
25
29
|
|
30
|
+
// Split on both forward and backward slashes for cross-platform compatibility
|
26
31
|
const folders = path
|
27
32
|
.replace(selectedStripPath, "")
|
28
|
-
.split(
|
33
|
+
.split(/[\\/]/)
|
29
34
|
.filter(Boolean)
|
30
35
|
|
31
36
|
const foldersWithStatic = ["static", ...folders].join("/")
|
@@ -1,11 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import { resolve } from "path"
|
2
|
+
import { realpathSync } from "fs"
|
3
3
|
const {
|
4
4
|
source_path: sourcePath,
|
5
5
|
additional_paths: additionalPaths
|
6
6
|
} = require("../config")
|
7
7
|
|
8
|
-
const inclusions = [sourcePath, ...additionalPaths].map((p) => {
|
8
|
+
const inclusions = [sourcePath, ...additionalPaths].map((p: string) => {
|
9
9
|
try {
|
10
10
|
return realpathSync(p)
|
11
11
|
} catch (e) {
|
@@ -13,7 +13,7 @@ const inclusions = [sourcePath, ...additionalPaths].map((p) => {
|
|
13
13
|
}
|
14
14
|
})
|
15
15
|
|
16
|
-
|
16
|
+
export = {
|
17
17
|
include: inclusions,
|
18
18
|
exclude: [
|
19
19
|
{
|
@@ -1,4 +1,3 @@
|
|
1
|
-
const path = require("path")
|
2
1
|
const { canProcess } = require("../utils/helpers")
|
3
2
|
const { getStyleRule } = require("../utils/getStyleRule")
|
4
3
|
|
@@ -7,13 +6,14 @@ const {
|
|
7
6
|
source_path: sourcePath
|
8
7
|
} = require("../config")
|
9
8
|
|
10
|
-
|
9
|
+
export = canProcess("less-loader", (resolvedPath: string) =>
|
11
10
|
getStyleRule(/\.(less)(\.erb)?$/i, [
|
12
11
|
{
|
13
12
|
loader: resolvedPath,
|
14
13
|
options: {
|
15
14
|
lessOptions: {
|
16
|
-
paths
|
15
|
+
// Additional paths for Less imports (node_modules is resolved automatically)
|
16
|
+
paths: [sourcePath, ...paths]
|
17
17
|
},
|
18
18
|
sourceMap: true
|
19
19
|
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
const config = require("../config")
|
2
|
+
|
3
|
+
const rspackRawConfig = () => ({
|
4
|
+
resourceQuery: /raw/,
|
5
|
+
type: "asset/source"
|
6
|
+
})
|
7
|
+
|
8
|
+
const webpackRawConfig = () => ({
|
9
|
+
oneOf: [
|
10
|
+
{
|
11
|
+
// Match any file with ?raw query parameter
|
12
|
+
resourceQuery: /raw/,
|
13
|
+
type: "asset/source"
|
14
|
+
},
|
15
|
+
{
|
16
|
+
// Fallback: match .html files without query
|
17
|
+
test: /\.html$/,
|
18
|
+
exclude: /\.(js|mjs|jsx|ts|tsx)$/,
|
19
|
+
type: "asset/source"
|
20
|
+
}
|
21
|
+
]
|
22
|
+
})
|
23
|
+
|
24
|
+
export =
|
25
|
+
config.assets_bundler === "rspack" ? rspackRawConfig() : webpackRawConfig()
|
@@ -141,26 +141,36 @@ if (erb) {
|
|
141
141
|
}
|
142
142
|
|
143
143
|
// File/asset handling using Rspack's built-in asset modules
|
144
|
+
// This is a critical rule required for proper asset handling
|
144
145
|
debug("Adding file/asset handling rule...")
|
145
146
|
const file = require("./file")
|
146
147
|
|
147
|
-
if (file) {
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
148
|
+
if (!file) {
|
149
|
+
throw new Error(
|
150
|
+
"CRITICAL: file rule configuration returned null. " +
|
151
|
+
"Asset handling is required for proper bundling. " +
|
152
|
+
"Please ensure the file rule module exports a valid rule configuration."
|
153
|
+
)
|
152
154
|
}
|
153
155
|
|
156
|
+
debug("Successfully added file/asset rule")
|
157
|
+
rules.push(file)
|
158
|
+
|
154
159
|
// Raw file loading
|
160
|
+
// This is a critical rule required for raw file imports
|
155
161
|
debug("Adding raw file loading rule...")
|
156
162
|
const raw = require("./raw")
|
157
163
|
|
158
|
-
if (raw) {
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
164
|
+
if (!raw) {
|
165
|
+
throw new Error(
|
166
|
+
"CRITICAL: raw rule configuration returned null. " +
|
167
|
+
"Raw file loading is required for proper bundling. " +
|
168
|
+
"Please ensure the raw rule module exports a valid rule configuration."
|
169
|
+
)
|
163
170
|
}
|
164
171
|
|
172
|
+
debug("Successfully added raw file rule")
|
173
|
+
rules.push(raw)
|
174
|
+
|
165
175
|
debug(`Rspack rules configuration complete. Total rules: ${rules.length}`)
|
166
|
-
|
176
|
+
export = rules
|
@@ -4,7 +4,7 @@ const { getStyleRule } = require("../utils/getStyleRule")
|
|
4
4
|
const { canProcess, packageMajorVersion } = require("../utils/helpers")
|
5
5
|
const { additional_paths: extraPaths } = require("../config")
|
6
6
|
|
7
|
-
|
7
|
+
export = canProcess("sass-loader", (resolvedPath: string) => {
|
8
8
|
const optionKey =
|
9
9
|
packageMajorVersion("sass-loader") > 15 ? "loadPaths" : "includePaths"
|
10
10
|
return getStyleRule(/\.(scss|sass)(\.erb)?$/i, [
|
@@ -1,4 +1,3 @@
|
|
1
|
-
const path = require("path")
|
2
1
|
const { canProcess } = require("../utils/helpers")
|
3
2
|
const { getStyleRule } = require("../utils/getStyleRule")
|
4
3
|
|
@@ -7,17 +6,14 @@ const {
|
|
7
6
|
source_path: sourcePath
|
8
7
|
} = require("../config")
|
9
8
|
|
10
|
-
|
9
|
+
export = canProcess("stylus-loader", (resolvedPath: string) =>
|
11
10
|
getStyleRule(/\.(styl(us)?)(\.erb)?$/i, [
|
12
11
|
{
|
13
12
|
loader: resolvedPath,
|
14
13
|
options: {
|
15
14
|
stylusOptions: {
|
16
|
-
|
17
|
-
|
18
|
-
sourcePath,
|
19
|
-
...paths
|
20
|
-
]
|
15
|
+
// Additional paths for Stylus imports (node_modules is resolved automatically)
|
16
|
+
include: [sourcePath, ...paths]
|
21
17
|
},
|
22
18
|
sourceMap: true
|
23
19
|
}
|
@@ -3,8 +3,8 @@ const { getSwcLoaderConfig } = require("../swc")
|
|
3
3
|
const { javascript_transpiler: javascriptTranspiler } = require("../config")
|
4
4
|
const jscommon = require("./jscommon")
|
5
5
|
|
6
|
-
|
6
|
+
export = loaderMatches(javascriptTranspiler, "swc", () => ({
|
7
7
|
test: /\.(ts|tsx|js|jsx|mjs|coffee)?(\.erb)?$/,
|
8
8
|
...jscommon,
|
9
|
-
use: ({ resource }) => getSwcLoaderConfig(resource)
|
9
|
+
use: ({ resource }: { resource: string }) => getSwcLoaderConfig(resource)
|
10
10
|
}))
|
@@ -0,0 +1,54 @@
|
|
1
|
+
/* eslint global-require: 0 */
|
2
|
+
/* eslint import/no-dynamic-require: 0 */
|
3
|
+
|
4
|
+
import { resolve } from "path"
|
5
|
+
import { existsSync } from "fs"
|
6
|
+
import { merge } from "webpack-merge"
|
7
|
+
import type { RuleSetRule } from "webpack"
|
8
|
+
|
9
|
+
const JSX_FILE_REGEX = /\.(jsx|tsx)(\.erb)?$/
|
10
|
+
const TYPESCRIPT_FILE_REGEX = /\.(ts|tsx)(\.erb)?$/
|
11
|
+
|
12
|
+
const isJsxFile = (filename: string): boolean => !!filename.match(JSX_FILE_REGEX)
|
13
|
+
|
14
|
+
const isTypescriptFile = (filename: string): boolean => !!filename.match(TYPESCRIPT_FILE_REGEX)
|
15
|
+
|
16
|
+
const getCustomConfig = (): Partial<RuleSetRule> => {
|
17
|
+
const path = resolve("config", "swc.config.js")
|
18
|
+
if (existsSync(path)) {
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
20
|
+
return require(path)
|
21
|
+
}
|
22
|
+
return {}
|
23
|
+
}
|
24
|
+
|
25
|
+
const getSwcLoaderConfig = (filenameToProcess: string): RuleSetRule => {
|
26
|
+
const customConfig = getCustomConfig()
|
27
|
+
const isTs = isTypescriptFile(filenameToProcess)
|
28
|
+
const isJsx = isJsxFile(filenameToProcess)
|
29
|
+
const jsxKey = isTs ? "tsx" : "jsx"
|
30
|
+
|
31
|
+
const defaultConfig: RuleSetRule = {
|
32
|
+
loader: require.resolve("swc-loader"),
|
33
|
+
options: {
|
34
|
+
jsc: {
|
35
|
+
parser: {
|
36
|
+
dynamicImport: true,
|
37
|
+
syntax: isTs ? "typescript" : "ecmascript",
|
38
|
+
[jsxKey]: isJsx
|
39
|
+
},
|
40
|
+
loose: true
|
41
|
+
},
|
42
|
+
sourceMaps: true,
|
43
|
+
env: {
|
44
|
+
coreJs: 3,
|
45
|
+
exclude: ["transform-typeof-symbol"],
|
46
|
+
mode: "entry"
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
return merge(defaultConfig, customConfig)
|
52
|
+
}
|
53
|
+
|
54
|
+
export { getSwcLoaderConfig }
|
@@ -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'
|
@@ -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
|
+
}
|