shakapacker 9.1.0 → 9.2.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/.gitignore +3 -0
- data/CHANGELOG.md +32 -1
- data/Gemfile.lock +1 -1
- data/README.md +21 -0
- data/bin/export-bundler-config +11 -0
- data/docs/deployment.md +52 -8
- data/docs/releasing.md +197 -0
- data/docs/rspack_migration_guide.md +28 -0
- data/docs/troubleshooting.md +124 -23
- data/lib/install/bin/export-bundler-config +11 -0
- data/lib/install/bin/shakapacker +1 -1
- data/lib/install/bin/shakapacker-dev-server +1 -1
- data/lib/shakapacker/bundler_switcher.rb +7 -0
- data/lib/shakapacker/configuration.rb +28 -2
- data/lib/shakapacker/doctor.rb +16 -0
- data/lib/shakapacker/rspack_runner.rb +1 -1
- data/lib/shakapacker/runner.rb +1 -1
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/webpack_runner.rb +1 -1
- data/lib/shakapacker.rb +10 -0
- data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
- data/package/configExporter/cli.ts +683 -0
- data/package/configExporter/configDocs.ts +102 -0
- data/package/configExporter/fileWriter.ts +92 -0
- data/package/configExporter/index.ts +5 -0
- data/package/configExporter/types.ts +36 -0
- data/package/configExporter/yamlSerializer.ts +266 -0
- data/package-lock.json +2 -2
- data/package.json +2 -1
- data/yarn.lock +151 -360
- metadata +12 -2
@@ -0,0 +1,102 @@
|
|
1
|
+
/**
|
2
|
+
* Documentation mapping for webpack/rspack configuration keys.
|
3
|
+
* Used to add inline comments when exporting configs with --annotate flag.
|
4
|
+
*/
|
5
|
+
|
6
|
+
export const CONFIG_DOCS: Record<string, string> = {
|
7
|
+
mode: "Controls webpack optimization: 'development' (fast builds, detailed errors), 'production' (optimized, minified), or 'none'",
|
8
|
+
output: "Configuration for output bundles",
|
9
|
+
"output.filename":
|
10
|
+
"Bundle name template. [name]=entry name, [contenthash]=content-based hash for caching, [chunkhash]=chunk hash",
|
11
|
+
"output.path": "Absolute directory path where bundles are written",
|
12
|
+
"output.publicPath":
|
13
|
+
"URL prefix for loading assets in the browser (used by webpack for code splitting and asset loading)",
|
14
|
+
"output.chunkFilename":
|
15
|
+
"Template for non-entry chunk files created by code splitting",
|
16
|
+
"output.assetModuleFilename":
|
17
|
+
"Template for asset module filenames (images, fonts, etc.)",
|
18
|
+
"output.crossOriginLoading":
|
19
|
+
"Cross-origin loading setting for script tags: 'anonymous', 'use-credentials', or false",
|
20
|
+
"output.globalObject":
|
21
|
+
"Global object reference for UMD builds (e.g., 'this', 'window', 'global')",
|
22
|
+
devtool:
|
23
|
+
"Source map style: 'source-map' (full, slow), 'eval-source-map' (full, fast rebuild), 'cheap-source-map' (fast, less detail), false (none)",
|
24
|
+
optimization: "Code optimization settings",
|
25
|
+
"optimization.minimize":
|
26
|
+
"Enable/disable minification (true in production mode)",
|
27
|
+
"optimization.minimizer":
|
28
|
+
"Array of minimizer plugins (e.g., TerserPlugin, CssMinimizerPlugin)",
|
29
|
+
"optimization.splitChunks":
|
30
|
+
"Code splitting configuration - extracts common dependencies into separate chunks",
|
31
|
+
"optimization.runtimeChunk":
|
32
|
+
"Extract webpack runtime into separate chunk: 'single' (one runtime for all), true (one per entry), false (inline)",
|
33
|
+
"optimization.moduleIds":
|
34
|
+
"Module ID generation strategy: 'deterministic' (stable), 'named' (readable), 'natural' (numeric order)",
|
35
|
+
"optimization.chunkIds":
|
36
|
+
"Chunk ID generation strategy: 'deterministic', 'named', 'natural'",
|
37
|
+
module: "Configures how different file types are processed",
|
38
|
+
"module.rules":
|
39
|
+
"Array of rules defining loaders and processing for different file types",
|
40
|
+
plugins:
|
41
|
+
"Array of webpack plugins to apply (e.g., HtmlWebpackPlugin, MiniCssExtractPlugin)",
|
42
|
+
resolve: "Module resolution configuration",
|
43
|
+
"resolve.extensions":
|
44
|
+
"File extensions to try when resolving modules (e.g., ['.js', '.jsx', '.ts', '.tsx'])",
|
45
|
+
"resolve.modules":
|
46
|
+
"Directories to search when resolving modules (e.g., ['node_modules', 'app/javascript'])",
|
47
|
+
"resolve.alias":
|
48
|
+
"Create import aliases for modules (e.g., @components -> ./src/components)",
|
49
|
+
resolveLoader: "Configuration for resolving loaders",
|
50
|
+
"resolveLoader.modules": "Directories to search for loaders",
|
51
|
+
entry:
|
52
|
+
"Entry points for the application - where webpack starts building the dependency graph",
|
53
|
+
devServer: "Webpack dev server configuration (HMR, proxying, HTTPS, etc.)",
|
54
|
+
"devServer.port": "Port number for dev server (default: 8080)",
|
55
|
+
"devServer.host": "Host for dev server (e.g., 'localhost', '0.0.0.0')",
|
56
|
+
"devServer.hot": "Enable Hot Module Replacement (HMR)",
|
57
|
+
"devServer.https": "Enable HTTPS for dev server",
|
58
|
+
stats:
|
59
|
+
"Controls bundle information display: 'normal', 'verbose', 'minimal', 'errors-only', 'none'",
|
60
|
+
bail: "Fail the build on first error (true) or continue and report all errors (false)",
|
61
|
+
performance: "Performance budget configuration",
|
62
|
+
"performance.maxAssetSize":
|
63
|
+
"Maximum size (in bytes) for individual assets before webpack warns",
|
64
|
+
"performance.maxEntrypointSize":
|
65
|
+
"Maximum size (in bytes) for entry point bundles before webpack warns",
|
66
|
+
target:
|
67
|
+
"Build target environment: 'web' (browser), 'node' (Node.js), 'webworker', etc.",
|
68
|
+
externals:
|
69
|
+
"Dependencies to exclude from bundle (assumed to be available in runtime environment)",
|
70
|
+
cache:
|
71
|
+
"Build caching configuration: false (disabled), { type: 'memory' }, or { type: 'filesystem' }",
|
72
|
+
watch: "Enable watch mode - rebuild on file changes",
|
73
|
+
watchOptions: "Watch mode configuration (polling, ignored files, etc.)"
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Get documentation for a specific config key path.
|
78
|
+
* Supports nested paths like 'output.filename'.
|
79
|
+
*/
|
80
|
+
export function getDocForKey(keyPath: string): string | undefined {
|
81
|
+
return CONFIG_DOCS[keyPath]
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Get documentation for a key, trying parent paths if exact match not found.
|
86
|
+
* E.g., 'output.filename' -> tries 'output.filename', then 'output'
|
87
|
+
*/
|
88
|
+
export function getDocForKeyWithFallback(keyPath: string): string | undefined {
|
89
|
+
// Try exact match first
|
90
|
+
if (CONFIG_DOCS[keyPath]) {
|
91
|
+
return CONFIG_DOCS[keyPath]
|
92
|
+
}
|
93
|
+
|
94
|
+
// Try parent key
|
95
|
+
const parts = keyPath.split(".")
|
96
|
+
if (parts.length > 1) {
|
97
|
+
const parentKey = parts.slice(0, -1).join(".")
|
98
|
+
return CONFIG_DOCS[parentKey]
|
99
|
+
}
|
100
|
+
|
101
|
+
return undefined
|
102
|
+
}
|
@@ -0,0 +1,92 @@
|
|
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
|
+
writeMultipleFiles(outputs: FileOutput[], targetDir: string): void {
|
15
|
+
// Ensure directory exists
|
16
|
+
this.ensureDirectory(targetDir)
|
17
|
+
|
18
|
+
// Write each file
|
19
|
+
outputs.forEach((output) => {
|
20
|
+
const safeName = basename(output.filename)
|
21
|
+
const filePath = resolve(targetDir, safeName)
|
22
|
+
this.validateOutputPath(filePath)
|
23
|
+
this.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
|
+
writeSingleFile(filePath: string, content: string, quiet = false): void {
|
36
|
+
// Ensure parent directory exists
|
37
|
+
const dir = dirname(filePath)
|
38
|
+
this.ensureDirectory(dir)
|
39
|
+
|
40
|
+
this.validateOutputPath(filePath)
|
41
|
+
this.writeFile(filePath, content)
|
42
|
+
if (!quiet && process.env.VERBOSE) {
|
43
|
+
console.log(`[Config Exporter] Config exported to: ${filePath}`)
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Generate filename for a config export
|
49
|
+
* Format: {bundler}-{env}-{type}.{ext}
|
50
|
+
* Examples:
|
51
|
+
* webpack-development-client.yaml
|
52
|
+
* rspack-production-server.yaml
|
53
|
+
* webpack-test-all.json
|
54
|
+
*/
|
55
|
+
generateFilename(
|
56
|
+
bundler: string,
|
57
|
+
env: string,
|
58
|
+
configType: "client" | "server" | "all",
|
59
|
+
format: "yaml" | "json" | "inspect"
|
60
|
+
): string {
|
61
|
+
const ext = format === "yaml" ? "yaml" : format === "json" ? "json" : "txt"
|
62
|
+
return `${bundler}-${env}-${configType}.${ext}`
|
63
|
+
}
|
64
|
+
|
65
|
+
private writeFile(filePath: string, content: string): void {
|
66
|
+
writeFileSync(filePath, content, "utf8")
|
67
|
+
}
|
68
|
+
|
69
|
+
private ensureDirectory(dir: string): void {
|
70
|
+
if (!existsSync(dir)) {
|
71
|
+
mkdirSync(dir, { recursive: true })
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Validate output path and warn if writing outside cwd
|
77
|
+
*/
|
78
|
+
validateOutputPath(outputPath: string): void {
|
79
|
+
const absPath = resolve(outputPath)
|
80
|
+
const cwd = process.cwd()
|
81
|
+
|
82
|
+
const isWithin = (base: string, target: string) => {
|
83
|
+
const rel = relative(base, target)
|
84
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))
|
85
|
+
}
|
86
|
+
if (!isWithin(cwd, absPath) && !isWithin(tmpdir(), absPath)) {
|
87
|
+
console.warn(
|
88
|
+
`[Config Exporter] Warning: Writing to ${absPath} which is outside current directory (${cwd}) or temp (${tmpdir()})`
|
89
|
+
)
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
export interface ExportOptions {
|
2
|
+
doctor?: boolean
|
3
|
+
save?: boolean
|
4
|
+
saveDir?: string
|
5
|
+
bundler?: "webpack" | "rspack"
|
6
|
+
env?: "development" | "production" | "test"
|
7
|
+
clientOnly?: boolean
|
8
|
+
serverOnly?: boolean
|
9
|
+
output?: string
|
10
|
+
format?: "yaml" | "json" | "inspect"
|
11
|
+
annotate?: boolean
|
12
|
+
verbose?: boolean
|
13
|
+
depth?: number | null
|
14
|
+
help?: boolean
|
15
|
+
}
|
16
|
+
|
17
|
+
export interface ConfigMetadata {
|
18
|
+
exportedAt: string
|
19
|
+
bundler: string
|
20
|
+
environment: string
|
21
|
+
configFile: string
|
22
|
+
configType: "client" | "server" | "all"
|
23
|
+
configCount: number
|
24
|
+
environmentVariables: {
|
25
|
+
NODE_ENV?: string
|
26
|
+
RAILS_ENV?: string
|
27
|
+
CLIENT_BUNDLE_ONLY?: string
|
28
|
+
SERVER_BUNDLE_ONLY?: string
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
export interface FileOutput {
|
33
|
+
filename: string
|
34
|
+
content: string
|
35
|
+
metadata: ConfigMetadata
|
36
|
+
}
|
@@ -0,0 +1,266 @@
|
|
1
|
+
import { ConfigMetadata } from "./types"
|
2
|
+
import { getDocForKey } from "./configDocs"
|
3
|
+
import { relative, isAbsolute } from "path"
|
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
|
+
private appRoot: string
|
12
|
+
|
13
|
+
constructor(options: { annotate: boolean; appRoot: string }) {
|
14
|
+
this.annotate = options.annotate
|
15
|
+
this.appRoot = options.appRoot
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Serialize a config object to YAML string with metadata header
|
20
|
+
*/
|
21
|
+
serialize(config: any, metadata: ConfigMetadata): string {
|
22
|
+
const output: string[] = []
|
23
|
+
|
24
|
+
// Add metadata header
|
25
|
+
output.push(this.createHeader(metadata))
|
26
|
+
output.push("")
|
27
|
+
|
28
|
+
// Serialize the config
|
29
|
+
output.push(this.serializeValue(config, 0, ""))
|
30
|
+
|
31
|
+
return output.join("\n")
|
32
|
+
}
|
33
|
+
|
34
|
+
private createHeader(metadata: ConfigMetadata): string {
|
35
|
+
const lines: string[] = []
|
36
|
+
lines.push("# " + "=".repeat(77))
|
37
|
+
lines.push("# Webpack/Rspack Configuration Export")
|
38
|
+
lines.push(`# Generated: ${metadata.exportedAt}`)
|
39
|
+
lines.push(`# Environment: ${metadata.environment}`)
|
40
|
+
lines.push(`# Bundler: ${metadata.bundler}`)
|
41
|
+
lines.push(`# Config Type: ${metadata.configType}`)
|
42
|
+
if (metadata.configCount > 1) {
|
43
|
+
lines.push(`# Total Configs: ${metadata.configCount}`)
|
44
|
+
}
|
45
|
+
lines.push("# " + "=".repeat(77))
|
46
|
+
return lines.join("\n")
|
47
|
+
}
|
48
|
+
|
49
|
+
private serializeValue(value: any, indent: number, keyPath: string): string {
|
50
|
+
if (value === null || value === undefined) {
|
51
|
+
return "null"
|
52
|
+
}
|
53
|
+
|
54
|
+
if (typeof value === "boolean") {
|
55
|
+
return value.toString()
|
56
|
+
}
|
57
|
+
|
58
|
+
if (typeof value === "number") {
|
59
|
+
return value.toString()
|
60
|
+
}
|
61
|
+
|
62
|
+
if (typeof value === "string") {
|
63
|
+
return this.serializeString(value, indent)
|
64
|
+
}
|
65
|
+
|
66
|
+
if (typeof value === "function") {
|
67
|
+
return this.serializeFunction(value)
|
68
|
+
}
|
69
|
+
|
70
|
+
if (value instanceof RegExp) {
|
71
|
+
return this.serializeString(value.toString())
|
72
|
+
}
|
73
|
+
|
74
|
+
if (Array.isArray(value)) {
|
75
|
+
return this.serializeArray(value, indent, keyPath)
|
76
|
+
}
|
77
|
+
|
78
|
+
if (typeof value === "object") {
|
79
|
+
return this.serializeObject(value, indent, keyPath)
|
80
|
+
}
|
81
|
+
|
82
|
+
return String(value)
|
83
|
+
}
|
84
|
+
|
85
|
+
private serializeString(str: string, indent: number = 0): string {
|
86
|
+
// Make absolute paths relative for cleaner output
|
87
|
+
const cleaned = this.makePathRelative(str)
|
88
|
+
|
89
|
+
// Handle multiline strings
|
90
|
+
if (cleaned.includes("\n")) {
|
91
|
+
const lines = cleaned.split("\n")
|
92
|
+
const lineIndent = " ".repeat(indent + 2)
|
93
|
+
return "|\n" + lines.map((line) => lineIndent + line).join("\n")
|
94
|
+
}
|
95
|
+
|
96
|
+
// Escape strings that need quoting
|
97
|
+
if (
|
98
|
+
cleaned.includes(":") ||
|
99
|
+
cleaned.includes("#") ||
|
100
|
+
cleaned.includes("'") ||
|
101
|
+
cleaned.includes('"') ||
|
102
|
+
cleaned.startsWith(" ") ||
|
103
|
+
cleaned.endsWith(" ")
|
104
|
+
) {
|
105
|
+
// Escape backslashes first, then quotes to avoid double-escaping
|
106
|
+
return `"${cleaned.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
107
|
+
}
|
108
|
+
|
109
|
+
return cleaned
|
110
|
+
}
|
111
|
+
|
112
|
+
private serializeFunction(fn: Function): string {
|
113
|
+
// Get function source code
|
114
|
+
const source = fn.toString()
|
115
|
+
|
116
|
+
// Pretty-print function: maintain readable formatting
|
117
|
+
const lines = source.split("\n")
|
118
|
+
|
119
|
+
// For very long functions, truncate
|
120
|
+
const maxLines = 50
|
121
|
+
const truncated = lines.length > maxLines
|
122
|
+
const displayLines = truncated ? lines.slice(0, maxLines) : lines
|
123
|
+
|
124
|
+
// Clean up indentation while preserving structure
|
125
|
+
const minIndent = Math.min(
|
126
|
+
...displayLines
|
127
|
+
.filter((l) => l.trim().length > 0)
|
128
|
+
.map((l) => l.match(/^\s*/)?.[0].length || 0)
|
129
|
+
)
|
130
|
+
|
131
|
+
const formatted =
|
132
|
+
displayLines.map((line) => line.substring(minIndent)).join("\n") +
|
133
|
+
(truncated ? "\n..." : "")
|
134
|
+
|
135
|
+
// Use serializeString to properly handle multiline
|
136
|
+
return this.serializeString(formatted)
|
137
|
+
}
|
138
|
+
|
139
|
+
private serializeArray(arr: any[], indent: number, keyPath: string): string {
|
140
|
+
if (arr.length === 0) {
|
141
|
+
return "[]"
|
142
|
+
}
|
143
|
+
|
144
|
+
const lines: string[] = []
|
145
|
+
const itemIndent = " ".repeat(indent + 2)
|
146
|
+
const contentIndent = " ".repeat(indent + 4)
|
147
|
+
|
148
|
+
arr.forEach((item, index) => {
|
149
|
+
const itemPath = `${keyPath}[${index}]`
|
150
|
+
const serialized = this.serializeValue(item, indent + 4, itemPath)
|
151
|
+
|
152
|
+
// Add documentation for array items if available
|
153
|
+
if (this.annotate) {
|
154
|
+
const doc = getDocForKey(itemPath)
|
155
|
+
if (doc) {
|
156
|
+
lines.push(`${itemIndent}# ${doc}`)
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
if (typeof item === "object" && !Array.isArray(item) && item !== null) {
|
161
|
+
// For objects in arrays, emit marker on its own line and indent content
|
162
|
+
lines.push(`${itemIndent}-`)
|
163
|
+
serialized
|
164
|
+
.split("\n")
|
165
|
+
.filter((line: string) => line.trim().length > 0)
|
166
|
+
.forEach((line: string) => {
|
167
|
+
lines.push(contentIndent + line)
|
168
|
+
})
|
169
|
+
} else if (serialized.includes("\n")) {
|
170
|
+
// For multiline values, emit marker on its own line and indent content
|
171
|
+
lines.push(`${itemIndent}-`)
|
172
|
+
serialized
|
173
|
+
.split("\n")
|
174
|
+
.filter((line: string) => line.trim().length > 0)
|
175
|
+
.forEach((line: string) => {
|
176
|
+
lines.push(contentIndent + line)
|
177
|
+
})
|
178
|
+
} else {
|
179
|
+
// For simple values, keep on same line
|
180
|
+
lines.push(`${itemIndent}- ${serialized}`)
|
181
|
+
}
|
182
|
+
})
|
183
|
+
|
184
|
+
return "\n" + lines.join("\n")
|
185
|
+
}
|
186
|
+
|
187
|
+
private serializeObject(obj: any, indent: number, keyPath: string): string {
|
188
|
+
const keys = Object.keys(obj)
|
189
|
+
if (keys.length === 0) {
|
190
|
+
return "{}"
|
191
|
+
}
|
192
|
+
|
193
|
+
const lines: string[] = []
|
194
|
+
const keyIndent = " ".repeat(indent)
|
195
|
+
const valueIndent = " ".repeat(indent + 2)
|
196
|
+
|
197
|
+
keys.forEach((key) => {
|
198
|
+
const value = obj[key]
|
199
|
+
const fullKeyPath = keyPath ? `${keyPath}.${key}` : key
|
200
|
+
|
201
|
+
// Add documentation comment if available and annotation is enabled
|
202
|
+
if (this.annotate) {
|
203
|
+
const doc = getDocForKey(fullKeyPath)
|
204
|
+
if (doc) {
|
205
|
+
lines.push(`${keyIndent}# ${doc}`)
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
// Handle multiline strings specially with block scalar
|
210
|
+
if (typeof value === "string" && value.includes("\n")) {
|
211
|
+
lines.push(`${keyIndent}${key}: |`)
|
212
|
+
for (const line of value.split("\n")) {
|
213
|
+
lines.push(`${valueIndent}${line}`)
|
214
|
+
}
|
215
|
+
} else if (
|
216
|
+
typeof value === "object" &&
|
217
|
+
value !== null &&
|
218
|
+
!Array.isArray(value)
|
219
|
+
) {
|
220
|
+
if (Object.keys(value).length === 0) {
|
221
|
+
lines.push(`${keyIndent}${key}: {}`)
|
222
|
+
} else {
|
223
|
+
lines.push(`${keyIndent}${key}:`)
|
224
|
+
const nestedLines = this.serializeObject(
|
225
|
+
value,
|
226
|
+
indent + 2,
|
227
|
+
fullKeyPath
|
228
|
+
)
|
229
|
+
lines.push(nestedLines)
|
230
|
+
}
|
231
|
+
} else if (Array.isArray(value)) {
|
232
|
+
if (value.length === 0) {
|
233
|
+
lines.push(`${keyIndent}${key}: []`)
|
234
|
+
} else {
|
235
|
+
lines.push(`${keyIndent}${key}:`)
|
236
|
+
const arrayLines = this.serializeArray(value, indent + 2, fullKeyPath)
|
237
|
+
lines.push(arrayLines)
|
238
|
+
}
|
239
|
+
} else {
|
240
|
+
const serialized = this.serializeValue(value, indent + 2, fullKeyPath)
|
241
|
+
lines.push(`${keyIndent}${key}: ${serialized}`)
|
242
|
+
}
|
243
|
+
})
|
244
|
+
|
245
|
+
return lines.join("\n")
|
246
|
+
}
|
247
|
+
|
248
|
+
private makePathRelative(str: string): string {
|
249
|
+
if (typeof str !== "string") return str
|
250
|
+
if (!isAbsolute(str)) return str
|
251
|
+
|
252
|
+
// Convert absolute paths to relative paths using path.relative
|
253
|
+
const rel = relative(this.appRoot, str)
|
254
|
+
|
255
|
+
if (rel === "") {
|
256
|
+
return "."
|
257
|
+
}
|
258
|
+
|
259
|
+
// If path is outside appRoot or already absolute, keep original
|
260
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
261
|
+
return str
|
262
|
+
}
|
263
|
+
|
264
|
+
return "./" + rel
|
265
|
+
}
|
266
|
+
}
|
data/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "shakapacker",
|
3
|
-
"version": "9.
|
3
|
+
"version": "9.2.0",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "shakapacker",
|
9
|
-
"version": "9.
|
9
|
+
"version": "9.2.0",
|
10
10
|
"license": "MIT",
|
11
11
|
"dependencies": {
|
12
12
|
"js-yaml": "^4.1.0",
|
data/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "shakapacker",
|
3
|
-
"version": "9.
|
3
|
+
"version": "9.2.0",
|
4
4
|
"description": "Use webpack to manage app-like JavaScript modules in Rails",
|
5
5
|
"homepage": "https://github.com/shakacode/shakapacker",
|
6
6
|
"bugs": {
|
@@ -21,6 +21,7 @@
|
|
21
21
|
"./rspack": "./package/rspack/index.js",
|
22
22
|
"./swc": "./package/swc/index.js",
|
23
23
|
"./esbuild": "./package/esbuild/index.js",
|
24
|
+
"./configExporter": "./package/configExporter/index.js",
|
24
25
|
"./package.json": "./package.json",
|
25
26
|
"./package/babel/preset.js": "./package/babel/preset.js",
|
26
27
|
"./package/*": "./package/*"
|