diff --git a/scripts/src/cicd-gates.ts b/scripts/src/cicd-gates.ts index ab008e6c..39258921 100644 --- a/scripts/src/cicd-gates.ts +++ b/scripts/src/cicd-gates.ts @@ -35,7 +35,7 @@ export async function runBranchFollowerGate(registry: BranchFollowerRegistry, fo ].join("\n"); const startedAt = Date.now(); const command = runKubeScript(registry, options, script, "", (timeoutSeconds + registry.controller.budgets.reconcileTransportGraceSeconds) * 1000); - const parsed = command.exitCode === 0 ? parseFirstJsonObject(command.stdout) : null; + const parsed = command.exitCode === 0 ? parseLastJsonObject(command.stdout) : null; const ok = command.exitCode === 0 && parsed !== null && parsed.ok === true; return { ok, @@ -353,6 +353,41 @@ function parseFirstJsonObject(text: string): Record | null { return null; } +function parseLastJsonObject(text: string): Record | null { + let last: Record | null = null; + let offset = 0; + while (offset < text.length) { + const start = text.indexOf("{", offset); + if (start < 0) break; + let depth = 0; + let inString = false; + let escaped = false; + let foundEnd = false; + for (let index = start; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === "\\") escaped = true; + else if (char === "\"") inString = false; + } else if (char === "\"") inString = true; + else if (char === "{") depth += 1; + else if (char === "}" && --depth === 0) { + try { + const parsed = JSON.parse(text.slice(start, index + 1)) as unknown; + if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) last = parsed as Record; + } catch { + // Keep scanning because controller bootstrap may print non-contract JSON-like fragments. + } + offset = index + 1; + foundEnd = true; + break; + } + } + if (!foundEnd) break; + } + return last; +} + function safeName(value: string): string { return value.toLowerCase().replace(/[^a-z0-9-]+/gu, "-").replace(/-+/gu, "-").replace(/^-|-$/gu, "").slice(0, 32); }