shakapacker 9.0.0.beta.4 → 9.0.0.beta.6
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/.eslintignore +1 -0
- data/.github/workflows/claude-code-review.yml +1 -1
- data/.github/workflows/dummy.yml +4 -0
- data/.github/workflows/generator.yml +7 -0
- data/.github/workflows/node.yml +22 -0
- data/.github/workflows/ruby.yml +11 -0
- data/.github/workflows/test-bundlers.yml +27 -9
- data/.gitignore +20 -0
- data/.yalcignore +26 -0
- data/CHANGELOG.md +58 -40
- data/CONTRIBUTING.md +64 -0
- data/Gemfile.lock +1 -1
- data/README.md +80 -1
- data/docs/optional-peer-dependencies.md +198 -0
- data/docs/typescript.md +99 -0
- data/docs/v9_upgrade.md +79 -2
- data/lib/install/template.rb +8 -1
- data/lib/shakapacker/configuration.rb +58 -1
- data/lib/shakapacker/doctor.rb +751 -0
- data/lib/shakapacker/swc_migrator.rb +292 -0
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker.rb +1 -0
- data/lib/tasks/shakapacker/doctor.rake +8 -0
- data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
- data/lib/tasks/shakapacker.rake +1 -0
- data/package/config.ts +162 -0
- data/package/{dev_server.js → dev_server.ts} +8 -5
- data/package/env.ts +67 -0
- data/package/environments/base.js +94 -117
- data/package/environments/base.ts +138 -0
- data/package/index.d.ts +3 -150
- data/package/{index.js → index.ts} +18 -8
- data/package/loaders.d.ts +28 -0
- data/package/types.ts +108 -0
- data/package/utils/configPath.ts +6 -0
- data/package/utils/{debug.js → debug.ts} +7 -7
- data/package/utils/defaultConfigPath.ts +4 -0
- data/package/utils/errorHelpers.ts +77 -0
- data/package/utils/{getStyleRule.js → getStyleRule.ts} +17 -20
- data/package/utils/helpers.ts +85 -0
- data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
- data/package/utils/{requireOrError.js → requireOrError.ts} +2 -2
- data/package/utils/snakeToCamelCase.ts +5 -0
- data/package/utils/typeGuards.ts +228 -0
- data/package/utils/{validateDependencies.js → validateDependencies.ts} +4 -4
- data/package/webpack-types.d.ts +33 -0
- data/package/webpackDevServerConfig.ts +117 -0
- data/package.json +112 -4
- data/test/peer-dependencies.sh +85 -0
- data/test/typescript/build.test.js +117 -0
- data/tsconfig.json +39 -0
- data/yarn.lock +1 -1
- metadata +34 -17
- data/package/config.js +0 -80
- data/package/env.js +0 -48
- data/package/utils/configPath.js +0 -4
- data/package/utils/defaultConfigPath.js +0 -2
- data/package/utils/helpers.js +0 -127
- data/package/utils/snakeToCamelCase.js +0 -5
- data/package/utils/validateCssModulesConfig.js +0 -91
- data/package/webpackDevServerConfig.js +0 -73
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
|
+
|
@@ -3,7 +3,7 @@
|
|
3
3
|
* Provides conditional logging based on environment variables
|
4
4
|
*/
|
5
5
|
|
6
|
-
const isDebugMode = () => {
|
6
|
+
const isDebugMode = (): boolean => {
|
7
7
|
// Explicitly check for debug mode being disabled
|
8
8
|
if (process.env.SHAKAPACKER_DEBUG === "false") {
|
9
9
|
return false
|
@@ -16,34 +16,34 @@ const isDebugMode = () => {
|
|
16
16
|
)
|
17
17
|
}
|
18
18
|
|
19
|
-
const debug = (message, ...args) => {
|
19
|
+
const debug = (message: string, ...args: any[]): void => {
|
20
20
|
if (isDebugMode()) {
|
21
21
|
// eslint-disable-next-line no-console
|
22
22
|
console.log(`[Shakapacker] ${message}`, ...args)
|
23
23
|
}
|
24
24
|
}
|
25
25
|
|
26
|
-
const warn = (message, ...args) => {
|
26
|
+
const warn = (message: string, ...args: any[]): void => {
|
27
27
|
// eslint-disable-next-line no-console
|
28
28
|
console.warn(`[Shakapacker] WARNING: ${message}`, ...args)
|
29
29
|
}
|
30
30
|
|
31
|
-
const error = (message, ...args) => {
|
31
|
+
const error = (message: string, ...args: any[]): void => {
|
32
32
|
// eslint-disable-next-line no-console
|
33
33
|
console.error(`[Shakapacker] ERROR: ${message}`, ...args)
|
34
34
|
}
|
35
35
|
|
36
|
-
const info = (message, ...args) => {
|
36
|
+
const info = (message: string, ...args: any[]): void => {
|
37
37
|
if (isDebugMode()) {
|
38
38
|
// eslint-disable-next-line no-console
|
39
39
|
console.info(`[Shakapacker] INFO: ${message}`, ...args)
|
40
40
|
}
|
41
41
|
}
|
42
42
|
|
43
|
-
|
43
|
+
export = {
|
44
44
|
debug,
|
45
45
|
warn,
|
46
46
|
error,
|
47
47
|
info,
|
48
48
|
isDebugMode
|
49
|
-
}
|
49
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
/**
|
2
|
+
* Error handling utilities for consistent error management
|
3
|
+
*/
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Checks if an error is a file not found error (ENOENT)
|
7
|
+
*/
|
8
|
+
export function isFileNotFoundError(error: unknown): boolean {
|
9
|
+
return (
|
10
|
+
error !== null &&
|
11
|
+
typeof error === 'object' &&
|
12
|
+
'code' in error &&
|
13
|
+
(error as NodeJS.ErrnoException).code === 'ENOENT'
|
14
|
+
)
|
15
|
+
}
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Checks if an error is a module not found error
|
19
|
+
*/
|
20
|
+
export function isModuleNotFoundError(error: unknown): boolean {
|
21
|
+
return (
|
22
|
+
error !== null &&
|
23
|
+
typeof error === 'object' &&
|
24
|
+
'code' in error &&
|
25
|
+
(error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND'
|
26
|
+
)
|
27
|
+
}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Creates a consistent error message for file operations
|
31
|
+
*/
|
32
|
+
export function createFileOperationError(
|
33
|
+
operation: 'read' | 'write' | 'delete',
|
34
|
+
filePath: string,
|
35
|
+
details?: string
|
36
|
+
): Error {
|
37
|
+
const baseMessage = `Failed to ${operation} file at path '${filePath}'`
|
38
|
+
const errorDetails = details ? ` - ${details}` : ''
|
39
|
+
const suggestion = operation === 'read'
|
40
|
+
? ' (check if file exists and permissions are correct)'
|
41
|
+
: operation === 'write'
|
42
|
+
? ' (check write permissions and disk space)'
|
43
|
+
: ' (check permissions)'
|
44
|
+
return new Error(`${baseMessage}${errorDetails}${suggestion}`)
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Safely gets error message from unknown error type
|
49
|
+
*/
|
50
|
+
export function getErrorMessage(error: unknown): string {
|
51
|
+
if (error instanceof Error) {
|
52
|
+
// Include stack trace for better debugging in development
|
53
|
+
const isDev = process.env.NODE_ENV === 'development'
|
54
|
+
return isDev && error.stack ? `${error.message}\n${error.stack}` : error.message
|
55
|
+
}
|
56
|
+
if (typeof error === 'string') {
|
57
|
+
return error
|
58
|
+
}
|
59
|
+
if (error && typeof error === 'object' && 'message' in error) {
|
60
|
+
return String((error as { message: unknown }).message)
|
61
|
+
}
|
62
|
+
// Provide more context for truly unknown errors
|
63
|
+
return `Unknown error occurred (type: ${typeof error}, value: ${JSON.stringify(error)})`
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Type guard for NodeJS errors with errno
|
68
|
+
*/
|
69
|
+
export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
70
|
+
return (
|
71
|
+
error instanceof Error &&
|
72
|
+
'code' in error &&
|
73
|
+
typeof (error as any).code === 'string'
|
74
|
+
)
|
75
|
+
}
|
76
|
+
|
77
|
+
|
@@ -3,12 +3,17 @@ const { canProcess, moduleExists } = require("./helpers")
|
|
3
3
|
const { requireOrError } = require("./requireOrError")
|
4
4
|
const config = require("../config")
|
5
5
|
const inliningCss = require("./inliningCss")
|
6
|
-
const { validateCssModulesConfig } = require("./validateCssModulesConfig")
|
7
6
|
|
8
|
-
|
7
|
+
interface StyleRule {
|
8
|
+
test: RegExp
|
9
|
+
use: any[]
|
10
|
+
type?: string
|
11
|
+
}
|
12
|
+
|
13
|
+
const getStyleRule = (test: RegExp, preprocessors: any[] = []): StyleRule | null => {
|
9
14
|
if (moduleExists("css-loader")) {
|
10
15
|
const tryPostcss = () =>
|
11
|
-
canProcess("postcss-loader", (loaderPath) => ({
|
16
|
+
canProcess("postcss-loader", (loaderPath: string) => ({
|
12
17
|
loader: loaderPath,
|
13
18
|
options: { sourceMap: true }
|
14
19
|
}))
|
@@ -20,31 +25,23 @@ const getStyleRule = (test, preprocessors = []) => {
|
|
20
25
|
? requireOrError("@rspack/core").CssExtractRspackPlugin.loader
|
21
26
|
: requireOrError("mini-css-extract-plugin").loader
|
22
27
|
|
23
|
-
const cssLoaderOptions = {
|
24
|
-
sourceMap: true,
|
25
|
-
importLoaders: 2,
|
26
|
-
modules: {
|
27
|
-
auto: true,
|
28
|
-
// v9 defaults: named exports with camelCase conversion
|
29
|
-
namedExport: true,
|
30
|
-
exportLocalsConvention: "camelCase"
|
31
|
-
}
|
32
|
-
}
|
33
|
-
|
34
|
-
// Validate CSS modules configuration
|
35
|
-
validateCssModulesConfig(cssLoaderOptions)
|
36
|
-
|
37
28
|
const use = [
|
38
29
|
inliningCss ? "style-loader" : extractionPlugin,
|
39
30
|
{
|
40
31
|
loader: require.resolve("css-loader"),
|
41
|
-
options:
|
32
|
+
options: {
|
33
|
+
sourceMap: true,
|
34
|
+
importLoaders: 2,
|
35
|
+
modules: {
|
36
|
+
auto: true
|
37
|
+
}
|
38
|
+
}
|
42
39
|
},
|
43
40
|
tryPostcss(),
|
44
41
|
...preprocessors
|
45
42
|
].filter(Boolean)
|
46
43
|
|
47
|
-
const result = {
|
44
|
+
const result: StyleRule = {
|
48
45
|
test,
|
49
46
|
use
|
50
47
|
}
|
@@ -59,4 +56,4 @@ const getStyleRule = (test, preprocessors = []) => {
|
|
59
56
|
return null
|
60
57
|
}
|
61
58
|
|
62
|
-
|
59
|
+
export = { getStyleRule }
|
@@ -0,0 +1,85 @@
|
|
1
|
+
const { isModuleNotFoundError, getErrorMessage } = require("./errorHelpers")
|
2
|
+
|
3
|
+
const isBoolean = (str: string): boolean => /^true/.test(str) || /^false/.test(str)
|
4
|
+
|
5
|
+
const ensureTrailingSlash = (path: string): string => (path.endsWith("/") ? path : `${path}/`)
|
6
|
+
|
7
|
+
const resolvedPath = (packageName: string): string | null => {
|
8
|
+
try {
|
9
|
+
return require.resolve(packageName)
|
10
|
+
} catch (error: unknown) {
|
11
|
+
if (!isModuleNotFoundError(error)) {
|
12
|
+
throw error
|
13
|
+
}
|
14
|
+
return null
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
const moduleExists = (packageName: string): boolean => !!resolvedPath(packageName)
|
19
|
+
|
20
|
+
const canProcess = <T = unknown>(rule: string, fn: (modulePath: string) => T): T | null => {
|
21
|
+
const modulePath = resolvedPath(rule)
|
22
|
+
|
23
|
+
if (modulePath) {
|
24
|
+
return fn(modulePath)
|
25
|
+
}
|
26
|
+
|
27
|
+
return null
|
28
|
+
}
|
29
|
+
|
30
|
+
const loaderMatches = <T = unknown>(configLoader: string, loaderToCheck: string, fn: () => T): T | null => {
|
31
|
+
if (configLoader !== loaderToCheck) {
|
32
|
+
return null
|
33
|
+
}
|
34
|
+
|
35
|
+
const loaderName = `${configLoader}-loader`
|
36
|
+
|
37
|
+
if (!moduleExists(loaderName)) {
|
38
|
+
throw new Error(
|
39
|
+
`Your Shakapacker config specified using ${configLoader}, but ${loaderName} package is not installed.\n` +
|
40
|
+
`\nTo fix this issue, run one of the following commands:\n` +
|
41
|
+
` npm install --save-dev ${loaderName}\n` +
|
42
|
+
` yarn add --dev ${loaderName}\n` +
|
43
|
+
`\nOr change your 'javascript_transpiler' setting in shakapacker.yml to use a different loader.`
|
44
|
+
)
|
45
|
+
}
|
46
|
+
|
47
|
+
return fn()
|
48
|
+
}
|
49
|
+
|
50
|
+
const packageFullVersion = (packageName: string): string => {
|
51
|
+
try {
|
52
|
+
// eslint-disable-next-line import/no-dynamic-require
|
53
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
54
|
+
// eslint-disable-next-line import/no-dynamic-require, global-require
|
55
|
+
const packageJson = require(packageJsonPath) as { version: string }
|
56
|
+
return packageJson.version
|
57
|
+
} catch (error: any) {
|
58
|
+
// Re-throw the error with proper code to maintain compatibility with babel preset
|
59
|
+
// The preset expects MODULE_NOT_FOUND errors to handle missing core-js gracefully
|
60
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
61
|
+
throw error
|
62
|
+
}
|
63
|
+
// For other errors, warn and re-throw
|
64
|
+
console.warn(
|
65
|
+
`[SHAKAPACKER WARNING] Failed to get version for package ${packageName}: ${getErrorMessage(error)}`
|
66
|
+
)
|
67
|
+
throw error
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
const packageMajorVersion = (packageName: string): string => {
|
72
|
+
const match = packageFullVersion(packageName).match(/^\d+/)
|
73
|
+
return match ? match[0] : "0"
|
74
|
+
}
|
75
|
+
|
76
|
+
export {
|
77
|
+
isBoolean,
|
78
|
+
ensureTrailingSlash,
|
79
|
+
canProcess,
|
80
|
+
moduleExists,
|
81
|
+
loaderMatches,
|
82
|
+
packageFullVersion,
|
83
|
+
packageMajorVersion,
|
84
|
+
resolvedPath
|
85
|
+
}
|
@@ -2,7 +2,7 @@ const { runningWebpackDevServer } = require("../env")
|
|
2
2
|
const devServer = require("../dev_server")
|
3
3
|
|
4
4
|
// This logic is tied to lib/shakapacker/instance.rb
|
5
|
-
const inliningCss =
|
6
|
-
runningWebpackDevServer && devServer.hmr && devServer.inline_css !== false
|
5
|
+
const inliningCss: boolean =
|
6
|
+
runningWebpackDevServer && !!devServer.hmr && devServer.inline_css !== false
|
7
7
|
|
8
|
-
|
8
|
+
export = inliningCss
|
@@ -2,7 +2,7 @@
|
|
2
2
|
/* eslint import/no-dynamic-require: 0 */
|
3
3
|
const config = require("../config")
|
4
4
|
|
5
|
-
const requireOrError = (moduleName) => {
|
5
|
+
const requireOrError = (moduleName: string): any => {
|
6
6
|
try {
|
7
7
|
return require(moduleName)
|
8
8
|
} catch (error) {
|
@@ -12,4 +12,4 @@ const requireOrError = (moduleName) => {
|
|
12
12
|
}
|
13
13
|
}
|
14
14
|
|
15
|
-
|
15
|
+
export = { requireOrError }
|
@@ -0,0 +1,228 @@
|
|
1
|
+
import { Config, DevServerConfig, YamlConfig } from "../types"
|
2
|
+
|
3
|
+
// Cache for validated configs in production
|
4
|
+
const validatedConfigs = new WeakMap<object, boolean>()
|
5
|
+
|
6
|
+
// Only validate in development or when explicitly enabled
|
7
|
+
const shouldValidate = process.env.NODE_ENV !== 'production' || process.env.SHAKAPACKER_STRICT_VALIDATION === 'true'
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Type guard to validate Config object at runtime
|
11
|
+
* In production, caches results for performance unless SHAKAPACKER_STRICT_VALIDATION is set
|
12
|
+
*/
|
13
|
+
export function isValidConfig(obj: unknown): obj is Config {
|
14
|
+
if (typeof obj !== 'object' || obj === null) {
|
15
|
+
return false
|
16
|
+
}
|
17
|
+
|
18
|
+
// Quick return for production with cached results
|
19
|
+
if (!shouldValidate && validatedConfigs.has(obj as object)) {
|
20
|
+
return validatedConfigs.get(obj as object) as boolean
|
21
|
+
}
|
22
|
+
|
23
|
+
const config = obj as Record<string, unknown>
|
24
|
+
|
25
|
+
// Check required string fields
|
26
|
+
const requiredStringFields = [
|
27
|
+
'source_path',
|
28
|
+
'source_entry_path',
|
29
|
+
'public_root_path',
|
30
|
+
'public_output_path',
|
31
|
+
'cache_path',
|
32
|
+
'javascript_transpiler'
|
33
|
+
]
|
34
|
+
|
35
|
+
for (const field of requiredStringFields) {
|
36
|
+
if (typeof config[field] !== 'string') {
|
37
|
+
// Cache negative result in production
|
38
|
+
if (!shouldValidate) {
|
39
|
+
validatedConfigs.set(obj as object, false)
|
40
|
+
}
|
41
|
+
return false
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
// Check required boolean fields
|
46
|
+
const requiredBooleanFields = [
|
47
|
+
'nested_entries',
|
48
|
+
'css_extract_ignore_order_warnings',
|
49
|
+
'webpack_compile_output',
|
50
|
+
'shakapacker_precompile',
|
51
|
+
'cache_manifest',
|
52
|
+
'ensure_consistent_versioning',
|
53
|
+
'useContentHash',
|
54
|
+
'compile'
|
55
|
+
]
|
56
|
+
|
57
|
+
for (const field of requiredBooleanFields) {
|
58
|
+
if (typeof config[field] !== 'boolean') {
|
59
|
+
// Cache negative result in production
|
60
|
+
if (!shouldValidate) {
|
61
|
+
validatedConfigs.set(obj as object, false)
|
62
|
+
}
|
63
|
+
return false
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
// Check arrays
|
68
|
+
if (!Array.isArray(config.additional_paths)) {
|
69
|
+
// Cache negative result in production
|
70
|
+
if (!shouldValidate) {
|
71
|
+
validatedConfigs.set(obj as object, false)
|
72
|
+
}
|
73
|
+
return false
|
74
|
+
}
|
75
|
+
|
76
|
+
// Check optional fields
|
77
|
+
if (config.dev_server !== undefined && !isValidDevServerConfig(config.dev_server)) {
|
78
|
+
// Cache negative result in production
|
79
|
+
if (!shouldValidate) {
|
80
|
+
validatedConfigs.set(obj as object, false)
|
81
|
+
}
|
82
|
+
return false
|
83
|
+
}
|
84
|
+
|
85
|
+
if (config.integrity !== undefined) {
|
86
|
+
const integrity = config.integrity as Record<string, unknown>
|
87
|
+
if (typeof integrity.enabled !== 'boolean' ||
|
88
|
+
typeof integrity.cross_origin !== 'string') {
|
89
|
+
// Cache negative result in production
|
90
|
+
if (!shouldValidate) {
|
91
|
+
validatedConfigs.set(obj as object, false)
|
92
|
+
}
|
93
|
+
return false
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
const result = true
|
98
|
+
|
99
|
+
// Cache result in production
|
100
|
+
if (!shouldValidate) {
|
101
|
+
validatedConfigs.set(obj as object, result)
|
102
|
+
}
|
103
|
+
|
104
|
+
return result
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Type guard to validate DevServerConfig object at runtime
|
109
|
+
* In production, performs minimal validation for performance
|
110
|
+
*/
|
111
|
+
export function isValidDevServerConfig(obj: unknown): obj is DevServerConfig {
|
112
|
+
if (typeof obj !== 'object' || obj === null) {
|
113
|
+
return false
|
114
|
+
}
|
115
|
+
|
116
|
+
// In production, skip deep validation unless explicitly enabled
|
117
|
+
if (!shouldValidate) {
|
118
|
+
return true
|
119
|
+
}
|
120
|
+
|
121
|
+
const config = obj as Record<string, unknown>
|
122
|
+
|
123
|
+
// All fields are optional, just check types if present
|
124
|
+
if (config.hmr !== undefined &&
|
125
|
+
typeof config.hmr !== 'boolean' &&
|
126
|
+
config.hmr !== 'only') {
|
127
|
+
return false
|
128
|
+
}
|
129
|
+
|
130
|
+
if (config.port !== undefined &&
|
131
|
+
typeof config.port !== 'number' &&
|
132
|
+
typeof config.port !== 'string' &&
|
133
|
+
config.port !== 'auto') {
|
134
|
+
return false
|
135
|
+
}
|
136
|
+
|
137
|
+
return true
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* Type guard to validate YamlConfig structure
|
142
|
+
* In production, performs minimal validation for performance
|
143
|
+
*/
|
144
|
+
export function isValidYamlConfig(obj: unknown): obj is YamlConfig {
|
145
|
+
if (typeof obj !== 'object' || obj === null) {
|
146
|
+
return false
|
147
|
+
}
|
148
|
+
|
149
|
+
// In production, skip deep validation unless explicitly enabled
|
150
|
+
if (!shouldValidate) {
|
151
|
+
return true
|
152
|
+
}
|
153
|
+
|
154
|
+
const config = obj as Record<string, unknown>
|
155
|
+
|
156
|
+
// Each key should map to an object
|
157
|
+
for (const env of Object.keys(config)) {
|
158
|
+
if (typeof config[env] !== 'object' || config[env] === null) {
|
159
|
+
return false
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
return true
|
164
|
+
}
|
165
|
+
|
166
|
+
/**
|
167
|
+
* Validates partial config used for merging
|
168
|
+
* Ensures that if fields are present, they have the correct types
|
169
|
+
* In production, performs minimal validation for performance
|
170
|
+
*/
|
171
|
+
export function isPartialConfig(obj: unknown): obj is Partial<Config> {
|
172
|
+
if (typeof obj !== 'object' || obj === null) {
|
173
|
+
return false
|
174
|
+
}
|
175
|
+
|
176
|
+
// In production, skip deep validation unless explicitly enabled
|
177
|
+
if (!shouldValidate) {
|
178
|
+
return true
|
179
|
+
}
|
180
|
+
|
181
|
+
const config = obj as Record<string, unknown>
|
182
|
+
|
183
|
+
// Check string fields if present
|
184
|
+
const stringFields = [
|
185
|
+
'source_path', 'source_entry_path', 'public_root_path',
|
186
|
+
'public_output_path', 'cache_path', 'javascript_transpiler'
|
187
|
+
]
|
188
|
+
|
189
|
+
for (const field of stringFields) {
|
190
|
+
if (field in config && typeof config[field] !== 'string') {
|
191
|
+
return false
|
192
|
+
}
|
193
|
+
}
|
194
|
+
|
195
|
+
// Check boolean fields if present
|
196
|
+
const booleanFields = [
|
197
|
+
'nested_entries', 'css_extract_ignore_order_warnings',
|
198
|
+
'webpack_compile_output', 'shakapacker_precompile',
|
199
|
+
'cache_manifest', 'ensure_consistent_versioning'
|
200
|
+
]
|
201
|
+
|
202
|
+
for (const field of booleanFields) {
|
203
|
+
if (field in config && typeof config[field] !== 'boolean') {
|
204
|
+
return false
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
// Check arrays if present
|
209
|
+
if ('additional_paths' in config && !Array.isArray(config.additional_paths)) {
|
210
|
+
return false
|
211
|
+
}
|
212
|
+
|
213
|
+
return true
|
214
|
+
}
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Creates a validation error with helpful context
|
218
|
+
*/
|
219
|
+
export function createConfigValidationError(
|
220
|
+
configPath: string,
|
221
|
+
environment: string,
|
222
|
+
details?: string
|
223
|
+
): Error {
|
224
|
+
const message = `Invalid configuration in ${configPath} for environment '${environment}'`
|
225
|
+
return new Error(details ? `${message}: ${details}` : message)
|
226
|
+
}
|
227
|
+
|
228
|
+
|
@@ -5,7 +5,7 @@
|
|
5
5
|
const { moduleExists } = require("./helpers")
|
6
6
|
const { error } = require("./debug")
|
7
7
|
|
8
|
-
const validateRspackDependencies = () => {
|
8
|
+
const validateRspackDependencies = (): void => {
|
9
9
|
const requiredDependencies = ["@rspack/core", "rspack-manifest-plugin"]
|
10
10
|
|
11
11
|
const missingDependencies = requiredDependencies.filter(
|
@@ -28,7 +28,7 @@ const validateRspackDependencies = () => {
|
|
28
28
|
}
|
29
29
|
}
|
30
30
|
|
31
|
-
const validateWebpackDependencies = () => {
|
31
|
+
const validateWebpackDependencies = (): void => {
|
32
32
|
const requiredDependencies = [
|
33
33
|
"webpack",
|
34
34
|
"webpack-cli",
|
@@ -55,7 +55,7 @@ const validateWebpackDependencies = () => {
|
|
55
55
|
}
|
56
56
|
}
|
57
57
|
|
58
|
-
|
58
|
+
export = {
|
59
59
|
validateRspackDependencies,
|
60
60
|
validateWebpackDependencies
|
61
|
-
}
|
61
|
+
}
|