Skip to content

Commit 0b2275f

Browse files
justin808claude
andcommitted
Fix cross-platform path handling and YAML serialization issues
**Path handling fixes:** - Replace string startsWith checks with path.relative for proper cross-platform path validation - Fix makePathRelative to use path.relative instead of hardcoded "/" separator - Update validateOutputPath to use os.tmpdir() instead of hardcoded "/tmp" - Add proper isAbsolute guards to prevent false positives on Windows **YAML serialization fixes:** - Fix multiline string indentation to use dynamic indent based on nesting level - Fix array item indentation to properly indent nested objects and multiline strings - Emit array markers on separate lines for complex content with proper 2-space sub-indentation - Fix function source escaping to use serializeString helper for proper quote/special char handling **TypeScript config loading:** - Add ts-node/register/transpile-only support for loading .ts config files - Handle ES module default exports by checking for .default property - Prevent ERR_UNKNOWN_FILE_EXTENSION errors when requiring TypeScript configs All changes maintain backward compatibility and improve reliability across platforms. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 471fc3e commit 0b2275f

3 files changed

Lines changed: 54 additions & 19 deletions

File tree

package/configExporter/cli.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,21 @@ async function loadConfigsForEnv(
273273
console.log(`[Config Exporter] Bundler: ${bundler}`)
274274

275275
// Load the config
276+
// Register ts-node for TypeScript config files
277+
if (configFile.endsWith(".ts")) {
278+
// eslint-disable-next-line @typescript-eslint/no-var-requires
279+
require("ts-node/register/transpile-only")
280+
}
281+
276282
// eslint-disable-next-line @typescript-eslint/no-var-requires
277283
delete require.cache[require.resolve(configFile)]
278-
const loadedConfig = require(configFile)
284+
// eslint-disable-next-line @typescript-eslint/no-var-requires
285+
let loadedConfig = require(configFile)
286+
287+
// Handle ES module default export
288+
if (typeof loadedConfig === "object" && "default" in loadedConfig) {
289+
loadedConfig = loadedConfig.default
290+
}
279291

280292
// Determine config type and split if array
281293
const configs: any[] = Array.isArray(loadedConfig)

package/configExporter/fileWriter.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { writeFileSync, mkdirSync, existsSync } from "fs"
2-
import { resolve, dirname } from "path"
2+
import { resolve, dirname, relative, isAbsolute } from "path"
3+
import { tmpdir } from "os"
34
import { FileOutput } from "./types"
45

56
/**
@@ -73,9 +74,13 @@ export class FileWriter {
7374
const absPath = resolve(outputPath)
7475
const cwd = process.cwd()
7576

76-
if (!absPath.startsWith(cwd) && !absPath.startsWith("/tmp")) {
77+
const isWithin = (base: string, target: string) => {
78+
const rel = relative(base, target)
79+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))
80+
}
81+
if (!isWithin(cwd, absPath) && !isWithin(tmpdir(), absPath)) {
7782
console.warn(
78-
`[Config Exporter] Warning: Writing to ${absPath} which is outside current directory`
83+
`[Config Exporter] Warning: Writing to ${absPath} which is outside current directory (${cwd}) or temp (${tmpdir()})`
7984
)
8085
}
8186
}

package/configExporter/yamlSerializer.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ConfigMetadata } from "./types"
22
import { getDocForKey } from "./configDocs"
3+
import { relative, isAbsolute } from "path"
34

45
/**
56
* Serializes webpack/rspack config to YAML format with optional inline documentation.
@@ -59,7 +60,7 @@ export class YamlSerializer {
5960
}
6061

6162
if (typeof value === "string") {
62-
return this.serializeString(value)
63+
return this.serializeString(value, indent)
6364
}
6465

6566
if (typeof value === "function") {
@@ -81,14 +82,15 @@ export class YamlSerializer {
8182
return String(value)
8283
}
8384

84-
private serializeString(str: string): string {
85+
private serializeString(str: string, indent: number = 0): string {
8586
// Make absolute paths relative for cleaner output
8687
const cleaned = this.makePathRelative(str)
8788

8889
// Handle multiline strings
8990
if (cleaned.includes("\n")) {
9091
const lines = cleaned.split("\n")
91-
return "|\n" + lines.map((line) => " " + line).join("\n")
92+
const lineIndent = " ".repeat(indent + 2)
93+
return "|\n" + lines.map((line) => lineIndent + line).join("\n")
9294
}
9395

9496
// Escape strings that need quoting
@@ -111,18 +113,19 @@ export class YamlSerializer {
111113
const source = fn.toString()
112114

113115
// Compact the source: remove extra whitespace but keep it readable
114-
const compacted = source
116+
let compacted = source
115117
.split("\n")
116118
.map((line) => line.trim())
117119
.filter((line) => line.length > 0)
118120
.join(" ")
119121

120122
// For very long functions, truncate with ellipsis
121123
if (compacted.length > 500) {
122-
return `"${compacted.substring(0, 500)}..."`
124+
compacted = compacted.substring(0, 500) + "..."
123125
}
124126

125-
return `"${compacted}"`
127+
// Use serializeString to properly escape the function source
128+
return this.serializeString(compacted)
126129
}
127130

128131
private serializeArray(arr: any[], indent: number, keyPath: string): string {
@@ -132,15 +135,26 @@ export class YamlSerializer {
132135

133136
const lines: string[] = []
134137
const itemIndent = " ".repeat(indent + 2)
138+
const contentIndent = " ".repeat(indent + 4)
135139

136140
arr.forEach((item, index) => {
137141
const itemPath = `${keyPath}[${index}]`
138-
const serialized = this.serializeValue(item, indent + 2, itemPath)
142+
const serialized = this.serializeValue(item, indent + 4, itemPath)
139143

140144
if (typeof item === "object" && !Array.isArray(item) && item !== null) {
141-
// For objects in arrays, use '- key: value' format
142-
lines.push(`${itemIndent}- ${serialized.trim()}`)
145+
// For objects in arrays, emit marker on its own line and indent content
146+
lines.push(`${itemIndent}-`)
147+
serialized.split("\n").forEach((line: string) => {
148+
lines.push(contentIndent + line)
149+
})
150+
} else if (serialized.includes("\n")) {
151+
// For multiline values, emit marker on its own line and indent content
152+
lines.push(`${itemIndent}-`)
153+
serialized.split("\n").forEach((line: string) => {
154+
lines.push(contentIndent + line)
155+
})
143156
} else {
157+
// For simple values, keep on same line
144158
lines.push(`${itemIndent}- ${serialized}`)
145159
}
146160
})
@@ -207,16 +221,20 @@ export class YamlSerializer {
207221

208222
private makePathRelative(str: string): string {
209223
if (typeof str !== "string") return str
224+
if (!isAbsolute(str)) return str
210225

211-
// Convert absolute paths to relative paths
212-
if (str.startsWith(this.appRoot + "/")) {
213-
return "./" + str.substring(this.appRoot.length + 1)
214-
}
226+
// Convert absolute paths to relative paths using path.relative
227+
const rel = relative(this.appRoot, str)
215228

216-
if (str === this.appRoot) {
229+
if (rel === "") {
217230
return "."
218231
}
219232

220-
return str
233+
// If path is outside appRoot or already absolute, keep original
234+
if (rel.startsWith("..") || isAbsolute(rel)) {
235+
return str
236+
}
237+
238+
return "./" + rel
221239
}
222240
}

0 commit comments

Comments
 (0)