shakapacker 9.2.0 → 9.3.0.beta.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/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
- data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
- data/.github/workflows/claude-code-review.yml +4 -5
- data/.github/workflows/claude.yml +1 -2
- data/.github/workflows/dummy.yml +4 -4
- data/.github/workflows/generator.yml +9 -9
- data/.github/workflows/node.yml +11 -2
- data/.github/workflows/ruby.yml +16 -16
- data/.github/workflows/test-bundlers.yml +9 -9
- data/.gitignore +4 -0
- data/CHANGELOG.md +74 -5
- data/CLAUDE.md +6 -1
- data/CONTRIBUTING.md +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +14 -14
- data/TODO.md +10 -2
- data/TODO_v9.md +13 -3
- data/bin/export-bundler-config +1 -1
- data/conductor-setup.sh +1 -1
- data/conductor.json +1 -1
- data/docs/cdn_setup.md +13 -8
- data/docs/common-upgrades.md +2 -1
- data/docs/configuration.md +630 -0
- data/docs/css-modules-export-mode.md +120 -100
- data/docs/customizing_babel_config.md +16 -16
- data/docs/deployment.md +18 -0
- data/docs/developing_shakapacker.md +6 -0
- data/docs/optional-peer-dependencies.md +9 -4
- data/docs/peer-dependencies.md +17 -6
- data/docs/precompile_hook.md +342 -0
- data/docs/react.md +57 -47
- data/docs/releasing.md +0 -2
- data/docs/rspack.md +25 -21
- data/docs/rspack_migration_guide.md +335 -8
- 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-performance.md +40 -19
- data/docs/troubleshooting.md +141 -3
- data/docs/typescript-migration.md +48 -39
- data/docs/typescript.md +12 -8
- data/docs/using_esbuild_loader.md +10 -10
- data/docs/v6_upgrade.md +33 -20
- data/docs/v7_upgrade.md +8 -6
- data/docs/v8_upgrade.md +13 -12
- data/docs/v9_upgrade.md +2 -1
- data/eslint.config.fast.js +134 -0
- data/eslint.config.js +140 -0
- data/jest.config.js +8 -1
- data/knip.ts +54 -0
- data/lib/install/bin/export-bundler-config +1 -1
- data/lib/install/config/shakapacker.yml +16 -5
- data/lib/shakapacker/compiler.rb +80 -0
- data/lib/shakapacker/configuration.rb +33 -5
- data/lib/shakapacker/dev_server_runner.rb +140 -1
- data/lib/shakapacker/doctor.rb +294 -65
- data/lib/shakapacker/instance.rb +8 -3
- data/lib/shakapacker/runner.rb +244 -8
- data/lib/shakapacker/version.rb +1 -1
- data/lib/tasks/shakapacker/doctor.rake +42 -2
- data/package/babel/preset.ts +7 -4
- data/package/config.ts +42 -30
- data/package/configExporter/buildValidator.ts +883 -0
- data/package/configExporter/cli.ts +972 -210
- data/package/configExporter/configFile.ts +520 -0
- data/package/configExporter/fileWriter.ts +12 -8
- data/package/configExporter/index.ts +11 -1
- data/package/configExporter/types.ts +54 -2
- data/package/configExporter/yamlSerializer.ts +22 -8
- data/package/dev_server.ts +1 -1
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +11 -5
- data/package/environments/base.ts +18 -13
- data/package/environments/development.ts +1 -1
- data/package/environments/production.ts +4 -1
- data/package/index.d.ts +50 -3
- data/package/index.d.ts.template +50 -0
- data/package/index.ts +7 -7
- data/package/loaders.d.ts +2 -2
- data/package/optimization/rspack.ts +1 -1
- data/package/plugins/rspack.ts +15 -4
- data/package/plugins/webpack.ts +7 -3
- data/package/rspack/index.ts +10 -2
- data/package/rules/raw.ts +3 -2
- data/package/rules/sass.ts +1 -1
- data/package/types/README.md +15 -13
- data/package/types/index.ts +5 -5
- data/package/types.ts +0 -1
- data/package/utils/defaultConfigPath.ts +4 -1
- data/package/utils/errorCodes.ts +129 -100
- data/package/utils/errorHelpers.ts +34 -29
- data/package/utils/getStyleRule.ts +5 -2
- data/package/utils/helpers.ts +21 -11
- data/package/utils/pathValidation.ts +43 -35
- data/package/utils/requireOrError.ts +1 -1
- data/package/utils/snakeToCamelCase.ts +1 -1
- data/package/utils/typeGuards.ts +132 -83
- data/package/utils/validateDependencies.ts +1 -1
- data/package/webpack-types.d.ts +3 -3
- data/package/webpackDevServerConfig.ts +22 -10
- data/package-lock.json +2 -2
- data/package.json +25 -16
- data/scripts/type-check-no-emit.js +1 -1
- data/test/configExporter/buildValidator.test.js +1292 -0
- data/test/configExporter/configFile.test.js +392 -0
- data/test/configExporter/integration.test.js +275 -0
- data/test/helpers.js +1 -1
- data/test/package/configExporter.test.js +154 -0
- data/test/package/environments/base.test.js +6 -3
- data/test/package/helpers.test.js +2 -2
- data/test/package/rules/babel.test.js +61 -51
- data/test/package/rules/esbuild.test.js +12 -3
- data/test/package/rules/file.test.js +3 -1
- 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 +4 -5
- data/test/package/rules/sass16.test.js +24 -0
- data/test/package/rules/swc.test.js +48 -38
- data/tools/README.md +15 -5
- data/tsconfig.eslint.json +2 -9
- data/yarn.lock +1954 -1493
- metadata +22 -3
- data/.eslintignore +0 -5
|
@@ -1,22 +1,98 @@
|
|
|
1
1
|
// This will be a substantial file - the main CLI entry point
|
|
2
2
|
// Migrating from bin/export-bundler-config but streamlined for TypeScript
|
|
3
3
|
|
|
4
|
-
import { existsSync, readFileSync } from "fs"
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from "fs"
|
|
5
5
|
import { resolve, dirname, sep, delimiter, basename } from "path"
|
|
6
6
|
import { inspect } from "util"
|
|
7
7
|
import { load as loadYaml } from "js-yaml"
|
|
8
|
+
import yargs from "yargs"
|
|
8
9
|
import { ExportOptions, ConfigMetadata, FileOutput } from "./types"
|
|
9
10
|
import { YamlSerializer } from "./yamlSerializer"
|
|
10
11
|
import { FileWriter } from "./fileWriter"
|
|
12
|
+
import { ConfigFileLoader, generateSampleConfigFile } from "./configFile"
|
|
13
|
+
import { BuildValidator } from "./buildValidator"
|
|
14
|
+
|
|
15
|
+
// Read version from package.json
|
|
16
|
+
const packageJson = JSON.parse(
|
|
17
|
+
readFileSync(resolve(__dirname, "../../package.json"), "utf8")
|
|
18
|
+
)
|
|
19
|
+
const VERSION = packageJson.version
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Environment variable names that can be set by build configurations
|
|
23
|
+
*/
|
|
24
|
+
const BUILD_ENV_VARS = [
|
|
25
|
+
"NODE_ENV",
|
|
26
|
+
"RAILS_ENV",
|
|
27
|
+
"NODE_OPTIONS",
|
|
28
|
+
"BABEL_ENV",
|
|
29
|
+
"WEBPACK_SERVE",
|
|
30
|
+
"CLIENT_BUNDLE_ONLY",
|
|
31
|
+
"SERVER_BUNDLE_ONLY"
|
|
32
|
+
] as const
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Saves current values of build environment variables for later restoration
|
|
36
|
+
* @returns Object mapping variable names to their current values (or undefined)
|
|
37
|
+
*/
|
|
38
|
+
function saveBuildEnvironmentVariables(): Record<string, string | undefined> {
|
|
39
|
+
const saved: Record<string, string | undefined> = {}
|
|
40
|
+
BUILD_ENV_VARS.forEach((varName) => {
|
|
41
|
+
saved[varName] = process.env[varName]
|
|
42
|
+
})
|
|
43
|
+
return saved
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Restores previously saved environment variable values
|
|
48
|
+
* @param saved - Object mapping variable names to their original values
|
|
49
|
+
*/
|
|
50
|
+
function restoreBuildEnvironmentVariables(
|
|
51
|
+
saved: Record<string, string | undefined>
|
|
52
|
+
): void {
|
|
53
|
+
BUILD_ENV_VARS.forEach((varName) => {
|
|
54
|
+
const originalValue = saved[varName]
|
|
55
|
+
if (originalValue === undefined) {
|
|
56
|
+
delete process.env[varName]
|
|
57
|
+
} else {
|
|
58
|
+
process.env[varName] = originalValue
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Clears all whitelisted build environment variables from process.env
|
|
65
|
+
* to prevent environment variable leakage between builds
|
|
66
|
+
*/
|
|
67
|
+
function clearBuildEnvironmentVariables(): void {
|
|
68
|
+
BUILD_ENV_VARS.forEach((varName) => {
|
|
69
|
+
delete process.env[varName]
|
|
70
|
+
})
|
|
71
|
+
}
|
|
11
72
|
|
|
12
73
|
// Main CLI entry point
|
|
13
74
|
export async function run(args: string[]): Promise<number> {
|
|
14
75
|
try {
|
|
15
76
|
const options = parseArguments(args)
|
|
16
77
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return
|
|
78
|
+
// Handle --init command
|
|
79
|
+
if (options.init) {
|
|
80
|
+
return runInitCommand(options)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Handle --list-builds command
|
|
84
|
+
if (options.listBuilds) {
|
|
85
|
+
return runListBuildsCommand(options)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle --validate or --validate-build command
|
|
89
|
+
if (options.validate || options.validateBuild) {
|
|
90
|
+
return await runValidateCommand(options)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle --all-builds command
|
|
94
|
+
if (options.allBuilds) {
|
|
95
|
+
return runAllBuildsCommand(options)
|
|
20
96
|
}
|
|
21
97
|
|
|
22
98
|
// Set up environment
|
|
@@ -27,137 +103,547 @@ export async function run(args: string[]): Promise<number> {
|
|
|
27
103
|
// Apply defaults
|
|
28
104
|
applyDefaults(options)
|
|
29
105
|
|
|
30
|
-
// Validate
|
|
31
|
-
|
|
106
|
+
// Validate after defaults are applied
|
|
107
|
+
if (options.annotate && options.format !== "yaml") {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Annotation requires YAML format. Use --no-annotate or --format=yaml."
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Validate --build requires config file
|
|
114
|
+
if (options.build) {
|
|
115
|
+
const loader = new ConfigFileLoader(options.configFile)
|
|
116
|
+
if (!loader.exists()) {
|
|
117
|
+
const configPath = options.configFile || ".bundler-config.yml"
|
|
118
|
+
throw new Error(
|
|
119
|
+
`--build requires a config file but ${configPath} not found. Run --init to create it.`
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
32
123
|
|
|
33
124
|
// Execute based on mode
|
|
34
125
|
if (options.doctor) {
|
|
35
126
|
await runDoctorMode(options, appRoot)
|
|
36
|
-
} else if (options.
|
|
37
|
-
|
|
38
|
-
} else {
|
|
127
|
+
} else if (options.stdout) {
|
|
128
|
+
// Explicit stdout mode
|
|
39
129
|
await runStdoutMode(options, appRoot)
|
|
130
|
+
} else if (options.output) {
|
|
131
|
+
// Save to single file
|
|
132
|
+
await runSingleFileMode(options, appRoot)
|
|
133
|
+
} else {
|
|
134
|
+
// Default: save to directory
|
|
135
|
+
await runSaveMode(options, appRoot)
|
|
40
136
|
}
|
|
41
137
|
|
|
42
138
|
return 0
|
|
43
|
-
} catch (error:
|
|
44
|
-
|
|
139
|
+
} catch (error: unknown) {
|
|
140
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
141
|
+
console.error(`[Config Exporter] Error: ${errorMessage}`)
|
|
45
142
|
return 1
|
|
46
143
|
}
|
|
47
144
|
}
|
|
48
145
|
|
|
49
146
|
function parseArguments(args: string[]): ExportOptions {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
serverOnly: false,
|
|
55
|
-
output: undefined,
|
|
56
|
-
depth: 20,
|
|
57
|
-
format: undefined,
|
|
58
|
-
help: false,
|
|
59
|
-
verbose: false,
|
|
60
|
-
doctor: false,
|
|
61
|
-
save: false,
|
|
62
|
-
saveDir: undefined,
|
|
63
|
-
annotate: undefined
|
|
64
|
-
}
|
|
147
|
+
const argv = yargs(args)
|
|
148
|
+
.version(VERSION)
|
|
149
|
+
.usage(
|
|
150
|
+
`Shakapacker Config Exporter
|
|
65
151
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (value.length === 0) {
|
|
69
|
-
throw new Error(`${prefix} requires a value`)
|
|
70
|
-
}
|
|
71
|
-
return value
|
|
72
|
-
}
|
|
152
|
+
Exports webpack or rspack configuration in a verbose, human-readable format
|
|
153
|
+
for comparison and analysis.
|
|
73
154
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
155
|
+
QUICK START (for troubleshooting):
|
|
156
|
+
bin/export-bundler-config --doctor
|
|
157
|
+
|
|
158
|
+
Exports annotated YAML configs for both development and production.
|
|
159
|
+
Creates separate files for client and server bundles.
|
|
160
|
+
Best for debugging, AI analysis, and comparing configurations.`
|
|
161
|
+
)
|
|
162
|
+
.option("doctor", {
|
|
163
|
+
type: "boolean",
|
|
164
|
+
default: false,
|
|
165
|
+
description:
|
|
166
|
+
"Export all configs for troubleshooting (dev + prod, annotated YAML)"
|
|
167
|
+
})
|
|
168
|
+
.option("save-dir", {
|
|
169
|
+
type: "string",
|
|
170
|
+
description:
|
|
171
|
+
"Directory for output files (default: shakapacker-config-exports)"
|
|
172
|
+
})
|
|
173
|
+
.option("stdout", {
|
|
174
|
+
type: "boolean",
|
|
175
|
+
default: false,
|
|
176
|
+
description: "Output to stdout instead of saving to files"
|
|
177
|
+
})
|
|
178
|
+
.option("bundler", {
|
|
179
|
+
type: "string",
|
|
180
|
+
choices: ["webpack", "rspack"] as const,
|
|
181
|
+
description: "Specify bundler (auto-detected if not provided)"
|
|
182
|
+
})
|
|
183
|
+
.option("env", {
|
|
184
|
+
type: "string",
|
|
185
|
+
choices: ["development", "production", "test"] as const,
|
|
186
|
+
description:
|
|
187
|
+
"Node environment (default: development, ignored with --doctor or --build)"
|
|
188
|
+
})
|
|
189
|
+
.option("client-only", {
|
|
190
|
+
type: "boolean",
|
|
191
|
+
default: false,
|
|
192
|
+
description: "Generate only client config (sets CLIENT_BUNDLE_ONLY=yes)"
|
|
193
|
+
})
|
|
194
|
+
.option("server-only", {
|
|
195
|
+
type: "boolean",
|
|
196
|
+
default: false,
|
|
197
|
+
description: "Generate only server config (sets SERVER_BUNDLE_ONLY=yes)"
|
|
198
|
+
})
|
|
199
|
+
.option("output", {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "Output to specific file instead of directory"
|
|
202
|
+
})
|
|
203
|
+
.option("depth", {
|
|
204
|
+
type: "number",
|
|
205
|
+
default: 20,
|
|
206
|
+
coerce: (value: number | string) => {
|
|
207
|
+
if (value === "null" || value === null) return null
|
|
208
|
+
return typeof value === "number" ? value : parseInt(String(value), 10)
|
|
209
|
+
},
|
|
210
|
+
description: "Inspection depth (use 'null' for unlimited)"
|
|
211
|
+
})
|
|
212
|
+
.option("format", {
|
|
213
|
+
type: "string",
|
|
214
|
+
choices: ["yaml", "json", "inspect"] as const,
|
|
215
|
+
description: "Output format (default: yaml for files, inspect for stdout)"
|
|
216
|
+
})
|
|
217
|
+
.option("annotate", {
|
|
218
|
+
type: "boolean",
|
|
219
|
+
description:
|
|
220
|
+
"Enable inline documentation (YAML only, default with --doctor or file output)"
|
|
221
|
+
})
|
|
222
|
+
.option("verbose", {
|
|
223
|
+
type: "boolean",
|
|
224
|
+
default: false,
|
|
225
|
+
description: "Show full output without compact mode"
|
|
226
|
+
})
|
|
227
|
+
.option("init", {
|
|
228
|
+
type: "boolean",
|
|
229
|
+
default: false,
|
|
230
|
+
description: "Generate sample .bundler-config.yml with examples"
|
|
231
|
+
})
|
|
232
|
+
.option("config-file", {
|
|
233
|
+
type: "string",
|
|
234
|
+
description: "Path to config file (default: .bundler-config.yml)"
|
|
235
|
+
})
|
|
236
|
+
.option("build", {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Export config for specific build from config file"
|
|
239
|
+
})
|
|
240
|
+
.option("list-builds", {
|
|
241
|
+
type: "boolean",
|
|
242
|
+
default: false,
|
|
243
|
+
description: "List all available builds from config file"
|
|
244
|
+
})
|
|
245
|
+
.option("all-builds", {
|
|
246
|
+
type: "boolean",
|
|
247
|
+
default: false,
|
|
248
|
+
description: "Export all builds from config file"
|
|
249
|
+
})
|
|
250
|
+
.option("validate", {
|
|
251
|
+
type: "boolean",
|
|
252
|
+
default: false,
|
|
253
|
+
description:
|
|
254
|
+
"Validate all builds by running webpack/rspack (requires config file)"
|
|
255
|
+
})
|
|
256
|
+
.option("validate-build", {
|
|
257
|
+
type: "string",
|
|
258
|
+
description: "Validate specific build from config file"
|
|
259
|
+
})
|
|
260
|
+
.option("webpack", {
|
|
261
|
+
type: "boolean",
|
|
262
|
+
default: false,
|
|
263
|
+
description: "Use webpack (overrides config file)"
|
|
264
|
+
})
|
|
265
|
+
.option("rspack", {
|
|
266
|
+
type: "boolean",
|
|
267
|
+
default: false,
|
|
268
|
+
description: "Use rspack (overrides config file)"
|
|
269
|
+
})
|
|
270
|
+
.check((argv) => {
|
|
271
|
+
if (argv.webpack && argv.rspack) {
|
|
86
272
|
throw new Error(
|
|
87
|
-
|
|
273
|
+
"--webpack and --rspack are mutually exclusive. Please specify only one."
|
|
88
274
|
)
|
|
89
275
|
}
|
|
90
|
-
|
|
91
|
-
} else if (arg.startsWith("--env=")) {
|
|
92
|
-
const env = parseValue(arg, "--env=")
|
|
93
|
-
if (env !== "development" && env !== "production" && env !== "test") {
|
|
276
|
+
if (argv["client-only"] && argv["server-only"]) {
|
|
94
277
|
throw new Error(
|
|
95
|
-
|
|
278
|
+
"--client-only and --server-only are mutually exclusive. Please specify only one."
|
|
96
279
|
)
|
|
97
280
|
}
|
|
98
|
-
|
|
99
|
-
} else if (arg === "--client-only") {
|
|
100
|
-
options.clientOnly = true
|
|
101
|
-
} else if (arg === "--server-only") {
|
|
102
|
-
options.serverOnly = true
|
|
103
|
-
} else if (arg.startsWith("--output=")) {
|
|
104
|
-
options.output = parseValue(arg, "--output=")
|
|
105
|
-
} else if (arg.startsWith("--depth=")) {
|
|
106
|
-
const depth = parseValue(arg, "--depth=")
|
|
107
|
-
options.depth = depth === "null" ? null : parseInt(depth, 10)
|
|
108
|
-
} else if (arg.startsWith("--format=")) {
|
|
109
|
-
const format = parseValue(arg, "--format=")
|
|
110
|
-
if (format !== "yaml" && format !== "json" && format !== "inspect") {
|
|
281
|
+
if (argv.output && argv["save-dir"]) {
|
|
111
282
|
throw new Error(
|
|
112
|
-
|
|
283
|
+
"--output and --save-dir are mutually exclusive. Use one or the other."
|
|
113
284
|
)
|
|
114
285
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
286
|
+
if (argv.stdout && argv["save-dir"]) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
"--stdout and --save-dir are mutually exclusive. Use one or the other."
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
if (argv.build && argv["all-builds"]) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
"--build and --all-builds are mutually exclusive. Use one or the other."
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
if (argv.validate && argv["validate-build"]) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
"--validate and --validate-build are mutually exclusive. Use one or the other."
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
if (argv.validate && (argv.build || argv["all-builds"])) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
"--validate cannot be used with --build or --all-builds."
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
return true
|
|
307
|
+
})
|
|
308
|
+
.help("help")
|
|
309
|
+
.alias("help", "h")
|
|
310
|
+
.epilogue(
|
|
311
|
+
`Examples:
|
|
312
|
+
|
|
313
|
+
# Config File Workflow
|
|
314
|
+
bin/export-bundler-config --init
|
|
315
|
+
bin/export-bundler-config --list-builds
|
|
316
|
+
bin/export-bundler-config --build=dev
|
|
317
|
+
bin/export-bundler-config --all-builds --save-dir=./configs
|
|
318
|
+
bin/export-bundler-config --build=dev --rspack
|
|
319
|
+
|
|
320
|
+
# Traditional Workflow (without config file)
|
|
321
|
+
bin/export-bundler-config --doctor
|
|
322
|
+
# Creates: webpack-development-client-hmr.yaml, webpack-development-client.yaml,
|
|
323
|
+
# webpack-development-server.yaml, webpack-production-client.yaml,
|
|
324
|
+
# webpack-production-server.yaml
|
|
325
|
+
|
|
326
|
+
bin/export-bundler-config --env=production --client-only
|
|
327
|
+
bin/export-bundler-config --save-dir=./debug
|
|
328
|
+
bin/export-bundler-config # Saves to shakapacker-config-exports/
|
|
329
|
+
|
|
330
|
+
# Validate builds
|
|
331
|
+
bin/export-bundler-config --validate # Validate all builds
|
|
332
|
+
bin/export-bundler-config --validate-build=dev # Validate specific build
|
|
333
|
+
bin/export-bundler-config --validate --verbose # Validate with full logs
|
|
122
334
|
|
|
123
|
-
|
|
335
|
+
# View config in terminal (stdout)
|
|
336
|
+
bin/export-bundler-config --stdout
|
|
337
|
+
bin/export-bundler-config --output=config.yaml # Save to specific file`
|
|
338
|
+
)
|
|
339
|
+
.strict()
|
|
340
|
+
.parseSync()
|
|
341
|
+
|
|
342
|
+
// Type assertions are safe here because yargs validates choices at runtime
|
|
343
|
+
// Handle --webpack and --rspack flags
|
|
344
|
+
let bundler: "webpack" | "rspack" | undefined = argv.bundler as
|
|
345
|
+
| "webpack"
|
|
346
|
+
| "rspack"
|
|
347
|
+
| undefined
|
|
348
|
+
if (argv.webpack) bundler = "webpack"
|
|
349
|
+
if (argv.rspack) bundler = "rspack"
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
bundler,
|
|
353
|
+
env: argv.env as "development" | "production" | "test" | undefined,
|
|
354
|
+
clientOnly: argv["client-only"],
|
|
355
|
+
serverOnly: argv["server-only"],
|
|
356
|
+
output: argv.output,
|
|
357
|
+
depth: argv.depth as number | null,
|
|
358
|
+
format: argv.format as "yaml" | "json" | "inspect" | undefined,
|
|
359
|
+
help: false, // yargs handles help internally
|
|
360
|
+
verbose: argv.verbose,
|
|
361
|
+
doctor: argv.doctor,
|
|
362
|
+
saveDir: argv["save-dir"],
|
|
363
|
+
stdout: argv.stdout,
|
|
364
|
+
annotate: argv.annotate,
|
|
365
|
+
init: argv.init,
|
|
366
|
+
configFile: argv["config-file"],
|
|
367
|
+
build: argv.build,
|
|
368
|
+
listBuilds: argv["list-builds"],
|
|
369
|
+
allBuilds: argv["all-builds"],
|
|
370
|
+
validate: argv.validate,
|
|
371
|
+
validateBuild: argv["validate-build"]
|
|
372
|
+
}
|
|
124
373
|
}
|
|
125
374
|
|
|
126
375
|
function applyDefaults(options: ExportOptions): void {
|
|
127
376
|
if (options.doctor) {
|
|
128
|
-
options.save = true
|
|
129
377
|
if (options.format === undefined) options.format = "yaml"
|
|
130
378
|
if (options.annotate === undefined) options.annotate = true
|
|
131
|
-
} else if (options.
|
|
379
|
+
} else if (!options.stdout && !options.output) {
|
|
380
|
+
// Default mode: save to directory
|
|
132
381
|
if (options.format === undefined) options.format = "yaml"
|
|
133
382
|
if (options.annotate === undefined) options.annotate = true
|
|
134
383
|
} else {
|
|
135
384
|
if (options.format === undefined) options.format = "inspect"
|
|
136
385
|
if (options.annotate === undefined) options.annotate = false
|
|
137
386
|
}
|
|
387
|
+
|
|
388
|
+
// Set default save directory for file output modes
|
|
389
|
+
if (!options.stdout && !options.output && !options.saveDir) {
|
|
390
|
+
options.saveDir = resolve(process.cwd(), "shakapacker-config-exports")
|
|
391
|
+
}
|
|
138
392
|
}
|
|
139
393
|
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
394
|
+
function runInitCommand(options: ExportOptions): number {
|
|
395
|
+
const configPath = options.configFile || ".bundler-config.yml"
|
|
396
|
+
const fullPath = resolve(process.cwd(), configPath)
|
|
397
|
+
|
|
398
|
+
if (existsSync(fullPath)) {
|
|
399
|
+
console.error(
|
|
400
|
+
`[Config Exporter] Error: Config file already exists: ${fullPath}`
|
|
401
|
+
)
|
|
402
|
+
console.error(
|
|
403
|
+
`Remove it first or use --config-file=<path> for a different location.`
|
|
144
404
|
)
|
|
405
|
+
return 1
|
|
145
406
|
}
|
|
146
407
|
|
|
147
|
-
|
|
148
|
-
|
|
408
|
+
const sampleConfig = generateSampleConfigFile()
|
|
409
|
+
writeFileSync(fullPath, sampleConfig, "utf8")
|
|
410
|
+
|
|
411
|
+
console.log(`[Config Exporter] ✅ Created config file: ${fullPath}`)
|
|
412
|
+
console.log(`\nNext steps:`)
|
|
413
|
+
console.log(` 1. Edit the config file to match your build setup`)
|
|
414
|
+
console.log(
|
|
415
|
+
` 2. List available builds: bin/export-bundler-config --list-builds`
|
|
416
|
+
)
|
|
417
|
+
console.log(
|
|
418
|
+
` 3. Export a build: bin/export-bundler-config --build=<name> --save\n`
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
return 0
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function runListBuildsCommand(options: ExportOptions): number {
|
|
425
|
+
try {
|
|
426
|
+
const loader = new ConfigFileLoader(options.configFile)
|
|
427
|
+
loader.listBuilds()
|
|
428
|
+
return 0
|
|
429
|
+
} catch (error: unknown) {
|
|
430
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
431
|
+
console.error(`[Config Exporter] Error: ${errorMessage}`)
|
|
432
|
+
return 1
|
|
149
433
|
}
|
|
434
|
+
}
|
|
150
435
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
436
|
+
async function runValidateCommand(options: ExportOptions): Promise<number> {
|
|
437
|
+
const savedEnv = saveBuildEnvironmentVariables()
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
// Validate that config file exists
|
|
441
|
+
const loader = new ConfigFileLoader(options.configFile)
|
|
442
|
+
if (!loader.exists()) {
|
|
443
|
+
const configPath = options.configFile || ".bundler-config.yml"
|
|
444
|
+
throw new Error(
|
|
445
|
+
`Config file ${configPath} not found. Run --init to create it.`
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Set up environment
|
|
450
|
+
const appRoot = findAppRoot()
|
|
451
|
+
process.chdir(appRoot)
|
|
452
|
+
setupNodePath(appRoot)
|
|
453
|
+
|
|
454
|
+
const config = loader.load()
|
|
455
|
+
const validator = new BuildValidator({ verbose: options.verbose || false })
|
|
456
|
+
|
|
457
|
+
// Determine which builds to validate
|
|
458
|
+
let buildsToValidate: string[]
|
|
459
|
+
if (options.validateBuild) {
|
|
460
|
+
// Validate specific build
|
|
461
|
+
if (!config.builds[options.validateBuild]) {
|
|
462
|
+
const available = Object.keys(config.builds).join(", ")
|
|
463
|
+
throw new Error(
|
|
464
|
+
`Build '${options.validateBuild}' not found in config file.\n` +
|
|
465
|
+
`Available builds: ${available}`
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
buildsToValidate = [options.validateBuild]
|
|
469
|
+
} else {
|
|
470
|
+
// Validate all builds
|
|
471
|
+
buildsToValidate = Object.keys(config.builds)
|
|
472
|
+
|
|
473
|
+
// Handle empty builds edge case
|
|
474
|
+
if (buildsToValidate.length === 0) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
`No builds found in config file. Add at least one build to .bundler-config.yml or run --init to see examples.`
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
console.log("\n" + "=".repeat(80))
|
|
482
|
+
console.log("🔍 Validating Builds")
|
|
483
|
+
console.log("=".repeat(80))
|
|
484
|
+
console.log(`\nValidating ${buildsToValidate.length} build(s)...\n`)
|
|
485
|
+
|
|
486
|
+
if (options.verbose) {
|
|
487
|
+
console.log("⚡ VERBOSE MODE ENABLED - Full build output will be shown")
|
|
488
|
+
console.log(
|
|
489
|
+
" This includes all webpack/rspack compilation logs, warnings, and progress messages"
|
|
490
|
+
)
|
|
491
|
+
console.log(" Use without --verbose to see only errors and summaries\n")
|
|
492
|
+
console.log("=".repeat(80) + "\n")
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const results = []
|
|
496
|
+
|
|
497
|
+
// Validate each build
|
|
498
|
+
for (const buildName of buildsToValidate) {
|
|
499
|
+
if (options.verbose) {
|
|
500
|
+
console.log("\n" + "=".repeat(80))
|
|
501
|
+
console.log(`📦 VALIDATING BUILD: ${buildName}`)
|
|
502
|
+
console.log("=".repeat(80))
|
|
503
|
+
} else {
|
|
504
|
+
console.log(`\n📦 Validating build: ${buildName}`)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Clear and restore environment to prevent leakage between builds
|
|
508
|
+
clearBuildEnvironmentVariables()
|
|
509
|
+
restoreBuildEnvironmentVariables(savedEnv)
|
|
510
|
+
|
|
511
|
+
// Get the build's environment to use for auto-detection
|
|
512
|
+
const buildConfig = config.builds[buildName]
|
|
513
|
+
const buildEnv =
|
|
514
|
+
buildConfig.environment?.NODE_ENV ||
|
|
515
|
+
(buildConfig.environment?.RAILS_ENV as
|
|
516
|
+
| "development"
|
|
517
|
+
| "production"
|
|
518
|
+
| "test"
|
|
519
|
+
| undefined) ||
|
|
520
|
+
"development"
|
|
521
|
+
|
|
522
|
+
// Auto-detect bundler using the build's environment
|
|
523
|
+
const defaultBundler = await autoDetectBundler(buildEnv, appRoot)
|
|
524
|
+
|
|
525
|
+
// Resolve build config with the correct default bundler
|
|
526
|
+
const resolvedBuild = loader.resolveBuild(
|
|
527
|
+
buildName,
|
|
528
|
+
options,
|
|
529
|
+
defaultBundler
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
// Validate the build
|
|
533
|
+
const result = await validator.validateBuild(resolvedBuild, appRoot)
|
|
534
|
+
results.push(result)
|
|
535
|
+
|
|
536
|
+
// Show immediate feedback
|
|
537
|
+
if (options.verbose) {
|
|
538
|
+
console.log("=".repeat(80))
|
|
539
|
+
}
|
|
540
|
+
if (result.success) {
|
|
541
|
+
console.log(` ✅ Build passed`)
|
|
542
|
+
} else {
|
|
543
|
+
console.log(` ❌ Build failed with ${result.errors.length} error(s)`)
|
|
544
|
+
}
|
|
545
|
+
if (options.verbose) {
|
|
546
|
+
console.log("")
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Print formatted results
|
|
551
|
+
const formattedResults = validator.formatResults(results)
|
|
552
|
+
console.log(formattedResults)
|
|
553
|
+
|
|
554
|
+
// Return exit code based on results
|
|
555
|
+
const hasFailures = results.some((r) => !r.success)
|
|
556
|
+
return hasFailures ? 1 : 0
|
|
557
|
+
} catch (error: unknown) {
|
|
558
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
559
|
+
console.error(`[Config Exporter] Error: ${errorMessage}`)
|
|
560
|
+
return 1
|
|
561
|
+
} finally {
|
|
562
|
+
// Restore original environment
|
|
563
|
+
restoreBuildEnvironmentVariables(savedEnv)
|
|
155
564
|
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
|
|
568
|
+
// Save original environment to restore after all builds
|
|
569
|
+
const savedEnv = saveBuildEnvironmentVariables()
|
|
156
570
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
571
|
+
try {
|
|
572
|
+
// Set up environment
|
|
573
|
+
const appRoot = findAppRoot()
|
|
574
|
+
process.chdir(appRoot)
|
|
575
|
+
setupNodePath(appRoot)
|
|
576
|
+
|
|
577
|
+
// Apply defaults
|
|
578
|
+
applyDefaults(options)
|
|
579
|
+
|
|
580
|
+
const loader = new ConfigFileLoader(options.configFile)
|
|
581
|
+
if (!loader.exists()) {
|
|
582
|
+
const configPath = options.configFile || ".bundler-config.yml"
|
|
583
|
+
throw new Error(
|
|
584
|
+
`Config file ${configPath} not found. Run --init to create it.`
|
|
585
|
+
)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const config = loader.load()
|
|
589
|
+
const buildNames = Object.keys(config.builds)
|
|
590
|
+
|
|
591
|
+
console.log(
|
|
592
|
+
`\n📦 Exporting ${buildNames.length} builds from config file...\n`
|
|
160
593
|
)
|
|
594
|
+
|
|
595
|
+
const fileWriter = new FileWriter()
|
|
596
|
+
const targetDir = options.saveDir! // Set by applyDefaults
|
|
597
|
+
const createdFiles: string[] = []
|
|
598
|
+
|
|
599
|
+
// Export each build
|
|
600
|
+
for (const buildName of buildNames) {
|
|
601
|
+
console.log(`\n📦 Exporting build: ${buildName}`)
|
|
602
|
+
|
|
603
|
+
// Clear and restore environment to prevent leakage between builds
|
|
604
|
+
clearBuildEnvironmentVariables()
|
|
605
|
+
restoreBuildEnvironmentVariables(savedEnv)
|
|
606
|
+
|
|
607
|
+
// Create a modified options object for this build
|
|
608
|
+
const buildOptions = { ...options, build: buildName }
|
|
609
|
+
const configs = await loadConfigsForEnv(undefined, buildOptions, appRoot)
|
|
610
|
+
|
|
611
|
+
for (const { config: cfg, metadata } of configs) {
|
|
612
|
+
const output = formatConfig(cfg, metadata, options, appRoot)
|
|
613
|
+
const filename = fileWriter.generateFilename(
|
|
614
|
+
metadata.bundler,
|
|
615
|
+
metadata.environment,
|
|
616
|
+
metadata.configType,
|
|
617
|
+
options.format!,
|
|
618
|
+
metadata.buildName
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
const fullPath = resolve(targetDir, filename)
|
|
622
|
+
fileWriter.writeSingleFile(fullPath, output)
|
|
623
|
+
createdFiles.push(fullPath)
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Print summary
|
|
628
|
+
console.log("\n" + "=".repeat(80))
|
|
629
|
+
console.log("✅ All Builds Exported!")
|
|
630
|
+
console.log("=".repeat(80))
|
|
631
|
+
console.log(`\nCreated ${createdFiles.length} configuration file(s) in:`)
|
|
632
|
+
console.log(` ${targetDir}\n`)
|
|
633
|
+
console.log("Files:")
|
|
634
|
+
createdFiles.forEach((file) => {
|
|
635
|
+
console.log(` ✓ ${basename(file)}`)
|
|
636
|
+
})
|
|
637
|
+
console.log("\n" + "=".repeat(80) + "\n")
|
|
638
|
+
|
|
639
|
+
return 0
|
|
640
|
+
} catch (error: unknown) {
|
|
641
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
642
|
+
console.error(`[Config Exporter] Error: ${errorMessage}`)
|
|
643
|
+
return 1
|
|
644
|
+
} finally {
|
|
645
|
+
// Restore original environment
|
|
646
|
+
restoreBuildEnvironmentVariables(savedEnv)
|
|
161
647
|
}
|
|
162
648
|
}
|
|
163
649
|
|
|
@@ -165,42 +651,148 @@ async function runDoctorMode(
|
|
|
165
651
|
options: ExportOptions,
|
|
166
652
|
appRoot: string
|
|
167
653
|
): Promise<void> {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
console.log("=".repeat(80))
|
|
171
|
-
console.log("\nExporting development AND production configs...")
|
|
172
|
-
console.log("")
|
|
654
|
+
// Save original environment to restore after all builds
|
|
655
|
+
const savedEnv = saveBuildEnvironmentVariables()
|
|
173
656
|
|
|
174
|
-
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
657
|
+
try {
|
|
658
|
+
console.log("\n" + "=".repeat(80))
|
|
659
|
+
console.log("🔍 Config Exporter - Doctor Mode")
|
|
660
|
+
console.log("=".repeat(80))
|
|
661
|
+
|
|
662
|
+
const fileWriter = new FileWriter()
|
|
663
|
+
const targetDir = options.saveDir! // Set by applyDefaults
|
|
664
|
+
|
|
665
|
+
const createdFiles: string[] = []
|
|
666
|
+
|
|
667
|
+
// Check if config file exists with shakapacker_doctor_default_builds_here flag
|
|
668
|
+
const configFilePath = options.configFile || ".bundler-config.yml"
|
|
669
|
+
const loader = new ConfigFileLoader(configFilePath)
|
|
670
|
+
|
|
671
|
+
if (loader.exists()) {
|
|
672
|
+
try {
|
|
673
|
+
const configData = loader.load()
|
|
674
|
+
if (configData.shakapacker_doctor_default_builds_here) {
|
|
675
|
+
console.log(
|
|
676
|
+
"\nUsing builds from config file (shakapacker_doctor_default_builds_here: true)...\n"
|
|
677
|
+
)
|
|
678
|
+
// Use config file builds
|
|
679
|
+
const buildNames = Object.keys(configData.builds)
|
|
680
|
+
|
|
681
|
+
for (const buildName of buildNames) {
|
|
682
|
+
console.log(`\n📦 Loading build: ${buildName}`)
|
|
683
|
+
|
|
684
|
+
// Clear and restore environment to prevent leakage between builds
|
|
685
|
+
clearBuildEnvironmentVariables()
|
|
686
|
+
restoreBuildEnvironmentVariables(savedEnv)
|
|
687
|
+
|
|
688
|
+
const configs = await loadConfigsForEnv(
|
|
689
|
+
undefined,
|
|
690
|
+
{ ...options, build: buildName },
|
|
691
|
+
appRoot
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
for (const { config, metadata } of configs) {
|
|
695
|
+
const output = formatConfig(config, metadata, options, appRoot)
|
|
696
|
+
const filename = fileWriter.generateFilename(
|
|
697
|
+
metadata.bundler,
|
|
698
|
+
metadata.environment,
|
|
699
|
+
metadata.configType,
|
|
700
|
+
options.format!,
|
|
701
|
+
metadata.buildName
|
|
702
|
+
)
|
|
703
|
+
const fullPath = resolve(targetDir, filename)
|
|
704
|
+
fileWriter.writeSingleFile(fullPath, output)
|
|
705
|
+
createdFiles.push(fullPath)
|
|
706
|
+
}
|
|
707
|
+
}
|
|
181
708
|
|
|
182
|
-
|
|
709
|
+
// Print summary and exit early
|
|
710
|
+
printDoctorSummary(createdFiles, targetDir)
|
|
711
|
+
return
|
|
712
|
+
}
|
|
713
|
+
} catch (error: unknown) {
|
|
714
|
+
// If config file exists but is invalid, warn and fall through to default behavior
|
|
715
|
+
const errorMessage =
|
|
716
|
+
error instanceof Error ? error.message : String(error)
|
|
717
|
+
console.log(`\n⚠️ Config file found but invalid: ${errorMessage}`)
|
|
718
|
+
console.log("Falling back to default doctor mode...\n")
|
|
719
|
+
}
|
|
720
|
+
}
|
|
183
721
|
|
|
184
|
-
|
|
185
|
-
console.log(
|
|
186
|
-
|
|
722
|
+
// Default behavior: hardcoded configs
|
|
723
|
+
console.log("\nExporting all development and production configs...")
|
|
724
|
+
console.log("")
|
|
187
725
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
metadata.configType,
|
|
194
|
-
options.format!
|
|
195
|
-
)
|
|
726
|
+
const configsToExport = [
|
|
727
|
+
{ label: "development (HMR)", env: "development" as const, hmr: true },
|
|
728
|
+
{ label: "development", env: "development" as const, hmr: false },
|
|
729
|
+
{ label: "production", env: "production" as const, hmr: false }
|
|
730
|
+
]
|
|
196
731
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
732
|
+
for (const { label, env, hmr } of configsToExport) {
|
|
733
|
+
console.log(`\n📦 Loading ${label} configuration...`)
|
|
734
|
+
|
|
735
|
+
// Clear and restore environment to prevent leakage between builds
|
|
736
|
+
clearBuildEnvironmentVariables()
|
|
737
|
+
restoreBuildEnvironmentVariables(savedEnv)
|
|
738
|
+
|
|
739
|
+
// Set WEBPACK_SERVE for HMR config
|
|
740
|
+
if (hmr) {
|
|
741
|
+
process.env.WEBPACK_SERVE = "true"
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const configs = await loadConfigsForEnv(env, options, appRoot)
|
|
745
|
+
|
|
746
|
+
for (const { config, metadata } of configs) {
|
|
747
|
+
const output = formatConfig(config, metadata, options, appRoot)
|
|
748
|
+
|
|
749
|
+
// Adjust filename for HMR config
|
|
750
|
+
let filename: string
|
|
751
|
+
if (
|
|
752
|
+
hmr &&
|
|
753
|
+
(metadata.configType === "client" || metadata.configType === "all")
|
|
754
|
+
) {
|
|
755
|
+
/**
|
|
756
|
+
* HMR Mode Filename Logic:
|
|
757
|
+
* - When WEBPACK_SERVE=true, webpack-dev-server runs and HMR is enabled
|
|
758
|
+
* - HMR only applies to client bundles (server bundles don't use HMR)
|
|
759
|
+
* - If configType is "all", we still only generate client file for HMR
|
|
760
|
+
* because the server bundle is identical to non-HMR development
|
|
761
|
+
* - Filename uses "client" type and "development-hmr" build name to
|
|
762
|
+
* distinguish it from regular development client bundle
|
|
763
|
+
*/
|
|
764
|
+
filename = fileWriter.generateFilename(
|
|
765
|
+
metadata.bundler,
|
|
766
|
+
metadata.environment,
|
|
767
|
+
"client",
|
|
768
|
+
options.format!,
|
|
769
|
+
"development-hmr"
|
|
770
|
+
)
|
|
771
|
+
} else {
|
|
772
|
+
filename = fileWriter.generateFilename(
|
|
773
|
+
metadata.bundler,
|
|
774
|
+
metadata.environment,
|
|
775
|
+
metadata.configType,
|
|
776
|
+
options.format!,
|
|
777
|
+
metadata.buildName
|
|
778
|
+
)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const fullPath = resolve(targetDir, filename)
|
|
782
|
+
const fileOutput: FileOutput = { filename, content: output, metadata }
|
|
783
|
+
fileWriter.writeSingleFile(fullPath, output)
|
|
784
|
+
createdFiles.push(fullPath)
|
|
785
|
+
}
|
|
201
786
|
}
|
|
787
|
+
|
|
788
|
+
printDoctorSummary(createdFiles, targetDir)
|
|
789
|
+
} finally {
|
|
790
|
+
// Restore original environment
|
|
791
|
+
restoreBuildEnvironmentVariables(savedEnv)
|
|
202
792
|
}
|
|
793
|
+
}
|
|
203
794
|
|
|
795
|
+
function printDoctorSummary(createdFiles: string[], targetDir: string): void {
|
|
204
796
|
// Print summary
|
|
205
797
|
console.log("\n" + "=".repeat(80))
|
|
206
798
|
console.log("✅ Export Complete!")
|
|
@@ -239,11 +831,13 @@ async function runSaveMode(
|
|
|
239
831
|
options: ExportOptions,
|
|
240
832
|
appRoot: string
|
|
241
833
|
): Promise<void> {
|
|
242
|
-
|
|
834
|
+
const env = options.env || "development"
|
|
835
|
+
console.log(`[Config Exporter] Exporting ${env} configs`)
|
|
243
836
|
|
|
244
837
|
const fileWriter = new FileWriter()
|
|
245
|
-
const targetDir = options.saveDir
|
|
246
|
-
const configs = await loadConfigsForEnv(options.env
|
|
838
|
+
const targetDir = options.saveDir! // Set by applyDefaults
|
|
839
|
+
const configs = await loadConfigsForEnv(options.env, options, appRoot)
|
|
840
|
+
const createdFiles: string[] = []
|
|
247
841
|
|
|
248
842
|
if (options.output) {
|
|
249
843
|
// Single file output
|
|
@@ -257,7 +851,9 @@ async function runSaveMode(
|
|
|
257
851
|
options,
|
|
258
852
|
appRoot
|
|
259
853
|
)
|
|
260
|
-
|
|
854
|
+
const fullPath = resolve(options.output)
|
|
855
|
+
fileWriter.writeSingleFile(fullPath, output)
|
|
856
|
+
createdFiles.push(fullPath)
|
|
261
857
|
} else {
|
|
262
858
|
// Multi-file output (one per config)
|
|
263
859
|
for (const { config, metadata } of configs) {
|
|
@@ -266,11 +862,20 @@ async function runSaveMode(
|
|
|
266
862
|
metadata.bundler,
|
|
267
863
|
metadata.environment,
|
|
268
864
|
metadata.configType,
|
|
269
|
-
options.format
|
|
865
|
+
options.format!,
|
|
866
|
+
metadata.buildName
|
|
270
867
|
)
|
|
271
|
-
|
|
868
|
+
const fullPath = resolve(targetDir, filename)
|
|
869
|
+
fileWriter.writeSingleFile(fullPath, output)
|
|
870
|
+
createdFiles.push(fullPath)
|
|
272
871
|
}
|
|
273
872
|
}
|
|
873
|
+
|
|
874
|
+
// Log all created files
|
|
875
|
+
console.log(`\n[Config Exporter] Created ${createdFiles.length} file(s):`)
|
|
876
|
+
createdFiles.forEach((file) => {
|
|
877
|
+
console.log(` ✓ ${file}`)
|
|
878
|
+
})
|
|
274
879
|
}
|
|
275
880
|
|
|
276
881
|
async function runStdoutMode(
|
|
@@ -289,17 +894,116 @@ async function runStdoutMode(
|
|
|
289
894
|
console.log(output)
|
|
290
895
|
}
|
|
291
896
|
|
|
897
|
+
async function runSingleFileMode(
|
|
898
|
+
options: ExportOptions,
|
|
899
|
+
appRoot: string
|
|
900
|
+
): Promise<void> {
|
|
901
|
+
const configs = await loadConfigsForEnv(options.env!, options, appRoot)
|
|
902
|
+
const combined = configs.map((c) => c.config)
|
|
903
|
+
const metadata = configs[0].metadata
|
|
904
|
+
metadata.configCount = combined.length
|
|
905
|
+
|
|
906
|
+
const config = combined.length === 1 ? combined[0] : combined
|
|
907
|
+
const output = formatConfig(config, metadata, options, appRoot)
|
|
908
|
+
|
|
909
|
+
const fileWriter = new FileWriter()
|
|
910
|
+
const filePath = resolve(process.cwd(), options.output!)
|
|
911
|
+
fileWriter.writeSingleFile(filePath, output)
|
|
912
|
+
}
|
|
913
|
+
|
|
292
914
|
async function loadConfigsForEnv(
|
|
293
|
-
env: "development" | "production" | "test",
|
|
915
|
+
env: "development" | "production" | "test" | undefined,
|
|
294
916
|
options: ExportOptions,
|
|
295
917
|
appRoot: string
|
|
296
918
|
): Promise<Array<{ config: any; metadata: ConfigMetadata }>> {
|
|
297
|
-
|
|
298
|
-
|
|
919
|
+
let bundler: "webpack" | "rspack"
|
|
920
|
+
let buildName: string | undefined
|
|
921
|
+
let buildOutputs: string[] = []
|
|
922
|
+
let customConfigFile: string | undefined
|
|
923
|
+
let bundlerEnvArgs: string[] = []
|
|
924
|
+
let finalEnv: "development" | "production" | "test"
|
|
925
|
+
|
|
926
|
+
// If using config file build
|
|
927
|
+
if (options.build) {
|
|
928
|
+
// Use a temporary env for auto-detection, will be overridden by build config
|
|
929
|
+
const tempEnv = env || "development"
|
|
930
|
+
const loader = new ConfigFileLoader(options.configFile)
|
|
931
|
+
const defaultBundler = await autoDetectBundler(tempEnv, appRoot)
|
|
932
|
+
const resolvedBuild = loader.resolveBuild(
|
|
933
|
+
options.build,
|
|
934
|
+
options,
|
|
935
|
+
defaultBundler
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
bundler = resolvedBuild.bundler
|
|
939
|
+
buildName = resolvedBuild.name
|
|
940
|
+
buildOutputs = resolvedBuild.outputs
|
|
941
|
+
customConfigFile = resolvedBuild.configFile
|
|
942
|
+
bundlerEnvArgs = resolvedBuild.bundlerEnvArgs
|
|
943
|
+
|
|
944
|
+
// Set environment variables from config
|
|
945
|
+
// Security: Only allow specific environment variables to prevent malicious configs
|
|
946
|
+
const DANGEROUS_ENV_VARS = [
|
|
947
|
+
"PATH",
|
|
948
|
+
"HOME",
|
|
949
|
+
"LD_PRELOAD",
|
|
950
|
+
"LD_LIBRARY_PATH",
|
|
951
|
+
"DYLD_LIBRARY_PATH",
|
|
952
|
+
"DYLD_INSERT_LIBRARIES"
|
|
953
|
+
]
|
|
954
|
+
|
|
955
|
+
for (const [key, value] of Object.entries(resolvedBuild.environment)) {
|
|
956
|
+
if (DANGEROUS_ENV_VARS.includes(key)) {
|
|
957
|
+
console.warn(
|
|
958
|
+
`[Config Exporter] Warning: Skipping dangerous environment variable: ${key}`
|
|
959
|
+
)
|
|
960
|
+
continue
|
|
961
|
+
}
|
|
962
|
+
if (!(BUILD_ENV_VARS as readonly string[]).includes(key)) {
|
|
963
|
+
console.warn(
|
|
964
|
+
`[Config Exporter] Warning: Skipping non-whitelisted environment variable: ${key}. ` +
|
|
965
|
+
`Allowed variables are: ${BUILD_ENV_VARS.join(", ")}`
|
|
966
|
+
)
|
|
967
|
+
continue
|
|
968
|
+
}
|
|
969
|
+
process.env[key] = value
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Determine final env: CLI flag > build config NODE_ENV > default
|
|
973
|
+
if (options.env) {
|
|
974
|
+
finalEnv = options.env
|
|
975
|
+
} else if (resolvedBuild.environment.NODE_ENV) {
|
|
976
|
+
const nodeEnv = resolvedBuild.environment.NODE_ENV
|
|
977
|
+
const allowedEnvs = ["development", "production", "test"]
|
|
978
|
+
if (allowedEnvs.includes(nodeEnv)) {
|
|
979
|
+
finalEnv = nodeEnv as "development" | "production" | "test"
|
|
980
|
+
} else {
|
|
981
|
+
throw new Error(
|
|
982
|
+
`Invalid NODE_ENV value in config: "${nodeEnv}". ` +
|
|
983
|
+
`Allowed values are: ${allowedEnvs.join(", ")}.`
|
|
984
|
+
)
|
|
985
|
+
}
|
|
986
|
+
} else {
|
|
987
|
+
finalEnv = "development"
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Sync process.env to reflect resolved environment
|
|
991
|
+
process.env.NODE_ENV = finalEnv
|
|
992
|
+
// Determine RAILS_ENV: CLI env option > build config RAILS_ENV > finalEnv
|
|
993
|
+
const railsEnv =
|
|
994
|
+
options.env || resolvedBuild.environment.RAILS_ENV || finalEnv
|
|
995
|
+
process.env.RAILS_ENV = railsEnv
|
|
996
|
+
} else {
|
|
997
|
+
// No build config - use CLI env or default
|
|
998
|
+
finalEnv = env || "development"
|
|
299
999
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
1000
|
+
// Auto-detect bundler if not specified
|
|
1001
|
+
bundler = options.bundler || (await autoDetectBundler(finalEnv, appRoot))
|
|
1002
|
+
|
|
1003
|
+
// Set environment variables
|
|
1004
|
+
process.env.NODE_ENV = finalEnv
|
|
1005
|
+
process.env.RAILS_ENV = finalEnv
|
|
1006
|
+
}
|
|
303
1007
|
|
|
304
1008
|
if (options.clientOnly) {
|
|
305
1009
|
process.env.CLIENT_BUNDLE_ONLY = "yes"
|
|
@@ -308,12 +1012,16 @@ async function loadConfigsForEnv(
|
|
|
308
1012
|
}
|
|
309
1013
|
|
|
310
1014
|
// Find and load config file
|
|
311
|
-
const configFile =
|
|
1015
|
+
const configFile =
|
|
1016
|
+
customConfigFile || findConfigFile(bundler, appRoot, finalEnv)
|
|
312
1017
|
// Quiet mode for cleaner output - only show if verbose or errors
|
|
313
1018
|
if (process.env.VERBOSE) {
|
|
314
1019
|
console.log(`[Config Exporter] Loading config: ${configFile}`)
|
|
315
|
-
console.log(`[Config Exporter] Environment: ${
|
|
1020
|
+
console.log(`[Config Exporter] Environment: ${finalEnv}`)
|
|
316
1021
|
console.log(`[Config Exporter] Bundler: ${bundler}`)
|
|
1022
|
+
if (buildName) {
|
|
1023
|
+
console.log(`[Config Exporter] Build: ${buildName}`)
|
|
1024
|
+
}
|
|
317
1025
|
}
|
|
318
1026
|
|
|
319
1027
|
// Load the config
|
|
@@ -331,8 +1039,26 @@ async function loadConfigsForEnv(
|
|
|
331
1039
|
}
|
|
332
1040
|
|
|
333
1041
|
// Clear require cache for config file and all related modules
|
|
334
|
-
|
|
335
|
-
|
|
1042
|
+
/**
|
|
1043
|
+
* AGGRESSIVE REQUIRE CACHE CLEARING
|
|
1044
|
+
*
|
|
1045
|
+
* Why: This tool can load multiple environments (dev/prod) and builds in a
|
|
1046
|
+
* single process. Node's require cache prevents modules from re-evaluating,
|
|
1047
|
+
* which causes stale environment values (NODE_ENV, etc.) to persist.
|
|
1048
|
+
*
|
|
1049
|
+
* What: Clears cache for:
|
|
1050
|
+
* - Webpack/rspack config files (they read process.env)
|
|
1051
|
+
* - Shakapacker modules (env detection, config loading)
|
|
1052
|
+
* - Config directory files (custom helpers that may read env)
|
|
1053
|
+
*
|
|
1054
|
+
* Trade-offs:
|
|
1055
|
+
* - More reliable: Ensures each build gets fresh environment
|
|
1056
|
+
* - Potentially brittle: String matching on paths (but comprehensive)
|
|
1057
|
+
* - Performance: Minimal impact since this runs per-build, not per-file
|
|
1058
|
+
*
|
|
1059
|
+
* Maintenance: If adding new shakapacker modules that read env vars,
|
|
1060
|
+
* ensure their paths are covered by the patterns below.
|
|
1061
|
+
*/
|
|
336
1062
|
const configDir = dirname(configFile)
|
|
337
1063
|
Object.keys(require.cache).forEach((key) => {
|
|
338
1064
|
if (
|
|
@@ -359,6 +1085,40 @@ async function loadConfigsForEnv(
|
|
|
359
1085
|
loadedConfig = loadedConfig.default
|
|
360
1086
|
}
|
|
361
1087
|
|
|
1088
|
+
// Handle function exports (webpack config functions)
|
|
1089
|
+
if (typeof loadedConfig === "function") {
|
|
1090
|
+
// Webpack config functions receive (env, argv) parameters
|
|
1091
|
+
// Build env object from bundler_env args if available
|
|
1092
|
+
const envObject: Record<string, any> = {}
|
|
1093
|
+
if (bundlerEnvArgs && bundlerEnvArgs.length > 0) {
|
|
1094
|
+
// Parse --env key=value or --env key into object
|
|
1095
|
+
for (let i = 0; i < bundlerEnvArgs.length; i += 2) {
|
|
1096
|
+
if (bundlerEnvArgs[i] === "--env") {
|
|
1097
|
+
const envArg = bundlerEnvArgs[i + 1]
|
|
1098
|
+
if (envArg.includes("=")) {
|
|
1099
|
+
const [key, value] = envArg.split("=")
|
|
1100
|
+
envObject[key] = value
|
|
1101
|
+
} else {
|
|
1102
|
+
envObject[envArg] = true
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const argv = { mode: finalEnv }
|
|
1109
|
+
try {
|
|
1110
|
+
loadedConfig = loadedConfig(envObject, argv)
|
|
1111
|
+
} catch (error: unknown) {
|
|
1112
|
+
const errorMessage =
|
|
1113
|
+
error instanceof Error ? error.message : String(error)
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
`Failed to execute config function: ${errorMessage}\n` +
|
|
1116
|
+
`Config file: ${configFile}\n` +
|
|
1117
|
+
`Environment: ${JSON.stringify(envObject)}`
|
|
1118
|
+
)
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
362
1122
|
// Determine config type and split if array
|
|
363
1123
|
const configs: any[] = Array.isArray(loadedConfig)
|
|
364
1124
|
? loadedConfig
|
|
@@ -368,8 +1128,27 @@ async function loadConfigsForEnv(
|
|
|
368
1128
|
configs.forEach((cfg, index) => {
|
|
369
1129
|
let configType: "client" | "server" | "all" = "all"
|
|
370
1130
|
|
|
371
|
-
//
|
|
372
|
-
if (
|
|
1131
|
+
// Use outputs from build config if available
|
|
1132
|
+
if (
|
|
1133
|
+
buildOutputs.length > 0 &&
|
|
1134
|
+
index < buildOutputs.length &&
|
|
1135
|
+
buildOutputs[index]
|
|
1136
|
+
) {
|
|
1137
|
+
const outputValue = buildOutputs[index]
|
|
1138
|
+
// Validate the output value is a valid config type
|
|
1139
|
+
if (
|
|
1140
|
+
outputValue === "client" ||
|
|
1141
|
+
outputValue === "server" ||
|
|
1142
|
+
outputValue === "all"
|
|
1143
|
+
) {
|
|
1144
|
+
configType = outputValue
|
|
1145
|
+
} else {
|
|
1146
|
+
throw new Error(
|
|
1147
|
+
`Invalid output type '${outputValue}' at index ${index} in build '${buildName}'. ` +
|
|
1148
|
+
`Allowed values are: client, server, all`
|
|
1149
|
+
)
|
|
1150
|
+
}
|
|
1151
|
+
} else if (configs.length === 2) {
|
|
373
1152
|
// Likely client and server configs
|
|
374
1153
|
configType = index === 0 ? "client" : "server"
|
|
375
1154
|
} else if (options.clientOnly) {
|
|
@@ -381,15 +1160,17 @@ async function loadConfigsForEnv(
|
|
|
381
1160
|
const metadata: ConfigMetadata = {
|
|
382
1161
|
exportedAt: new Date().toISOString(),
|
|
383
1162
|
bundler,
|
|
384
|
-
environment:
|
|
1163
|
+
environment: finalEnv,
|
|
385
1164
|
configFile,
|
|
386
1165
|
configType,
|
|
387
1166
|
configCount: configs.length,
|
|
1167
|
+
buildName,
|
|
388
1168
|
environmentVariables: {
|
|
389
1169
|
NODE_ENV: process.env.NODE_ENV,
|
|
390
1170
|
RAILS_ENV: process.env.RAILS_ENV,
|
|
391
1171
|
CLIENT_BUNDLE_ONLY: process.env.CLIENT_BUNDLE_ONLY,
|
|
392
|
-
SERVER_BUNDLE_ONLY: process.env.SERVER_BUNDLE_ONLY
|
|
1172
|
+
SERVER_BUNDLE_ONLY: process.env.SERVER_BUNDLE_ONLY,
|
|
1173
|
+
WEBPACK_SERVE: process.env.WEBPACK_SERVE
|
|
393
1174
|
}
|
|
394
1175
|
}
|
|
395
1176
|
|
|
@@ -526,46 +1307,86 @@ function cleanConfig(obj: any, rootPath: string): any {
|
|
|
526
1307
|
return clean(obj)
|
|
527
1308
|
}
|
|
528
1309
|
|
|
529
|
-
|
|
1310
|
+
/**
|
|
1311
|
+
* Loads and returns shakapacker.yml configuration
|
|
1312
|
+
*/
|
|
1313
|
+
function loadShakapackerConfig(
|
|
530
1314
|
env: string,
|
|
531
1315
|
appRoot: string
|
|
532
|
-
):
|
|
1316
|
+
): { bundler: "webpack" | "rspack"; configPath: string } {
|
|
533
1317
|
try {
|
|
534
|
-
const
|
|
1318
|
+
const configFilePath =
|
|
535
1319
|
process.env.SHAKAPACKER_CONFIG ||
|
|
536
1320
|
resolve(appRoot, "config/shakapacker.yml")
|
|
537
1321
|
|
|
538
|
-
if (existsSync(
|
|
539
|
-
const config: any = loadYaml(readFileSync(
|
|
1322
|
+
if (existsSync(configFilePath)) {
|
|
1323
|
+
const config: any = loadYaml(readFileSync(configFilePath, "utf8"))
|
|
540
1324
|
const envConfig = config[env] || config.default || {}
|
|
1325
|
+
|
|
1326
|
+
// Get bundler
|
|
541
1327
|
const bundler = envConfig.assets_bundler || "webpack"
|
|
542
1328
|
if (bundler !== "webpack" && bundler !== "rspack") {
|
|
543
1329
|
console.warn(
|
|
544
1330
|
`[Config Exporter] Invalid bundler '${bundler}' in shakapacker.yml, defaulting to webpack`
|
|
545
1331
|
)
|
|
546
|
-
return
|
|
1332
|
+
return {
|
|
1333
|
+
bundler: "webpack",
|
|
1334
|
+
configPath: bundler === "rspack" ? "config/rspack" : "config/webpack"
|
|
1335
|
+
}
|
|
547
1336
|
}
|
|
548
|
-
|
|
549
|
-
|
|
1337
|
+
|
|
1338
|
+
// Get config path
|
|
1339
|
+
const customConfigPath = envConfig.assets_bundler_config_path
|
|
1340
|
+
const configPath =
|
|
1341
|
+
customConfigPath ||
|
|
1342
|
+
(bundler === "rspack" ? "config/rspack" : "config/webpack")
|
|
1343
|
+
|
|
1344
|
+
console.log(
|
|
1345
|
+
`[Config Exporter] Auto-detected bundler: ${bundler}, config path: ${configPath}`
|
|
1346
|
+
)
|
|
1347
|
+
return { bundler, configPath }
|
|
550
1348
|
}
|
|
551
|
-
} catch (error:
|
|
1349
|
+
} catch (error: unknown) {
|
|
552
1350
|
console.warn(
|
|
553
|
-
`[Config Exporter] Error
|
|
1351
|
+
`[Config Exporter] Error loading shakapacker config, defaulting to webpack`
|
|
554
1352
|
)
|
|
555
1353
|
}
|
|
556
1354
|
|
|
557
|
-
return "webpack"
|
|
1355
|
+
return { bundler: "webpack", configPath: "config/webpack" }
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* Auto-detects bundler from shakapacker.yml
|
|
1360
|
+
*
|
|
1361
|
+
* Error Handling Strategy:
|
|
1362
|
+
* - Invalid bundler → warns and defaults to webpack (graceful fallback)
|
|
1363
|
+
* - Config read errors → warns and defaults to webpack (graceful fallback)
|
|
1364
|
+
*
|
|
1365
|
+
* Rationale for warnings vs errors:
|
|
1366
|
+
* - This reads shakapacker.yml (infrastructure config), not user build config
|
|
1367
|
+
* - Failures here should not block the tool; defaulting to webpack is safe
|
|
1368
|
+
* - Contrast with NODE_ENV validation in build configs, which throws errors
|
|
1369
|
+
* because invalid NODE_ENV would produce incorrect builds
|
|
1370
|
+
*/
|
|
1371
|
+
async function autoDetectBundler(
|
|
1372
|
+
env: string,
|
|
1373
|
+
appRoot: string
|
|
1374
|
+
): Promise<"webpack" | "rspack"> {
|
|
1375
|
+
const { bundler } = loadShakapackerConfig(env, appRoot)
|
|
1376
|
+
return bundler
|
|
558
1377
|
}
|
|
559
1378
|
|
|
560
1379
|
function findConfigFile(
|
|
561
1380
|
bundler: "webpack" | "rspack",
|
|
562
|
-
appRoot: string
|
|
1381
|
+
appRoot: string,
|
|
1382
|
+
env: string
|
|
563
1383
|
): string {
|
|
1384
|
+
const { configPath } = loadShakapackerConfig(env, appRoot)
|
|
564
1385
|
const extensions = ["ts", "js"]
|
|
565
1386
|
|
|
566
1387
|
if (bundler === "rspack") {
|
|
567
1388
|
for (const ext of extensions) {
|
|
568
|
-
const rspackPath = resolve(appRoot, `
|
|
1389
|
+
const rspackPath = resolve(appRoot, configPath, `rspack.config.${ext}`)
|
|
569
1390
|
if (existsSync(rspackPath)) {
|
|
570
1391
|
return rspackPath
|
|
571
1392
|
}
|
|
@@ -574,14 +1395,14 @@ function findConfigFile(
|
|
|
574
1395
|
|
|
575
1396
|
// Fall back to webpack config
|
|
576
1397
|
for (const ext of extensions) {
|
|
577
|
-
const webpackPath = resolve(appRoot, `
|
|
1398
|
+
const webpackPath = resolve(appRoot, configPath, `webpack.config.${ext}`)
|
|
578
1399
|
if (existsSync(webpackPath)) {
|
|
579
1400
|
return webpackPath
|
|
580
1401
|
}
|
|
581
1402
|
}
|
|
582
1403
|
|
|
583
1404
|
throw new Error(
|
|
584
|
-
`Could not find ${bundler} config file. Expected:
|
|
1405
|
+
`Could not find ${bundler} config file. Expected: ${configPath}/${bundler}.config.{js,ts}`
|
|
585
1406
|
)
|
|
586
1407
|
}
|
|
587
1408
|
|
|
@@ -622,62 +1443,3 @@ function setupNodePath(appRoot: string): void {
|
|
|
622
1443
|
require("module").Module._initPaths()
|
|
623
1444
|
}
|
|
624
1445
|
}
|
|
625
|
-
|
|
626
|
-
function showHelp(): void {
|
|
627
|
-
console.log(`
|
|
628
|
-
Shakapacker Config Exporter
|
|
629
|
-
|
|
630
|
-
Exports webpack or rspack configuration in a verbose, human-readable format
|
|
631
|
-
for comparison and analysis.
|
|
632
|
-
|
|
633
|
-
QUICK START (for troubleshooting):
|
|
634
|
-
bin/export-bundler-config --doctor
|
|
635
|
-
|
|
636
|
-
Exports annotated YAML configs for both development and production.
|
|
637
|
-
Creates separate files for client and server bundles.
|
|
638
|
-
Best for debugging, AI analysis, and comparing configurations.
|
|
639
|
-
|
|
640
|
-
Usage:
|
|
641
|
-
bin/export-bundler-config [options]
|
|
642
|
-
|
|
643
|
-
Options:
|
|
644
|
-
--doctor Export all configs for troubleshooting (dev + prod, annotated YAML)
|
|
645
|
-
--save Save to auto-generated file(s) (default: YAML format)
|
|
646
|
-
--save-dir=<directory> Directory for output files (requires --save)
|
|
647
|
-
--bundler=webpack|rspack Specify bundler (auto-detected if not provided)
|
|
648
|
-
--env=development|production|test Node environment (default: development, ignored with --doctor)
|
|
649
|
-
--client-only Generate only client config (sets CLIENT_BUNDLE_ONLY=yes)
|
|
650
|
-
--server-only Generate only server config (sets SERVER_BUNDLE_ONLY=yes)
|
|
651
|
-
--output=<filename> Output to specific file (default: stdout)
|
|
652
|
-
--depth=<number> Inspection depth (default: 20, use 'null' for unlimited)
|
|
653
|
-
--format=yaml|json|inspect Output format (default: inspect for stdout, yaml for --save/--doctor)
|
|
654
|
-
--no-annotate Disable inline documentation (YAML only)
|
|
655
|
-
--verbose Show full output without compact mode
|
|
656
|
-
--help, -h Show this help message
|
|
657
|
-
|
|
658
|
-
Note: --client-only and --server-only are mutually exclusive.
|
|
659
|
-
--save-dir requires --save.
|
|
660
|
-
--output and --save-dir are mutually exclusive.
|
|
661
|
-
If neither --client-only nor --server-only specified, both configs are generated.
|
|
662
|
-
|
|
663
|
-
Examples:
|
|
664
|
-
# RECOMMENDED: Export everything for troubleshooting
|
|
665
|
-
bin/export-bundler-config --doctor
|
|
666
|
-
# Creates: webpack-development-client.yaml, webpack-development-server.yaml,
|
|
667
|
-
# webpack-production-client.yaml, webpack-production-server.yaml
|
|
668
|
-
|
|
669
|
-
# Save current environment configs
|
|
670
|
-
bin/export-bundler-config --save
|
|
671
|
-
# Creates: webpack-development-client.yaml, webpack-development-server.yaml
|
|
672
|
-
|
|
673
|
-
# Save to specific directory
|
|
674
|
-
bin/export-bundler-config --save --save-dir=./debug
|
|
675
|
-
|
|
676
|
-
# Export only client config for production
|
|
677
|
-
bin/export-bundler-config --save --env=production --client-only
|
|
678
|
-
# Creates: webpack-production-client.yaml
|
|
679
|
-
|
|
680
|
-
# View config in terminal (stdout)
|
|
681
|
-
bin/export-bundler-config
|
|
682
|
-
`)
|
|
683
|
-
}
|