shakapacker 8.4.0 → 9.7.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.
- checksums.yaml +4 -4
- data/.claude/commands/address-review.md +206 -0
- data/.claude/commands/update-changelog.md +354 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
- data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
- data/.github/STATUS.md +1 -0
- data/.github/actionlint-matcher.json +17 -0
- data/.github/workflows/claude-code-review.yml +45 -0
- data/.github/workflows/claude.yml +55 -0
- data/.github/workflows/dummy.yml +18 -5
- data/.github/workflows/eslint-validation.yml +46 -0
- data/.github/workflows/generator.yml +38 -22
- data/.github/workflows/node.yml +116 -2
- data/.github/workflows/ruby.yml +57 -15
- data/.github/workflows/test-bundlers.yml +180 -0
- data/.gitignore +27 -0
- data/.husky/pre-commit +2 -0
- data/.npmignore +56 -0
- data/.prettierignore +7 -0
- data/.rubocop.yml +2 -0
- data/.yalcignore +26 -0
- data/CHANGELOG.md +487 -19
- data/CLAUDE.md +63 -0
- data/CONTRIBUTING.md +268 -21
- data/ESLINT_TECHNICAL_DEBT.md +165 -0
- data/README.md +497 -137
- data/Rakefile +44 -4
- data/TODO.md +58 -0
- data/TODO_v9.md +97 -0
- data/bin/conductor-exec +24 -0
- data/bin/shakapacker-config +11 -0
- data/conductor-setup.sh +147 -0
- data/conductor.json +9 -0
- data/docs/api-reference.md +519 -0
- data/docs/cdn_setup.md +384 -0
- data/docs/common-upgrades.md +695 -0
- data/docs/configuration.md +845 -0
- data/docs/css-modules-export-mode.md +566 -0
- data/docs/customizing_babel_config.md +16 -16
- data/docs/deployment.md +78 -7
- data/docs/developing_shakapacker.md +6 -0
- data/docs/early_hints.md +433 -0
- data/docs/early_hints_manual_api.md +454 -0
- data/docs/feature_testing.md +492 -0
- data/docs/node_package_api.md +70 -0
- data/docs/optional-peer-dependencies.md +203 -0
- data/docs/peer-dependencies.md +71 -0
- data/docs/precompile_hook.md +486 -0
- data/docs/preventing_fouc.md +132 -0
- data/docs/react.md +58 -48
- data/docs/releasing.md +288 -0
- data/docs/rspack.md +218 -0
- data/docs/rspack_migration_guide.md +862 -0
- data/docs/sprockets.md +1 -0
- data/docs/style_loader_vs_mini_css.md +12 -12
- data/docs/subresource_integrity.md +13 -7
- data/docs/transpiler-migration.md +212 -0
- data/docs/transpiler-performance.md +200 -0
- data/docs/troubleshooting.md +272 -24
- data/docs/typescript-migration.md +388 -0
- data/docs/typescript.md +103 -0
- data/docs/using_esbuild_loader.md +12 -12
- data/docs/using_swc_loader.md +121 -16
- data/docs/v6_upgrade.md +42 -19
- data/docs/v7_upgrade.md +8 -6
- data/docs/v8_upgrade.md +13 -12
- data/docs/v9_upgrade.md +616 -0
- data/eslint.config.fast.js +254 -0
- data/eslint.config.js +309 -0
- data/jest.config.js +8 -1
- data/knip.ts +61 -0
- data/lib/install/bin/shakapacker +4 -6
- data/lib/install/bin/shakapacker-config +11 -0
- data/lib/install/bin/shakapacker-dev-server +1 -1
- data/lib/install/binstubs.rb +6 -2
- data/lib/install/config/rspack/rspack.config.js +6 -0
- data/lib/install/config/rspack/rspack.config.ts +7 -0
- data/lib/install/config/shakapacker.yml +75 -12
- data/lib/install/config/webpack/webpack.config.ts +7 -0
- data/lib/install/package.json +38 -0
- data/lib/install/template.rb +207 -45
- data/lib/shakapacker/build_config_loader.rb +147 -0
- data/lib/shakapacker/bundler_switcher.rb +415 -0
- data/lib/shakapacker/compiler.rb +87 -0
- data/lib/shakapacker/configuration.rb +475 -6
- data/lib/shakapacker/dev_server.rb +88 -1
- data/lib/shakapacker/dev_server_runner.rb +240 -6
- data/lib/shakapacker/doctor.rb +1191 -0
- data/lib/shakapacker/env.rb +19 -3
- data/lib/shakapacker/helper.rb +411 -14
- data/lib/shakapacker/install/env.rb +33 -0
- data/lib/shakapacker/instance.rb +93 -4
- data/lib/shakapacker/manifest.rb +167 -30
- data/lib/shakapacker/railtie.rb +4 -0
- data/lib/shakapacker/rspack_runner.rb +19 -0
- data/lib/shakapacker/runner.rb +668 -9
- data/lib/shakapacker/swc_migrator.rb +384 -0
- data/lib/shakapacker/utils/manager.rb +2 -0
- data/lib/shakapacker/utils/version_syntax_converter.rb +1 -1
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/version_checker.rb +1 -1
- data/lib/shakapacker/webpack_runner.rb +4 -42
- data/lib/shakapacker.rb +159 -1
- data/lib/tasks/shakapacker/binstubs.rake +4 -2
- data/lib/tasks/shakapacker/check_binstubs.rake +2 -2
- data/lib/tasks/shakapacker/doctor.rake +48 -0
- data/lib/tasks/shakapacker/export_bundler_config.rake +68 -0
- data/lib/tasks/shakapacker/install.rake +16 -4
- data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
- data/lib/tasks/shakapacker/switch_bundler.rake +72 -0
- data/lib/tasks/shakapacker.rake +2 -0
- data/package/.npmignore +4 -0
- data/package/babel/preset.ts +59 -0
- data/package/config.ts +189 -0
- data/package/configExporter/buildValidator.ts +906 -0
- data/package/configExporter/cli.ts +1748 -0
- data/package/configExporter/configDocs.ts +102 -0
- data/package/configExporter/configFile.ts +663 -0
- data/package/configExporter/fileWriter.ts +112 -0
- data/package/configExporter/index.ts +15 -0
- data/package/configExporter/types.ts +159 -0
- data/package/configExporter/yamlSerializer.ts +391 -0
- data/package/dev_server.ts +27 -0
- data/package/env.ts +92 -0
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +36 -0
- data/package/environments/base.ts +147 -0
- data/package/environments/development.ts +88 -0
- data/package/environments/production.ts +82 -0
- data/package/environments/test.ts +55 -0
- data/package/environments/types.ts +98 -0
- data/package/esbuild/index.ts +40 -0
- data/package/index.d.ts +68 -93
- data/package/index.d.ts.template +72 -0
- data/package/index.ts +104 -0
- data/package/loaders.d.ts +28 -0
- data/package/optimization/rspack.ts +36 -0
- data/package/optimization/webpack.ts +55 -0
- data/package/plugins/envFilter.ts +82 -0
- data/package/plugins/rspack.ts +119 -0
- data/package/plugins/webpack.ts +82 -0
- data/package/rspack/index.ts +91 -0
- data/package/rules/{babel.js → babel.ts} +2 -2
- data/package/rules/{coffee.js → coffee.ts} +1 -1
- data/package/rules/css.ts +3 -0
- data/package/rules/{erb.js → erb.ts} +1 -1
- data/package/rules/esbuild.ts +10 -0
- data/package/rules/file.ts +41 -0
- data/package/rules/{jscommon.js → jscommon.ts} +5 -4
- data/package/rules/{less.js → less.ts} +4 -4
- data/package/rules/raw.ts +28 -0
- data/package/rules/rspack.ts +174 -0
- data/package/rules/sass.ts +21 -0
- data/package/rules/{stylus.js → stylus.ts} +4 -8
- data/package/rules/swc.ts +10 -0
- data/package/rules/{index.js → webpack.ts} +1 -2
- data/package/swc/index.ts +54 -0
- data/package/types/README.md +90 -0
- data/package/types/index.ts +69 -0
- data/package/types.ts +105 -0
- data/package/utils/bundlerUtils.ts +232 -0
- data/package/utils/configPath.ts +6 -0
- data/package/utils/debug.ts +45 -0
- data/package/utils/defaultConfigPath.ts +7 -0
- data/package/utils/ensureManifestExists.ts +17 -0
- data/package/utils/errorCodes.ts +249 -0
- data/package/utils/errorHelpers.ts +152 -0
- data/package/utils/getStyleRule.ts +75 -0
- data/package/utils/helpers.ts +99 -0
- data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
- data/package/utils/pathValidation.ts +207 -0
- data/package/utils/requireOrError.ts +24 -0
- data/package/utils/snakeToCamelCase.ts +5 -0
- data/package/utils/typeGuards.ts +388 -0
- data/package/utils/validateDependencies.ts +61 -0
- data/package/webpack-types.d.ts +33 -0
- data/package/webpackDevServerConfig.ts +130 -0
- data/package.json +157 -18
- data/scripts/remove-use-strict.js +44 -0
- data/scripts/type-check-no-emit.js +27 -0
- data/shakapacker.gemspec +4 -2
- data/sig/shakapacker/commands.rbs +35 -0
- data/sig/shakapacker/compiler.rbs +65 -0
- data/sig/shakapacker/compiler_strategy.rbs +41 -0
- data/sig/shakapacker/configuration.rbs +140 -0
- data/sig/shakapacker/dev_server.rbs +56 -0
- data/sig/shakapacker/env.rbs +25 -0
- data/sig/shakapacker/helper.rbs +98 -0
- data/sig/shakapacker/instance.rbs +46 -0
- data/sig/shakapacker/manifest.rbs +69 -0
- data/sig/shakapacker/version.rbs +4 -0
- data/sig/shakapacker.rbs +66 -0
- data/test/configExporter/buildValidator.test.js +1295 -0
- data/test/configExporter/configFile.test.js +393 -0
- data/test/configExporter/integration.test.js +262 -0
- data/test/helpers.js +1 -1
- data/test/package/bundlerUtils.rspack.test.js +145 -0
- data/test/package/bundlerUtils.test.js +97 -0
- data/test/package/config.test.js +14 -0
- data/test/package/configExporter/cli.test.js +440 -0
- data/test/package/configExporter/types.test.js +163 -0
- data/test/package/configExporter.test.js +491 -0
- data/test/package/env.test.js +42 -7
- data/test/package/environments/base.test.js +14 -4
- data/test/package/helpers.test.js +2 -2
- data/test/package/plugins/envFiltering.test.js +453 -0
- data/test/package/plugins/webpackSubresourceIntegrity.test.js +89 -0
- data/test/package/rspack/index.test.js +293 -0
- data/test/package/rspack/optimization.test.js +86 -0
- data/test/package/rspack/plugins.test.js +185 -0
- data/test/package/rspack/rules.test.js +229 -0
- data/test/package/rules/babel.test.js +65 -38
- data/test/package/rules/esbuild.test.js +13 -4
- data/test/package/rules/file.test.js +7 -1
- data/test/package/rules/raw.test.js +40 -7
- data/test/package/rules/sass-version-parsing.test.js +71 -0
- data/test/package/rules/sass.test.js +11 -6
- data/test/package/rules/sass1.test.js +8 -5
- data/test/package/rules/sass16.test.js +24 -0
- data/test/package/rules/swc.test.js +50 -39
- data/test/package/rules/webpack.test.js +35 -0
- data/test/package/staging.test.js +4 -3
- data/test/package/transpiler-defaults.test.js +169 -0
- data/test/package/utils/ensureManifestExists.test.js +51 -0
- data/test/package/yamlSerializer.test.js +204 -0
- data/test/peer-dependencies.sh +85 -0
- data/test/resolver.js +34 -3
- data/test/scripts/remove-use-strict.test.js +125 -0
- data/test/typescript/build.test.js +118 -0
- data/test/typescript/environments.test.js +107 -0
- data/test/typescript/pathValidation.test.js +186 -0
- data/test/typescript/requireOrError.test.js +49 -0
- data/test/typescript/securityValidation.test.js +182 -0
- data/tools/README.md +134 -0
- data/tools/css-modules-v9-codemod.js +179 -0
- data/tsconfig.eslint.json +9 -0
- data/tsconfig.json +38 -0
- data/yarn.lock +3202 -1097
- metadata +212 -44
- data/.eslintignore +0 -4
- data/.eslintrc.js +0 -36
- data/Gemfile.lock +0 -251
- data/package/babel/preset.js +0 -48
- data/package/config.js +0 -56
- data/package/dev_server.js +0 -23
- data/package/env.js +0 -48
- data/package/environments/base.js +0 -171
- data/package/environments/development.js +0 -13
- data/package/environments/production.js +0 -88
- data/package/environments/test.js +0 -3
- data/package/esbuild/index.js +0 -40
- data/package/index.js +0 -40
- data/package/rules/css.js +0 -3
- data/package/rules/esbuild.js +0 -10
- data/package/rules/file.js +0 -29
- data/package/rules/raw.js +0 -5
- data/package/rules/sass.js +0 -18
- data/package/rules/swc.js +0 -10
- data/package/swc/index.js +0 -50
- data/package/utils/configPath.js +0 -4
- data/package/utils/defaultConfigPath.js +0 -2
- data/package/utils/getStyleRule.js +0 -40
- data/package/utils/helpers.js +0 -62
- data/package/utils/snakeToCamelCase.js +0 -5
- data/package/webpackDevServerConfig.js +0 -71
- data/test/package/rules/index.test.js +0 -16
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs"
|
|
2
|
+
import { resolve, dirname, relative, isAbsolute, basename } from "path"
|
|
3
|
+
import { tmpdir } from "os"
|
|
4
|
+
import { FileOutput } from "./types"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handles writing config exports to files.
|
|
8
|
+
* Supports single file output or multiple files (one per config).
|
|
9
|
+
*/
|
|
10
|
+
export class FileWriter {
|
|
11
|
+
/**
|
|
12
|
+
* Write multiple config files (one per config in array)
|
|
13
|
+
*/
|
|
14
|
+
static writeMultipleFiles(outputs: FileOutput[], targetDir: string): void {
|
|
15
|
+
// Ensure directory exists
|
|
16
|
+
FileWriter.ensureDirectory(targetDir)
|
|
17
|
+
|
|
18
|
+
// Write each file
|
|
19
|
+
outputs.forEach((output) => {
|
|
20
|
+
const safeName = basename(output.filename)
|
|
21
|
+
const filePath = resolve(targetDir, safeName)
|
|
22
|
+
FileWriter.validateOutputPath(filePath)
|
|
23
|
+
FileWriter.writeFile(filePath, output.content)
|
|
24
|
+
console.log(`[Config Exporter] Created: ${filePath}`)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
console.log(
|
|
28
|
+
`[Config Exporter] Exported ${outputs.length} config file(s) to ${targetDir}`
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Write a single file
|
|
34
|
+
*/
|
|
35
|
+
static writeSingleFile(filePath: string, content: string): void {
|
|
36
|
+
// Ensure parent directory exists
|
|
37
|
+
const dir = dirname(filePath)
|
|
38
|
+
FileWriter.ensureDirectory(dir)
|
|
39
|
+
|
|
40
|
+
FileWriter.validateOutputPath(filePath)
|
|
41
|
+
FileWriter.writeFile(filePath, content)
|
|
42
|
+
console.log(`[Config Exporter] Created: ${filePath}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate filename for a config export
|
|
47
|
+
* Format without build: {bundler}-{env}-{type}.{ext}
|
|
48
|
+
* Format with build: {bundler}-{build}-{type}.{ext}
|
|
49
|
+
*
|
|
50
|
+
* @param bundler - The bundler type (webpack, rspack)
|
|
51
|
+
* @param env - The environment (development, production, test)
|
|
52
|
+
* @param configType - Type of config. Built-in: "client", "server", "all", "client-hmr". Custom: any string from outputs array
|
|
53
|
+
* @param format - Output format (yaml, json, inspect)
|
|
54
|
+
* @param buildName - Optional build name that overrides env in filename
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Built-in types
|
|
58
|
+
* generateFilename("webpack", "development", "client", "yaml")
|
|
59
|
+
* // => "webpack-development-client.yml"
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Custom output names
|
|
63
|
+
* generateFilename("webpack", "development", "client-modern", "yaml", "dev-hmr")
|
|
64
|
+
* // => "webpack-dev-hmr-client-modern.yml"
|
|
65
|
+
*/
|
|
66
|
+
static generateFilename(
|
|
67
|
+
bundler: string,
|
|
68
|
+
env: string,
|
|
69
|
+
configType: string,
|
|
70
|
+
format: "yaml" | "json" | "inspect",
|
|
71
|
+
buildName?: string
|
|
72
|
+
): string {
|
|
73
|
+
let ext: string
|
|
74
|
+
if (format === "yaml") {
|
|
75
|
+
ext = "yml"
|
|
76
|
+
} else if (format === "json") {
|
|
77
|
+
ext = "json"
|
|
78
|
+
} else {
|
|
79
|
+
ext = "txt"
|
|
80
|
+
}
|
|
81
|
+
const name = buildName || env
|
|
82
|
+
return `${bundler}-${name}-${configType}.${ext}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private static writeFile(filePath: string, content: string): void {
|
|
86
|
+
writeFileSync(filePath, content, "utf8")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private static ensureDirectory(dir: string): void {
|
|
90
|
+
if (!existsSync(dir)) {
|
|
91
|
+
mkdirSync(dir, { recursive: true })
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validate output path and warn if writing outside cwd
|
|
97
|
+
*/
|
|
98
|
+
private static validateOutputPath(outputPath: string): void {
|
|
99
|
+
const absPath = resolve(outputPath)
|
|
100
|
+
const cwd = process.cwd()
|
|
101
|
+
|
|
102
|
+
const isWithin = (base: string, target: string) => {
|
|
103
|
+
const rel = relative(base, target)
|
|
104
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))
|
|
105
|
+
}
|
|
106
|
+
if (!isWithin(cwd, absPath) && !isWithin(tmpdir(), absPath)) {
|
|
107
|
+
console.warn(
|
|
108
|
+
`[Config Exporter] Warning: Writing to ${absPath} which is outside current directory (${cwd}) or temp (${tmpdir()})`
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { run } from "./cli"
|
|
2
|
+
export type {
|
|
3
|
+
ExportOptions,
|
|
4
|
+
ConfigMetadata,
|
|
5
|
+
FileOutput,
|
|
6
|
+
BundlerConfigFile,
|
|
7
|
+
BuildConfig,
|
|
8
|
+
ResolvedBuildConfig,
|
|
9
|
+
BuildValidationResult
|
|
10
|
+
} from "./types"
|
|
11
|
+
export { YamlSerializer } from "./yamlSerializer"
|
|
12
|
+
export { FileWriter } from "./fileWriter"
|
|
13
|
+
export { getDocForKey } from "./configDocs"
|
|
14
|
+
export { ConfigFileLoader, generateSampleConfigFile } from "./configFile"
|
|
15
|
+
export { BuildValidator } from "./buildValidator"
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable names that can be set by build configurations.
|
|
3
|
+
* These are the only environment variables that build configs are allowed to set.
|
|
4
|
+
* This whitelist prevents malicious configs from modifying critical system variables.
|
|
5
|
+
*/
|
|
6
|
+
export const BUILD_ENV_VARS = [
|
|
7
|
+
"NODE_ENV",
|
|
8
|
+
"RAILS_ENV",
|
|
9
|
+
"NODE_OPTIONS",
|
|
10
|
+
"BABEL_ENV",
|
|
11
|
+
"WEBPACK_SERVE",
|
|
12
|
+
"CLIENT_BUNDLE_ONLY",
|
|
13
|
+
"SERVER_BUNDLE_ONLY"
|
|
14
|
+
] as const
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Environment variables that must never be set by build configurations.
|
|
18
|
+
* Setting these could compromise system security or cause unexpected behavior.
|
|
19
|
+
*/
|
|
20
|
+
export const DANGEROUS_ENV_VARS = [
|
|
21
|
+
"PATH",
|
|
22
|
+
"HOME",
|
|
23
|
+
"LD_PRELOAD",
|
|
24
|
+
"LD_LIBRARY_PATH",
|
|
25
|
+
"DYLD_LIBRARY_PATH",
|
|
26
|
+
"DYLD_INSERT_LIBRARIES"
|
|
27
|
+
] as const
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Type predicate to check if a string is in the BUILD_ENV_VARS whitelist
|
|
31
|
+
*
|
|
32
|
+
* Note: The type assertion is necessary because TypeScript's type system cannot
|
|
33
|
+
* infer that .includes() on a readonly const array will properly narrow the type.
|
|
34
|
+
* The assertion is safe because we're only widening the type for the includes() check.
|
|
35
|
+
*/
|
|
36
|
+
export function isBuildEnvVar(
|
|
37
|
+
key: string
|
|
38
|
+
): key is (typeof BUILD_ENV_VARS)[number] {
|
|
39
|
+
return (BUILD_ENV_VARS as readonly string[]).includes(key)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Type predicate to check if a string is in the DANGEROUS_ENV_VARS blacklist
|
|
44
|
+
*
|
|
45
|
+
* Note: The type assertion is necessary because TypeScript's type system cannot
|
|
46
|
+
* infer that .includes() on a readonly const array will properly narrow the type.
|
|
47
|
+
* The assertion is safe because we're only widening the type for the includes() check.
|
|
48
|
+
*/
|
|
49
|
+
export function isDangerousEnvVar(
|
|
50
|
+
key: string
|
|
51
|
+
): key is (typeof DANGEROUS_ENV_VARS)[number] {
|
|
52
|
+
return (DANGEROUS_ENV_VARS as readonly string[]).includes(key)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default directory for config exports when using --doctor or file output modes.
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_EXPORT_DIR = "shakapacker-config-exports"
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Default config file path for bundler build configurations.
|
|
62
|
+
*/
|
|
63
|
+
export const DEFAULT_CONFIG_FILE = "config/shakapacker-builds.yml"
|
|
64
|
+
|
|
65
|
+
export interface ExportOptions {
|
|
66
|
+
doctor?: boolean
|
|
67
|
+
saveDir?: string
|
|
68
|
+
stdout?: boolean
|
|
69
|
+
bundler?: "webpack" | "rspack"
|
|
70
|
+
env?: "development" | "production" | "test"
|
|
71
|
+
clientOnly?: boolean
|
|
72
|
+
serverOnly?: boolean
|
|
73
|
+
output?: string
|
|
74
|
+
format?: "yaml" | "json" | "inspect"
|
|
75
|
+
annotate?: boolean
|
|
76
|
+
verbose?: boolean
|
|
77
|
+
depth?: number | null
|
|
78
|
+
help?: boolean
|
|
79
|
+
// New config file options
|
|
80
|
+
init?: boolean
|
|
81
|
+
ssr?: boolean
|
|
82
|
+
configFile?: string
|
|
83
|
+
build?: string
|
|
84
|
+
listBuilds?: boolean
|
|
85
|
+
allBuilds?: boolean
|
|
86
|
+
// Validation options
|
|
87
|
+
validate?: boolean
|
|
88
|
+
validateBuild?: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ConfigMetadata {
|
|
92
|
+
exportedAt: string
|
|
93
|
+
bundler: string
|
|
94
|
+
environment: string
|
|
95
|
+
configFile: string
|
|
96
|
+
/**
|
|
97
|
+
* Type of webpack/rspack config output.
|
|
98
|
+
* Built-in types: "client", "server", "all", "client-hmr"
|
|
99
|
+
* Custom types: Any string matching your outputs array (e.g., "client-modern", "client-legacy", "server-bundle")
|
|
100
|
+
*/
|
|
101
|
+
configType: string
|
|
102
|
+
configCount: number
|
|
103
|
+
buildName?: string // New: name of the build from config file
|
|
104
|
+
environmentVariables: {
|
|
105
|
+
NODE_ENV?: string
|
|
106
|
+
RAILS_ENV?: string
|
|
107
|
+
CLIENT_BUNDLE_ONLY?: string
|
|
108
|
+
SERVER_BUNDLE_ONLY?: string
|
|
109
|
+
WEBPACK_SERVE?: string
|
|
110
|
+
HMR?: string
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface FileOutput {
|
|
115
|
+
filename: string
|
|
116
|
+
content: string
|
|
117
|
+
metadata: ConfigMetadata
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Config file schema types
|
|
121
|
+
export interface BundlerConfigFile {
|
|
122
|
+
default_bundler?: "webpack" | "rspack"
|
|
123
|
+
shakapacker_doctor_default_builds_here?: boolean
|
|
124
|
+
builds: Record<string, BuildConfig>
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface BuildConfig {
|
|
128
|
+
description?: string
|
|
129
|
+
bundler?: "webpack" | "rspack"
|
|
130
|
+
dev_server?: boolean
|
|
131
|
+
environment?: Record<string, string>
|
|
132
|
+
bundler_env?: Record<string, string | boolean>
|
|
133
|
+
outputs?: string[]
|
|
134
|
+
config?: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface ResolvedBuildConfig {
|
|
138
|
+
name: string
|
|
139
|
+
description?: string
|
|
140
|
+
bundler: "webpack" | "rspack"
|
|
141
|
+
environment: Record<string, string>
|
|
142
|
+
bundlerEnvArgs: string[] // Converted bundler_env to CLI args
|
|
143
|
+
outputs: string[]
|
|
144
|
+
configFile?: string
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface BuildValidationResult {
|
|
148
|
+
buildName: string
|
|
149
|
+
success: boolean
|
|
150
|
+
errors: string[]
|
|
151
|
+
warnings: string[]
|
|
152
|
+
output: string[]
|
|
153
|
+
outputs?: string[] // Build outputs (e.g., ["client", "server"])
|
|
154
|
+
configFile?: string // Config file path if specified
|
|
155
|
+
outputPath?: string // Output directory where files are written
|
|
156
|
+
startTime?: number // Unix timestamp in milliseconds
|
|
157
|
+
endTime?: number // Unix timestamp in milliseconds
|
|
158
|
+
duration?: number // Duration in milliseconds
|
|
159
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { relative, isAbsolute } from "path"
|
|
2
|
+
import { ConfigMetadata } from "./types"
|
|
3
|
+
import { getDocForKey } from "./configDocs"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Serializes webpack/rspack config to YAML format with optional inline documentation.
|
|
7
|
+
* Handles functions, RegExp, and special objects that don't serialize well to standard YAML.
|
|
8
|
+
*/
|
|
9
|
+
export class YamlSerializer {
|
|
10
|
+
private annotate: boolean
|
|
11
|
+
|
|
12
|
+
private appRoot: string
|
|
13
|
+
|
|
14
|
+
constructor(options: { annotate: boolean; appRoot: string }) {
|
|
15
|
+
this.annotate = options.annotate
|
|
16
|
+
this.appRoot = options.appRoot
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Serialize a config object to YAML string with metadata header
|
|
21
|
+
*/
|
|
22
|
+
serialize(config: unknown, metadata: ConfigMetadata): string {
|
|
23
|
+
const output: string[] = []
|
|
24
|
+
|
|
25
|
+
// Add metadata header
|
|
26
|
+
output.push(YamlSerializer.createHeader(metadata))
|
|
27
|
+
output.push("")
|
|
28
|
+
|
|
29
|
+
// Serialize the config
|
|
30
|
+
output.push(this.serializeValue(config, 0, ""))
|
|
31
|
+
|
|
32
|
+
return output.join("\n")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private static createHeader(metadata: ConfigMetadata): string {
|
|
36
|
+
const lines: string[] = []
|
|
37
|
+
lines.push(`# ${"=".repeat(77)}`)
|
|
38
|
+
lines.push("# Webpack/Rspack Configuration Export")
|
|
39
|
+
lines.push(`# Generated: ${metadata.exportedAt}`)
|
|
40
|
+
lines.push(`# Environment: ${metadata.environment}`)
|
|
41
|
+
lines.push(`# Bundler: ${metadata.bundler}`)
|
|
42
|
+
lines.push(`# Config Type: ${metadata.configType}`)
|
|
43
|
+
if (metadata.configCount > 1) {
|
|
44
|
+
lines.push(`# Total Configs: ${metadata.configCount}`)
|
|
45
|
+
}
|
|
46
|
+
lines.push(`# ${"=".repeat(77)}`)
|
|
47
|
+
return lines.join("\n")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private serializeValue(
|
|
51
|
+
value: unknown,
|
|
52
|
+
indent: number,
|
|
53
|
+
keyPath: string
|
|
54
|
+
): string {
|
|
55
|
+
if (value === null || value === undefined) {
|
|
56
|
+
return "null"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof value === "boolean") {
|
|
60
|
+
return value.toString()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof value === "number") {
|
|
64
|
+
return value.toString()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof value === "string") {
|
|
68
|
+
return this.serializeString(value, indent)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof value === "function") {
|
|
72
|
+
return this.serializeFunction(
|
|
73
|
+
value as (...args: unknown[]) => unknown,
|
|
74
|
+
indent
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (value instanceof RegExp) {
|
|
79
|
+
// Extract pattern without surrounding slashes: "/pattern/flags" -> "pattern"
|
|
80
|
+
// Include flags as inline comment when present for semantic clarity
|
|
81
|
+
const regexStr = value.toString()
|
|
82
|
+
const lastSlash = regexStr.lastIndexOf("/")
|
|
83
|
+
const pattern = regexStr.slice(1, lastSlash)
|
|
84
|
+
const flags = regexStr.slice(lastSlash + 1)
|
|
85
|
+
|
|
86
|
+
const serializedPattern = this.serializeString(pattern, indent)
|
|
87
|
+
|
|
88
|
+
// Add flags as inline comment if present (e.g., "pattern" # flags: gi)
|
|
89
|
+
if (flags) {
|
|
90
|
+
return `${serializedPattern} # flags: ${flags}`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return serializedPattern
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
return this.serializeArray(value, indent, keyPath)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (typeof value === "object") {
|
|
101
|
+
return this.serializeObject(
|
|
102
|
+
value as Record<string, unknown>,
|
|
103
|
+
indent,
|
|
104
|
+
keyPath
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Handle remaining types explicitly
|
|
109
|
+
if (typeof value === "symbol") return value.toString()
|
|
110
|
+
if (typeof value === "bigint") return value.toString()
|
|
111
|
+
|
|
112
|
+
// All remaining types are primitives (string, number, boolean, null, undefined)
|
|
113
|
+
// that String() handles safely - cast to exclude objects since we've already handled them
|
|
114
|
+
return String(value as string | number | boolean | null | undefined)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private serializeString(str: string, indent: number = 0): string {
|
|
118
|
+
// Make absolute paths relative for cleaner output
|
|
119
|
+
const cleaned = this.makePathRelative(str)
|
|
120
|
+
|
|
121
|
+
// Handle multiline strings
|
|
122
|
+
if (cleaned.includes("\n")) {
|
|
123
|
+
const lines = cleaned.split("\n")
|
|
124
|
+
const lineIndent = " ".repeat(indent + 2)
|
|
125
|
+
return `|\n${lines.map((line) => lineIndent + line).join("\n")}`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Escape strings that need quoting in YAML
|
|
129
|
+
// YAML has many special characters that can cause parsing errors:
|
|
130
|
+
// : # ' " (basic delimiters)
|
|
131
|
+
// [ ] { } (flow collections)
|
|
132
|
+
// * & ! @ ` (special constructs: aliases, anchors, tags)
|
|
133
|
+
if (
|
|
134
|
+
cleaned.includes(":") ||
|
|
135
|
+
cleaned.includes("#") ||
|
|
136
|
+
cleaned.includes("'") ||
|
|
137
|
+
cleaned.includes('"') ||
|
|
138
|
+
cleaned.includes("[") ||
|
|
139
|
+
cleaned.includes("]") ||
|
|
140
|
+
cleaned.includes("{") ||
|
|
141
|
+
cleaned.includes("}") ||
|
|
142
|
+
cleaned.includes("*") ||
|
|
143
|
+
cleaned.includes("&") ||
|
|
144
|
+
cleaned.includes("!") ||
|
|
145
|
+
cleaned.includes("@") ||
|
|
146
|
+
cleaned.includes("`") ||
|
|
147
|
+
cleaned.startsWith(" ") ||
|
|
148
|
+
cleaned.endsWith(" ")
|
|
149
|
+
) {
|
|
150
|
+
// Escape backslashes first, then quotes to avoid double-escaping
|
|
151
|
+
return `"${cleaned.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return cleaned
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private serializeFunction(
|
|
158
|
+
fn: (...args: unknown[]) => unknown,
|
|
159
|
+
indent: number = 0
|
|
160
|
+
): string {
|
|
161
|
+
// Get function source code
|
|
162
|
+
const source = fn.toString()
|
|
163
|
+
|
|
164
|
+
// Pretty-print function: maintain readable formatting
|
|
165
|
+
const lines = source.split("\n")
|
|
166
|
+
|
|
167
|
+
// For very long functions, truncate
|
|
168
|
+
const maxLines = 50
|
|
169
|
+
const truncated = lines.length > maxLines
|
|
170
|
+
const displayLines = truncated ? lines.slice(0, maxLines) : lines
|
|
171
|
+
|
|
172
|
+
// Clean up indentation while preserving structure
|
|
173
|
+
const indentLevels = displayLines
|
|
174
|
+
.filter((l) => l.trim().length > 0)
|
|
175
|
+
.map((l) => l.match(/^\s*/)?.[0].length || 0)
|
|
176
|
+
const minIndent = indentLevels.length > 0 ? Math.min(...indentLevels) : 0
|
|
177
|
+
|
|
178
|
+
const formatted =
|
|
179
|
+
displayLines.map((line) => line.substring(minIndent)).join("\n") +
|
|
180
|
+
(truncated ? "\n..." : "")
|
|
181
|
+
|
|
182
|
+
// Use serializeString to properly handle multiline with correct indentation
|
|
183
|
+
return this.serializeString(formatted, indent)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private serializeArray(
|
|
187
|
+
arr: unknown[],
|
|
188
|
+
indent: number,
|
|
189
|
+
keyPath: string
|
|
190
|
+
): string {
|
|
191
|
+
if (arr.length === 0) {
|
|
192
|
+
return "[]"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const lines: string[] = []
|
|
196
|
+
const itemIndent = " ".repeat(indent + 2)
|
|
197
|
+
const contentIndent = " ".repeat(indent + 4)
|
|
198
|
+
|
|
199
|
+
arr.forEach((item, index) => {
|
|
200
|
+
const itemPath = `${keyPath}[${index}]`
|
|
201
|
+
|
|
202
|
+
// Check if this is a plugin object and add its name as a comment
|
|
203
|
+
const pluginName = YamlSerializer.getConstructorName(item)
|
|
204
|
+
const isPlugin = pluginName && /(^|\.)plugins\[\d+\]/.test(itemPath)
|
|
205
|
+
const isEmpty =
|
|
206
|
+
typeof item === "object" &&
|
|
207
|
+
item !== null &&
|
|
208
|
+
!Array.isArray(item) &&
|
|
209
|
+
Object.keys(item).length === 0
|
|
210
|
+
|
|
211
|
+
// For non-empty plugins, add comment before the plugin
|
|
212
|
+
// For empty plugins, the name will be shown inline
|
|
213
|
+
if (isPlugin && !isEmpty) {
|
|
214
|
+
lines.push(`${itemIndent}# ${pluginName}`)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const serialized = this.serializeValue(item, indent + 4, itemPath)
|
|
218
|
+
|
|
219
|
+
// Add documentation for array items if available
|
|
220
|
+
if (this.annotate) {
|
|
221
|
+
const doc = getDocForKey(itemPath)
|
|
222
|
+
if (doc) {
|
|
223
|
+
lines.push(`${itemIndent}# ${doc}`)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (typeof item === "object" && !Array.isArray(item) && item !== null) {
|
|
228
|
+
// For objects in arrays, emit marker on its own line and indent content
|
|
229
|
+
lines.push(`${itemIndent}-`)
|
|
230
|
+
const nonEmptyLines = serialized
|
|
231
|
+
.split("\n")
|
|
232
|
+
.filter((line: string) => line.trim().length > 0)
|
|
233
|
+
// Compute minimum leading whitespace to preserve relative indentation
|
|
234
|
+
const indentLevels = nonEmptyLines.map(
|
|
235
|
+
(line: string) => line.match(/^\s*/)?.[0].length || 0
|
|
236
|
+
)
|
|
237
|
+
const minIndent =
|
|
238
|
+
indentLevels.length > 0 ? Math.min(...indentLevels) : 0
|
|
239
|
+
nonEmptyLines.forEach((line: string) => {
|
|
240
|
+
// Remove only the common indent, preserving relative indentation
|
|
241
|
+
lines.push(contentIndent + line.substring(minIndent))
|
|
242
|
+
})
|
|
243
|
+
} else if (serialized.includes("\n")) {
|
|
244
|
+
// For multiline values, emit marker on its own line and indent content
|
|
245
|
+
lines.push(`${itemIndent}-`)
|
|
246
|
+
const nonEmptyLines = serialized
|
|
247
|
+
.split("\n")
|
|
248
|
+
.filter((line: string) => line.trim().length > 0)
|
|
249
|
+
// Compute minimum leading whitespace to preserve relative indentation
|
|
250
|
+
const indentLevels = nonEmptyLines.map(
|
|
251
|
+
(line: string) => line.match(/^\s*/)?.[0].length || 0
|
|
252
|
+
)
|
|
253
|
+
const minIndent =
|
|
254
|
+
indentLevels.length > 0 ? Math.min(...indentLevels) : 0
|
|
255
|
+
nonEmptyLines.forEach((line: string) => {
|
|
256
|
+
// Remove only the common indent, preserving relative indentation
|
|
257
|
+
lines.push(contentIndent + line.substring(minIndent))
|
|
258
|
+
})
|
|
259
|
+
} else {
|
|
260
|
+
// For simple values, keep on same line
|
|
261
|
+
lines.push(`${itemIndent}- ${serialized}`)
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
return `\n${lines.join("\n")}`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private serializeObject(
|
|
269
|
+
obj: Record<string, unknown>,
|
|
270
|
+
indent: number,
|
|
271
|
+
keyPath: string
|
|
272
|
+
): string {
|
|
273
|
+
const keys = Object.keys(obj).sort()
|
|
274
|
+
const constructorName = YamlSerializer.getConstructorName(obj)
|
|
275
|
+
|
|
276
|
+
// For empty objects, show constructor name if available
|
|
277
|
+
if (keys.length === 0) {
|
|
278
|
+
if (constructorName) {
|
|
279
|
+
return `{} # ${constructorName}`
|
|
280
|
+
}
|
|
281
|
+
return "{}"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const lines: string[] = []
|
|
285
|
+
const keyIndent = " ".repeat(indent)
|
|
286
|
+
const valueIndent = " ".repeat(indent + 2)
|
|
287
|
+
|
|
288
|
+
keys.forEach((key) => {
|
|
289
|
+
const value = obj[key]
|
|
290
|
+
const fullKeyPath = keyPath ? `${keyPath}.${key}` : key
|
|
291
|
+
|
|
292
|
+
// Add documentation comment if available and annotation is enabled
|
|
293
|
+
if (this.annotate) {
|
|
294
|
+
const doc = getDocForKey(fullKeyPath)
|
|
295
|
+
if (doc) {
|
|
296
|
+
lines.push(`${keyIndent}# ${doc}`)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Handle multiline strings specially with block scalar
|
|
301
|
+
if (typeof value === "string" && value.includes("\n")) {
|
|
302
|
+
lines.push(`${keyIndent}${key}: |`)
|
|
303
|
+
for (const line of value.split("\n")) {
|
|
304
|
+
lines.push(`${valueIndent}${line}`)
|
|
305
|
+
}
|
|
306
|
+
} else if (value instanceof RegExp || typeof value === "function") {
|
|
307
|
+
// Handle RegExp and functions explicitly before the generic object check
|
|
308
|
+
// to prevent them from being treated as empty objects (RegExp/functions
|
|
309
|
+
// have no enumerable keys but should serialize as their string representation)
|
|
310
|
+
const serialized = this.serializeValue(value, indent + 2, fullKeyPath)
|
|
311
|
+
lines.push(`${keyIndent}${key}: ${serialized}`)
|
|
312
|
+
} else if (
|
|
313
|
+
typeof value === "object" &&
|
|
314
|
+
value !== null &&
|
|
315
|
+
!Array.isArray(value)
|
|
316
|
+
) {
|
|
317
|
+
if (Object.keys(value).length === 0) {
|
|
318
|
+
lines.push(`${keyIndent}${key}: {}`)
|
|
319
|
+
} else {
|
|
320
|
+
lines.push(`${keyIndent}${key}:`)
|
|
321
|
+
const nestedLines = this.serializeObject(
|
|
322
|
+
value as Record<string, unknown>,
|
|
323
|
+
indent + 2,
|
|
324
|
+
fullKeyPath
|
|
325
|
+
)
|
|
326
|
+
lines.push(nestedLines)
|
|
327
|
+
}
|
|
328
|
+
} else if (Array.isArray(value)) {
|
|
329
|
+
if (value.length === 0) {
|
|
330
|
+
lines.push(`${keyIndent}${key}: []`)
|
|
331
|
+
} else {
|
|
332
|
+
lines.push(`${keyIndent}${key}:`)
|
|
333
|
+
const arrayLines = this.serializeArray(value, indent + 2, fullKeyPath)
|
|
334
|
+
lines.push(arrayLines)
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
const serialized = this.serializeValue(value, indent + 2, fullKeyPath)
|
|
338
|
+
lines.push(`${keyIndent}${key}: ${serialized}`)
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
return lines.join("\n")
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private makePathRelative(str: string): string {
|
|
346
|
+
if (!isAbsolute(str)) return str
|
|
347
|
+
|
|
348
|
+
// Convert absolute paths to relative paths using path.relative
|
|
349
|
+
const rel = relative(this.appRoot, str)
|
|
350
|
+
|
|
351
|
+
if (rel === "") {
|
|
352
|
+
return "."
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// If path is outside appRoot or already absolute, keep original
|
|
356
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
357
|
+
return str
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return `./${rel}`
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Extracts the constructor name from an object
|
|
365
|
+
* Returns null for plain objects (Object constructor) or objects without prototypes
|
|
366
|
+
*/
|
|
367
|
+
private static getConstructorName(obj: unknown): string | null {
|
|
368
|
+
if (!obj || typeof obj !== "object") return null
|
|
369
|
+
if (Array.isArray(obj)) return null
|
|
370
|
+
|
|
371
|
+
// Use Object.getPrototypeOf for safer access to constructor
|
|
372
|
+
// This handles Object.create(null) and unusual prototypes correctly
|
|
373
|
+
try {
|
|
374
|
+
const proto = Object.getPrototypeOf(obj) as {
|
|
375
|
+
constructor?: { name?: string }
|
|
376
|
+
} | null
|
|
377
|
+
if (!proto || proto === Object.prototype) return null
|
|
378
|
+
|
|
379
|
+
const { constructor } = proto
|
|
380
|
+
if (!constructor || typeof constructor !== "function") return null
|
|
381
|
+
|
|
382
|
+
const constructorName = constructor.name
|
|
383
|
+
if (!constructorName || constructorName === "Object") return null
|
|
384
|
+
|
|
385
|
+
return constructorName
|
|
386
|
+
} catch {
|
|
387
|
+
// Handle frozen objects or other edge cases
|
|
388
|
+
return null
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// These are the raw shakapacker dev server config settings from the YML file with ENV overrides applied.
|
|
2
|
+
import type { DevServerConfig, Config } from "./types"
|
|
3
|
+
|
|
4
|
+
const { isBoolean } = require("./utils/helpers")
|
|
5
|
+
const config = require("./config") as Config
|
|
6
|
+
|
|
7
|
+
const envFetch = (key: string): string | boolean | undefined => {
|
|
8
|
+
const value = process.env[key]
|
|
9
|
+
if (!value) return undefined
|
|
10
|
+
return isBoolean(value) ? JSON.parse(value) : value
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const devServerConfig: DevServerConfig | undefined = config.dev_server
|
|
14
|
+
|
|
15
|
+
if (devServerConfig) {
|
|
16
|
+
const envPrefix = devServerConfig.env_prefix || "SHAKAPACKER_DEV_SERVER"
|
|
17
|
+
|
|
18
|
+
Object.keys(devServerConfig).forEach((key) => {
|
|
19
|
+
const envValue = envFetch(`${envPrefix}_${key.toUpperCase()}`)
|
|
20
|
+
if (envValue !== undefined) {
|
|
21
|
+
// Use bracket notation to avoid ASI issues
|
|
22
|
+
;(devServerConfig as Record<string, unknown>)[key] = envValue
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export = devServerConfig || {}
|