shakapacker 9.3.0.beta.7 → 9.3.1
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/.claude/commands/update-changelog.md +224 -0
- data/.github/actionlint-matcher.json +17 -0
- data/.github/workflows/dummy.yml +9 -0
- data/.github/workflows/generator.yml +13 -0
- data/.github/workflows/node.yml +83 -0
- data/.github/workflows/ruby.yml +11 -0
- data/.github/workflows/test-bundlers.yml +10 -0
- data/CHANGELOG.md +55 -111
- data/CLAUDE.md +6 -10
- data/CONTRIBUTING.md +57 -0
- data/Gemfile.lock +1 -1
- data/README.md +84 -8
- data/docs/api-reference.md +519 -0
- data/docs/configuration.md +38 -4
- data/docs/css-modules-export-mode.md +40 -6
- data/docs/rspack_migration_guide.md +238 -2
- data/docs/transpiler-migration.md +12 -9
- data/docs/troubleshooting.md +21 -21
- data/docs/using_swc_loader.md +13 -10
- data/docs/v9_upgrade.md +11 -2
- data/eslint.config.fast.js +128 -8
- data/eslint.config.js +89 -33
- data/knip.ts +8 -1
- data/lib/install/config/shakapacker.yml +20 -7
- data/lib/shakapacker/configuration.rb +274 -8
- data/lib/shakapacker/dev_server.rb +88 -1
- data/lib/shakapacker/dev_server_runner.rb +4 -0
- data/lib/shakapacker/doctor.rb +5 -5
- data/lib/shakapacker/instance.rb +85 -1
- data/lib/shakapacker/manifest.rb +85 -11
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker.rb +143 -3
- data/lib/tasks/shakapacker/doctor.rake +1 -1
- data/lib/tasks/shakapacker/export_bundler_config.rake +4 -4
- data/package/config.ts +2 -4
- data/package/configExporter/buildValidator.ts +53 -29
- data/package/configExporter/cli.ts +106 -76
- data/package/configExporter/configFile.ts +33 -26
- data/package/configExporter/types.ts +64 -0
- data/package/configExporter/yamlSerializer.ts +118 -43
- data/package/dev_server.ts +3 -2
- data/package/env.ts +2 -2
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +6 -6
- data/package/environments/base.ts +6 -6
- data/package/environments/development.ts +7 -9
- data/package/environments/production.ts +7 -8
- data/package/environments/test.ts +4 -2
- data/package/esbuild/index.ts +0 -2
- data/package/index.d.ts +1 -0
- data/package/index.d.ts.template +1 -0
- data/package/index.ts +28 -5
- data/package/loaders.d.ts +2 -2
- data/package/optimization/webpack.ts +29 -31
- data/package/plugins/rspack.ts +3 -1
- data/package/plugins/webpack.ts +5 -3
- data/package/rspack/index.ts +5 -4
- data/package/rules/file.ts +2 -1
- data/package/rules/jscommon.ts +1 -0
- data/package/rules/raw.ts +3 -1
- data/package/rules/rspack.ts +0 -2
- data/package/rules/sass.ts +0 -2
- data/package/rules/webpack.ts +0 -1
- data/package/swc/index.ts +0 -2
- data/package/types.ts +8 -11
- data/package/utils/debug.ts +0 -4
- data/package/utils/getStyleRule.ts +17 -9
- data/package/utils/helpers.ts +8 -4
- data/package/utils/pathValidation.ts +78 -18
- data/package/utils/requireOrError.ts +14 -5
- data/package/utils/typeGuards.ts +43 -46
- data/package/webpack-types.d.ts +2 -2
- data/package/webpackDevServerConfig.ts +5 -4
- data/package.json +2 -3
- data/test/package/configExporter/cli.test.js +440 -0
- data/test/package/configExporter/types.test.js +163 -0
- data/test/package/configExporter.test.js +264 -0
- data/test/package/transpiler-defaults.test.js +42 -0
- data/test/package/yamlSerializer.test.js +204 -0
- data/test/typescript/pathValidation.test.js +44 -0
- data/test/typescript/requireOrError.test.js +49 -0
- data/yarn.lock +0 -32
- metadata +14 -5
- data/.eslintrc.fast.js +0 -40
- data/.eslintrc.js +0 -84
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
* @module environments/production
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/* eslint global-require: 0 */
|
|
7
6
|
/* eslint import/no-dynamic-require: 0 */
|
|
8
7
|
|
|
9
|
-
const { resolve } = require("path")
|
|
10
|
-
const { merge } = require("webpack-merge")
|
|
11
|
-
const baseConfig = require("./base")
|
|
12
|
-
const { moduleExists } = require("../utils/helpers")
|
|
13
|
-
const config = require("../config")
|
|
14
8
|
import type {
|
|
15
9
|
Configuration as WebpackConfiguration,
|
|
16
10
|
WebpackPluginInstance
|
|
17
11
|
} from "webpack"
|
|
18
12
|
import type { CompressionPluginConstructor } from "./types"
|
|
13
|
+
import type { Config } from "../types"
|
|
14
|
+
|
|
15
|
+
const { resolve } = require("path")
|
|
16
|
+
const { merge } = require("webpack-merge")
|
|
17
|
+
const baseConfig = require("./base")
|
|
18
|
+
const { moduleExists } = require("../utils/helpers")
|
|
19
|
+
const config = require("../config") as Config
|
|
19
20
|
|
|
20
21
|
const optimizationPath = resolve(
|
|
21
22
|
__dirname,
|
|
@@ -27,7 +28,6 @@ const { getOptimization } = require(optimizationPath)
|
|
|
27
28
|
|
|
28
29
|
let CompressionPlugin: CompressionPluginConstructor | null = null
|
|
29
30
|
if (moduleExists("compression-webpack-plugin")) {
|
|
30
|
-
// eslint-disable-next-line global-require
|
|
31
31
|
CompressionPlugin = require("compression-webpack-plugin")
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -73,7 +73,6 @@ const productionConfig: Partial<WebpackConfiguration> = {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
if (config.useContentHash === false) {
|
|
76
|
-
// eslint-disable-next-line no-console
|
|
77
76
|
console.warn(`⚠️ WARNING
|
|
78
77
|
Setting 'useContentHash' to 'false' in the production environment (specified by NODE_ENV environment variable) is not allowed!
|
|
79
78
|
Content hashes get added to the filenames regardless of setting useContentHash in 'shakapacker.yml' to false.
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* @module environments/test
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { Configuration as WebpackConfiguration } from "webpack"
|
|
7
|
+
import type { Config } from "../types"
|
|
8
|
+
|
|
6
9
|
const { merge } = require("webpack-merge")
|
|
7
|
-
const config = require("../config")
|
|
10
|
+
const config = require("../config") as Config
|
|
8
11
|
const baseConfig = require("./base")
|
|
9
|
-
import type { Configuration as WebpackConfiguration } from "webpack"
|
|
10
12
|
|
|
11
13
|
interface TestConfig {
|
|
12
14
|
mode: "development" | "production" | "none"
|
data/package/esbuild/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint global-require: 0 */
|
|
2
1
|
/* eslint import/no-dynamic-require: 0 */
|
|
3
2
|
|
|
4
3
|
import { resolve } from "path"
|
|
@@ -21,7 +20,6 @@ const getLoaderExtension = (filename: string): string => {
|
|
|
21
20
|
const getCustomConfig = (): Partial<RuleSetRule> => {
|
|
22
21
|
const path = resolve("config", "esbuild.config.js")
|
|
23
22
|
if (existsSync(path)) {
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
23
|
return require(path)
|
|
26
24
|
}
|
|
27
25
|
return {}
|
data/package/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* When adding/modifying exports in index.ts, update this file accordingly.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
// @ts-expect-error: webpack is an optional peer dependency (using type-only import)
|
|
10
11
|
import type { Configuration, RuleSetRule } from "webpack"
|
|
11
12
|
import type { Config, DevServerConfig, Env } from "./types"
|
|
12
13
|
|
data/package/index.d.ts.template
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* When adding/modifying exports in index.ts, update this file accordingly.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
// @ts-expect-error: webpack is an optional peer dependency (using type-only import)
|
|
10
11
|
import type { Configuration, RuleSetRule } from "webpack"
|
|
11
12
|
import type { Config, DevServerConfig, Env } from "./types"
|
|
12
13
|
|
data/package/index.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
/* eslint global-require: 0 */
|
|
2
1
|
/* eslint import/no-dynamic-require: 0 */
|
|
3
2
|
|
|
4
3
|
import * as webpackMerge from "webpack-merge"
|
|
5
4
|
import { resolve } from "path"
|
|
6
5
|
import { existsSync } from "fs"
|
|
7
|
-
|
|
8
|
-
import type { Configuration } from "webpack"
|
|
6
|
+
import type { Configuration, RuleSetRule } from "webpack"
|
|
9
7
|
import config from "./config"
|
|
10
8
|
import baseConfig from "./environments/base"
|
|
11
9
|
import devServer from "./dev_server"
|
|
@@ -14,8 +12,16 @@ import { moduleExists, canProcess } from "./utils/helpers"
|
|
|
14
12
|
import inliningCss from "./utils/inliningCss"
|
|
15
13
|
|
|
16
14
|
const rulesPath = resolve(__dirname, "rules", `${config.assets_bundler}.js`)
|
|
17
|
-
|
|
15
|
+
/** Array of webpack/rspack loader rules */
|
|
16
|
+
const rules = require(rulesPath) as RuleSetRule[]
|
|
18
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Generate webpack configuration with optional custom config.
|
|
20
|
+
*
|
|
21
|
+
* @param extraConfig - Optional webpack configuration to merge with base config
|
|
22
|
+
* @returns Final webpack configuration
|
|
23
|
+
* @throws {Error} If more than one argument is provided
|
|
24
|
+
*/
|
|
19
25
|
const generateWebpackConfig = (
|
|
20
26
|
extraConfig: Configuration = {},
|
|
21
27
|
...extraArgs: unknown[]
|
|
@@ -41,15 +47,32 @@ const generateWebpackConfig = (
|
|
|
41
47
|
return webpackMerge.merge({}, environmentConfig, extraConfig)
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The Shakapacker module exports.
|
|
52
|
+
* This object is exported via CommonJS `export =`.
|
|
53
|
+
*
|
|
54
|
+
* NOTE: This pattern is temporary and will be replaced with named exports
|
|
55
|
+
* once issue #641 is resolved.
|
|
56
|
+
*/
|
|
44
57
|
export = {
|
|
45
|
-
|
|
58
|
+
/** Shakapacker configuration from shakapacker.yml */
|
|
59
|
+
config,
|
|
60
|
+
/** Development server configuration */
|
|
46
61
|
devServer,
|
|
62
|
+
/** Generate webpack configuration with optional custom config */
|
|
47
63
|
generateWebpackConfig,
|
|
64
|
+
/** Base webpack/rspack configuration */
|
|
48
65
|
baseConfig,
|
|
66
|
+
/** Environment configuration (railsEnv, nodeEnv, etc.) */
|
|
49
67
|
env,
|
|
68
|
+
/** Array of webpack/rspack loader rules */
|
|
50
69
|
rules,
|
|
70
|
+
/** Check if a module exists in node_modules */
|
|
51
71
|
moduleExists,
|
|
72
|
+
/** Process a file if a specific loader is available */
|
|
52
73
|
canProcess,
|
|
74
|
+
/** Whether CSS should be inlined (dev server with HMR) */
|
|
53
75
|
inliningCss,
|
|
76
|
+
/** webpack-merge functions (merge, mergeWithCustomize, mergeWithRules, unique) */
|
|
54
77
|
...webpackMerge
|
|
55
78
|
}
|
data/package/loaders.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// @ts-
|
|
1
|
+
// @ts-expect-error: webpack is an optional peer dependency (using type-only import)
|
|
2
2
|
import type { LoaderDefinitionFunction } from "webpack"
|
|
3
3
|
|
|
4
4
|
export interface ShakapackerLoaderOptions {
|
|
5
|
-
[key: string]:
|
|
5
|
+
[key: string]: unknown
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export interface ShakapackerLoader {
|
|
@@ -19,38 +19,36 @@ interface OptimizationConfig {
|
|
|
19
19
|
minimizer: unknown[]
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const getOptimization = (): OptimizationConfig => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
parse
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ascii_only: true
|
|
48
|
-
}
|
|
22
|
+
const getOptimization = (): OptimizationConfig => ({
|
|
23
|
+
minimizer: [
|
|
24
|
+
tryCssMinimizer(),
|
|
25
|
+
new TerserPlugin({
|
|
26
|
+
// SHAKAPACKER_PARALLEL env var: number of parallel workers, or true for auto (os.cpus().length - 1)
|
|
27
|
+
// If not set or invalid, defaults to true (automatic parallelization)
|
|
28
|
+
parallel: process.env.SHAKAPACKER_PARALLEL
|
|
29
|
+
? Number.parseInt(process.env.SHAKAPACKER_PARALLEL, 10) || true
|
|
30
|
+
: true,
|
|
31
|
+
terserOptions: {
|
|
32
|
+
parse: {
|
|
33
|
+
// Let terser parse ecma 8 code but always output
|
|
34
|
+
// ES5 compliant code for older browsers
|
|
35
|
+
ecma: 8
|
|
36
|
+
},
|
|
37
|
+
compress: {
|
|
38
|
+
ecma: 5,
|
|
39
|
+
warnings: false,
|
|
40
|
+
comparisons: false
|
|
41
|
+
},
|
|
42
|
+
mangle: { safari10: true },
|
|
43
|
+
output: {
|
|
44
|
+
ecma: 5,
|
|
45
|
+
comments: false,
|
|
46
|
+
ascii_only: true
|
|
49
47
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
].filter(Boolean)
|
|
51
|
+
})
|
|
54
52
|
|
|
55
53
|
export = {
|
|
56
54
|
getOptimization
|
data/package/plugins/rspack.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { Config } from "../types"
|
|
2
|
+
|
|
1
3
|
const { requireOrError } = require("../utils/requireOrError")
|
|
2
4
|
|
|
3
5
|
const { RspackManifestPlugin } = requireOrError("rspack-manifest-plugin")
|
|
4
6
|
const rspack = requireOrError("@rspack/core")
|
|
5
|
-
const config = require("../config")
|
|
7
|
+
const config = require("../config") as Config
|
|
6
8
|
const { isProduction } = require("../env")
|
|
7
9
|
const { moduleExists } = require("../utils/helpers")
|
|
8
10
|
|
data/package/plugins/webpack.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { Config } from "../types"
|
|
2
|
+
|
|
1
3
|
const { requireOrError } = require("../utils/requireOrError")
|
|
2
4
|
// TODO: Change to `const { WebpackAssetsManifest }` when dropping 'webpack-assets-manifest < 6.0.0' (Node >=20.10.0) support
|
|
3
5
|
const WebpackAssetsManifest = requireOrError("webpack-assets-manifest")
|
|
4
6
|
const webpack = requireOrError("webpack")
|
|
5
|
-
const config = require("../config")
|
|
6
|
-
const { isProduction
|
|
7
|
+
const config = require("../config") as Config
|
|
8
|
+
const { isProduction } = require("../env")
|
|
7
9
|
const { moduleExists } = require("../utils/helpers")
|
|
8
10
|
|
|
9
11
|
const getPlugins = (): unknown[] => {
|
|
@@ -15,7 +17,7 @@ const getPlugins = (): unknown[] => {
|
|
|
15
17
|
const plugins = [
|
|
16
18
|
new webpack.EnvironmentPlugin(process.env),
|
|
17
19
|
new WebpackAssetsManifestConstructor({
|
|
18
|
-
merge:
|
|
20
|
+
merge: true,
|
|
19
21
|
entrypoints: true,
|
|
20
22
|
writeToDisk: true,
|
|
21
23
|
output: config.manifestPath,
|
data/package/rspack/index.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
/* eslint global-require: 0 */
|
|
2
1
|
/* eslint import/no-dynamic-require: 0 */
|
|
3
2
|
|
|
4
3
|
// Mixed require/import syntax:
|
|
5
4
|
// - Using require() for compiled JS modules that may not have proper ES module exports
|
|
6
5
|
// - Using import for type-only imports and Node.js built-in modules
|
|
7
|
-
const webpackMerge = require("webpack-merge")
|
|
8
6
|
import { resolve } from "path"
|
|
9
7
|
import { existsSync } from "fs"
|
|
10
8
|
import type { RspackConfigWithDevServer } from "../environments/types"
|
|
11
|
-
|
|
9
|
+
import type { Config } from "../types"
|
|
10
|
+
|
|
11
|
+
const webpackMerge = require("webpack-merge")
|
|
12
|
+
const config = require("../config") as Config
|
|
12
13
|
const baseConfig = require("../environments/base")
|
|
13
14
|
const devServer = require("../dev_server")
|
|
14
15
|
const env = require("../env")
|
|
@@ -35,7 +36,7 @@ const generateRspackConfig = (
|
|
|
35
36
|
|
|
36
37
|
const { nodeEnv } = env
|
|
37
38
|
const path = resolve(__dirname, "../environments", `${nodeEnv}.js`)
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
const environmentConfig = existsSync(path) ? require(path) : baseConfig
|
|
40
41
|
|
|
41
42
|
// Create base rspack config
|
data/package/rules/file.ts
CHANGED
data/package/rules/jscommon.ts
CHANGED
data/package/rules/raw.ts
CHANGED
data/package/rules/rspack.ts
CHANGED
data/package/rules/sass.ts
CHANGED
data/package/rules/webpack.ts
CHANGED
data/package/swc/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint global-require: 0 */
|
|
2
1
|
/* eslint import/no-dynamic-require: 0 */
|
|
3
2
|
|
|
4
3
|
import { resolve } from "path"
|
|
@@ -18,7 +17,6 @@ const isTypescriptFile = (filename: string): boolean =>
|
|
|
18
17
|
const getCustomConfig = (): Partial<RuleSetRule> => {
|
|
19
18
|
const path = resolve("config", "swc.config.js")
|
|
20
19
|
if (existsSync(path)) {
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
20
|
return require(path)
|
|
23
21
|
}
|
|
24
22
|
return {}
|
data/package/types.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface Config {
|
|
|
15
15
|
source_entry_path: string
|
|
16
16
|
nested_entries: boolean
|
|
17
17
|
css_extract_ignore_order_warnings: boolean
|
|
18
|
+
css_modules_export_mode?: "named" | "default"
|
|
18
19
|
public_root_path: string
|
|
19
20
|
public_output_path: string
|
|
20
21
|
private_output_path?: string
|
|
@@ -53,8 +54,6 @@ export interface Env {
|
|
|
53
54
|
type Header =
|
|
54
55
|
| Array<{ key: string; value: string }>
|
|
55
56
|
| Record<string, string | string[]>
|
|
56
|
-
type ServerType = "http" | "https" | "spdy"
|
|
57
|
-
type WebSocketType = "sockjs" | "ws"
|
|
58
57
|
|
|
59
58
|
/**
|
|
60
59
|
* This has the same keys and behavior as https://webpack.js.org/configuration/dev-server/ except:
|
|
@@ -63,7 +62,7 @@ type WebSocketType = "sockjs" | "ws"
|
|
|
63
62
|
* @see {import('webpack-dev-server').Configuration}
|
|
64
63
|
*/
|
|
65
64
|
export interface DevServerConfig {
|
|
66
|
-
allowed_hosts?:
|
|
65
|
+
allowed_hosts?: string | string[]
|
|
67
66
|
bonjour?: boolean | Record<string, unknown> // bonjour.BonjourOptions
|
|
68
67
|
client?: Record<string, unknown> // Client
|
|
69
68
|
compress?: boolean
|
|
@@ -71,7 +70,7 @@ export interface DevServerConfig {
|
|
|
71
70
|
headers?: Header | (() => Header)
|
|
72
71
|
history_api_fallback?: boolean | Record<string, unknown> // HistoryApiFallbackOptions
|
|
73
72
|
hmr?: "only" | boolean
|
|
74
|
-
host?:
|
|
73
|
+
host?: string
|
|
75
74
|
http2?: boolean
|
|
76
75
|
https?: boolean | https.ServerOptions
|
|
77
76
|
ipc?: boolean | string
|
|
@@ -85,23 +84,21 @@ export interface DevServerConfig {
|
|
|
85
84
|
| string[]
|
|
86
85
|
| Record<string, unknown>
|
|
87
86
|
| Record<string, unknown>[]
|
|
88
|
-
port?:
|
|
87
|
+
port?: string | number
|
|
89
88
|
proxy?: unknown // ProxyConfigMap | ProxyConfigArray
|
|
90
89
|
setup_exit_signals?: boolean
|
|
91
|
-
static?:
|
|
92
|
-
watch_files?:
|
|
90
|
+
static?: unknown // Static | Array<string | Static>
|
|
91
|
+
watch_files?: unknown // WatchFiles | Array<WatchFiles | string>
|
|
93
92
|
web_socket_server?:
|
|
94
93
|
| string
|
|
95
94
|
| boolean
|
|
96
|
-
| WebSocketType
|
|
97
95
|
| {
|
|
98
|
-
type?: string | boolean
|
|
96
|
+
type?: string | boolean
|
|
99
97
|
options?: Record<string, unknown>
|
|
100
98
|
}
|
|
101
99
|
server?:
|
|
102
100
|
| string
|
|
103
101
|
| boolean
|
|
104
|
-
|
|
|
105
|
-
| { type?: string | boolean | ServerType; options?: https.ServerOptions }
|
|
102
|
+
| { type?: string | boolean; options?: https.ServerOptions }
|
|
106
103
|
[otherWebpackDevServerConfigKey: string]: unknown
|
|
107
104
|
}
|
data/package/utils/debug.ts
CHANGED
|
@@ -18,24 +18,20 @@ const isDebugMode = (): boolean => {
|
|
|
18
18
|
|
|
19
19
|
const debug = (message: string, ...args: unknown[]): void => {
|
|
20
20
|
if (isDebugMode()) {
|
|
21
|
-
// eslint-disable-next-line no-console
|
|
22
21
|
console.log(`[Shakapacker] ${message}`, ...args)
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
const warn = (message: string, ...args: unknown[]): void => {
|
|
27
|
-
// eslint-disable-next-line no-console
|
|
28
26
|
console.warn(`[Shakapacker] WARNING: ${message}`, ...args)
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
const error = (message: string, ...args: unknown[]): void => {
|
|
32
|
-
// eslint-disable-next-line no-console
|
|
33
30
|
console.error(`[Shakapacker] ERROR: ${message}`, ...args)
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
const info = (message: string, ...args: unknown[]): void => {
|
|
37
34
|
if (isDebugMode()) {
|
|
38
|
-
// eslint-disable-next-line no-console
|
|
39
35
|
console.info(`[Shakapacker] INFO: ${message}`, ...args)
|
|
40
36
|
}
|
|
41
37
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Config } from "../types"
|
|
2
|
+
|
|
2
3
|
const { canProcess, moduleExists } = require("./helpers")
|
|
3
4
|
const { requireOrError } = require("./requireOrError")
|
|
4
|
-
const config = require("../config")
|
|
5
|
+
const config = require("../config") as Config
|
|
5
6
|
const inliningCss = require("./inliningCss")
|
|
6
7
|
|
|
7
8
|
interface StyleRule {
|
|
8
9
|
test: RegExp
|
|
9
|
-
use:
|
|
10
|
+
use: unknown[]
|
|
10
11
|
type?: string
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const getStyleRule = (
|
|
14
15
|
test: RegExp,
|
|
15
|
-
preprocessors:
|
|
16
|
+
preprocessors: unknown[] = []
|
|
16
17
|
): StyleRule | null => {
|
|
17
18
|
if (moduleExists("css-loader")) {
|
|
18
19
|
const tryPostcss = () =>
|
|
@@ -28,6 +29,11 @@ const getStyleRule = (
|
|
|
28
29
|
? requireOrError("@rspack/core").CssExtractRspackPlugin.loader
|
|
29
30
|
: requireOrError("mini-css-extract-plugin").loader
|
|
30
31
|
|
|
32
|
+
// Determine CSS Modules export mode based on configuration
|
|
33
|
+
// 'named' (default): Use named exports with camelCaseOnly (v9 behavior)
|
|
34
|
+
// 'default': Use default exports with camelCase (v8 behavior)
|
|
35
|
+
const useNamedExports = config.css_modules_export_mode !== "default"
|
|
36
|
+
|
|
31
37
|
const use = [
|
|
32
38
|
inliningCss ? "style-loader" : extractionPlugin,
|
|
33
39
|
{
|
|
@@ -37,11 +43,13 @@ const getStyleRule = (
|
|
|
37
43
|
importLoaders: 2,
|
|
38
44
|
modules: {
|
|
39
45
|
auto: true,
|
|
40
|
-
// v9
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
namedExport:
|
|
44
|
-
exportLocalsConvention:
|
|
46
|
+
// Use named exports for v9 (default), or default exports for v8 compatibility
|
|
47
|
+
namedExport: useNamedExports,
|
|
48
|
+
// 'camelCaseOnly' with namedExport: true (v9 default)
|
|
49
|
+
// 'camelCase' with namedExport: false (v8 behavior - exports both original and camelCase)
|
|
50
|
+
exportLocalsConvention: useNamedExports
|
|
51
|
+
? "camelCaseOnly"
|
|
52
|
+
: "camelCase"
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
55
|
},
|
data/package/utils/helpers.ts
CHANGED
|
@@ -38,6 +38,11 @@ const loaderMatches = <T = unknown>(
|
|
|
38
38
|
loaderToCheck: string,
|
|
39
39
|
fn: () => T
|
|
40
40
|
): T | null => {
|
|
41
|
+
// If transpiler is set to 'none', skip all transpiler rules (for custom webpack configs)
|
|
42
|
+
if (configLoader === "none") {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
if (configLoader !== loaderToCheck) {
|
|
42
47
|
return null
|
|
43
48
|
}
|
|
@@ -59,15 +64,14 @@ const loaderMatches = <T = unknown>(
|
|
|
59
64
|
|
|
60
65
|
const packageFullVersion = (packageName: string): string => {
|
|
61
66
|
try {
|
|
62
|
-
// eslint-disable-next-line import/no-dynamic-require
|
|
63
67
|
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
|
64
|
-
// eslint-disable-next-line import/no-dynamic-require
|
|
68
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
65
69
|
const packageJson = require(packageJsonPath) as { version: string }
|
|
66
70
|
return packageJson.version
|
|
67
|
-
} catch (error:
|
|
71
|
+
} catch (error: unknown) {
|
|
68
72
|
// Re-throw the error with proper code to maintain compatibility with babel preset
|
|
69
73
|
// The preset expects MODULE_NOT_FOUND errors to handle missing core-js gracefully
|
|
70
|
-
if (error.code === "MODULE_NOT_FOUND") {
|
|
74
|
+
if ((error as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") {
|
|
71
75
|
throw error
|
|
72
76
|
}
|
|
73
77
|
// For other errors, warn and re-throw
|
|
@@ -28,26 +28,87 @@ export function isPathTraversalSafe(inputPath: string): boolean {
|
|
|
28
28
|
/**
|
|
29
29
|
* Resolves and validates a path within a base directory
|
|
30
30
|
* Prevents directory traversal attacks by ensuring the resolved path
|
|
31
|
-
* stays within the base directory
|
|
31
|
+
* stays within the base directory.
|
|
32
|
+
* Also resolves symlinks to prevent symlink-based path traversal attacks.
|
|
33
|
+
*
|
|
34
|
+
* @param basePath - The base directory to validate against
|
|
35
|
+
* @param userPath - The user-provided path to validate
|
|
36
|
+
* @param resolveSymlinks - Whether to resolve symlinks (default: true for security)
|
|
37
|
+
* @returns The validated absolute path
|
|
38
|
+
* @throws Error if path is outside base directory
|
|
32
39
|
*/
|
|
33
|
-
export function safeResolvePath(
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
export function safeResolvePath(
|
|
41
|
+
basePath: string,
|
|
42
|
+
userPath: string,
|
|
43
|
+
resolveSymlinks = true
|
|
44
|
+
): string {
|
|
45
|
+
// Resolve the base path through symlinks if enabled
|
|
46
|
+
let normalizedBase: string
|
|
47
|
+
try {
|
|
48
|
+
normalizedBase = resolveSymlinks
|
|
49
|
+
? fs.realpathSync(basePath)
|
|
50
|
+
: path.resolve(basePath)
|
|
51
|
+
} catch (error: unknown) {
|
|
52
|
+
// If basePath doesn't exist (ENOENT), fall back to path.resolve
|
|
53
|
+
// Rethrow other errors (e.g., permission issues) as they indicate real problems
|
|
54
|
+
const nodeError = error as NodeJS.ErrnoException
|
|
55
|
+
if (nodeError?.code === "ENOENT") {
|
|
56
|
+
normalizedBase = path.resolve(basePath)
|
|
57
|
+
} else {
|
|
58
|
+
throw error
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// For paths that may not exist yet, validate the parent directory
|
|
63
|
+
const absolutePath = path.resolve(basePath, userPath)
|
|
64
|
+
const parentDir = path.dirname(absolutePath)
|
|
65
|
+
const fileName = path.basename(absolutePath)
|
|
66
|
+
|
|
67
|
+
// Resolve parent directory through symlinks if it exists and symlink resolution is enabled
|
|
68
|
+
let resolvedParent: string
|
|
69
|
+
try {
|
|
70
|
+
resolvedParent = resolveSymlinks
|
|
71
|
+
? fs.realpathSync(parentDir)
|
|
72
|
+
: path.resolve(parentDir)
|
|
73
|
+
} catch (error: unknown) {
|
|
74
|
+
// If parent doesn't exist (ENOENT), validate the absolute path as-is
|
|
75
|
+
// Rethrow other errors (e.g., permission issues) as they indicate real problems
|
|
76
|
+
const nodeError = error as NodeJS.ErrnoException
|
|
77
|
+
if (nodeError?.code === "ENOENT") {
|
|
78
|
+
if (
|
|
79
|
+
!absolutePath.startsWith(normalizedBase + path.sep) &&
|
|
80
|
+
absolutePath !== normalizedBase
|
|
81
|
+
) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`[SHAKAPACKER SECURITY] Path traversal attempt detected.\n` +
|
|
84
|
+
`Requested path would resolve outside of allowed directory.\n` +
|
|
85
|
+
`Base: ${normalizedBase}\n` +
|
|
86
|
+
`Attempted: ${userPath}\n` +
|
|
87
|
+
`Resolved to: ${absolutePath}`
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
return absolutePath
|
|
91
|
+
}
|
|
92
|
+
throw error
|
|
93
|
+
}
|
|
36
94
|
|
|
37
|
-
//
|
|
38
|
-
const resolved = path.resolve(
|
|
95
|
+
// Reconstruct the full path with the resolved (symlink-free) parent
|
|
96
|
+
const resolved = path.resolve(resolvedParent, fileName)
|
|
39
97
|
|
|
40
98
|
// Ensure the resolved path is within the base directory
|
|
41
99
|
if (
|
|
42
100
|
!resolved.startsWith(normalizedBase + path.sep) &&
|
|
43
101
|
resolved !== normalizedBase
|
|
44
102
|
) {
|
|
103
|
+
const symlinkNote = resolveSymlinks
|
|
104
|
+
? ` (symlink-resolved from ${userPath})`
|
|
105
|
+
: ""
|
|
45
106
|
throw new Error(
|
|
46
107
|
`[SHAKAPACKER SECURITY] Path traversal attempt detected.\n` +
|
|
47
108
|
`Requested path would resolve outside of allowed directory.\n` +
|
|
48
109
|
`Base: ${normalizedBase}\n` +
|
|
49
110
|
`Attempted: ${userPath}\n` +
|
|
50
|
-
`Resolved to: ${resolved}`
|
|
111
|
+
`Resolved to: ${resolved}${symlinkNote}`
|
|
51
112
|
)
|
|
52
113
|
}
|
|
53
114
|
|
|
@@ -77,17 +138,16 @@ export function validatePaths(paths: string[], basePath: string): string[] {
|
|
|
77
138
|
console.warn(
|
|
78
139
|
`[SHAKAPACKER WARNING] Skipping potentially unsafe path: ${userPath}`
|
|
79
140
|
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
141
|
+
} else {
|
|
142
|
+
try {
|
|
143
|
+
const safePath = safeResolvePath(basePath, userPath)
|
|
144
|
+
validatedPaths.push(safePath)
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.warn(
|
|
147
|
+
`[SHAKAPACKER WARNING] Invalid path configuration: ${userPath}\n` +
|
|
148
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`
|
|
149
|
+
)
|
|
150
|
+
}
|
|
91
151
|
}
|
|
92
152
|
}
|
|
93
153
|
|