Skip to content

Commit 8f94028

Browse files
committed
Fix YAML import to flatten multi-line script blocks and prevent nested arrays
1 parent 8dc3de2 commit 8f94028

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@noxify/gitlab-ci-builder": patch
3+
---
4+
5+
Fix regression in YAML import script formatting where multi-line simple script blocks produced a nested array structure (`script: [[...]]`) instead of a flat array (`script: [ ... ]`).
6+
7+
The importer now flattens multi-line simple commands correctly and preserves template literals only when shell operators (pipes, heredoc, redirects, continuations) are present.
8+

src/import.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,59 @@ function formatValue(value: unknown, indentLevel: number): string {
234234
// Special handling for script-related properties
235235
const scriptProperties = ["script", "before_script", "after_script"]
236236
if (scriptProperties.includes(k)) {
237-
// For script arrays, format each element intelligently
237+
// For script arrays, format each element intelligently and FLATTEN
238238
if (Array.isArray(v)) {
239-
const formattedScripts = v.map((item) => formatScriptValue(item))
240-
return `${indent}${key}: [${formattedScripts.join(", ")}]`
239+
const flattened: string[] = []
240+
for (const item of v) {
241+
if (typeof item === "string") {
242+
// Detect simple multi-line string without shell operators and split into lines
243+
const hasNewline = item.includes("\n")
244+
if (hasNewline) {
245+
const shellOperatorPatterns = [
246+
/\\\n/, // Line continuation
247+
/<</, // Heredoc
248+
/(?<!\|)\|(?!\|)/, // Pipe (but not ||)
249+
/>>?/, // Redirect output
250+
/2>/, // Redirect stderr
251+
/&>/, // Redirect both
252+
/(?<!<)<(?!<)/, // Redirect input (but not <<)
253+
]
254+
const hasShellOperators = shellOperatorPatterns.some((p) => p.test(item))
255+
if (!hasShellOperators) {
256+
const linesSplit = item
257+
.split("\n")
258+
.map((l) => l.trim())
259+
.filter((l) => l.length > 0)
260+
if (linesSplit.length > 0) {
261+
for (const line of linesSplit) {
262+
flattened.push(JSON.stringify(line))
263+
}
264+
continue
265+
}
266+
}
267+
}
268+
}
269+
const formatted = formatScriptValue(item)
270+
// If formatted result itself looks like an array literal ["..."] we should expand it
271+
if (/^\[(?:.|\n)*\]$/.test(formatted)) {
272+
// Attempt to parse safely by wrapping in JSON (replace single backticks/template etc.)
273+
try {
274+
// Replace trailing commas if any and parse as JSON after ensuring double quotes
275+
const jsonCandidate = formatted
276+
const parsed = JSON.parse(jsonCandidate)
277+
if (Array.isArray(parsed)) {
278+
for (const inner of parsed) {
279+
flattened.push(JSON.stringify(inner))
280+
}
281+
continue
282+
}
283+
} catch {
284+
// Fallback: just push formatted string literal as is
285+
}
286+
}
287+
flattened.push(formatted)
288+
}
289+
return `${indent}${key}: [${flattened.join(", ")}]`
241290
}
242291
return `${indent}${key}: ${formatScriptValue(v)}`
243292
}

tests/script-formatting.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ test:
2323
echo "step3"
2424
`
2525
const result = fromYaml(yaml)
26-
expect(result).toContain(
27-
'script: [["echo \\"step1\\"", "echo \\"step2\\"", "echo \\"step3\\""]]',
28-
)
26+
expect(result).toContain('script: ["echo \\"step1\\"", "echo \\"step2\\"", "echo \\"step3\\""]')
27+
expect(result).not.toContain("script: [[")
2928
})
3029

3130
it("should preserve line continuation with backslash as template literal", () => {
@@ -105,8 +104,9 @@ test:
105104
`
106105
const result = fromYaml(yaml)
107106
expect(result).toContain('script: ["echo \\"simple\\"",')
108-
expect(result).toContain('["echo \\"multi1\\"", "echo \\"multi2\\""]')
107+
expect(result).toContain('"echo \\"multi1\\"", "echo \\"multi2\\""')
109108
expect(result).toContain("`complex")
109+
expect(result).not.toContain("script: [[")
110110
})
111111

112112
it("should handle before_script with shell operators", () => {
@@ -120,7 +120,9 @@ test:
120120
`
121121
const result = fromYaml(yaml)
122122
expect(result).toContain("before_script:")
123-
expect(result).toContain('["mkdir ~/.npm-global", "export PATH=$PATH:~/.npm-global/bin"')
123+
expect(result).toContain(
124+
'"mkdir ~/.npm-global", "export PATH=$PATH:~/.npm-global/bin", "npm i -g [email protected]"',
125+
)
124126
})
125127

126128
it("should handle after_script with continuations", () => {

0 commit comments

Comments
 (0)