renderJobTo writes a single job to b directly, with no intermediate string allocation.
(b *strings.Builder, job *Job)
| 204 | |
| 205 | // renderJobTo writes a single job to b directly, with no intermediate string allocation. |
| 206 | func (jm *JobManager) renderJobTo(b *strings.Builder, job *Job) { |
| 207 | jobLog.Printf("Rendering job: %s (steps=%d, needs=%d, reusable=%t)", job.Name, len(job.Steps), len(job.Needs), job.Uses != "") |
| 208 | |
| 209 | fmt.Fprintf(b, " %s:\n", job.Name) |
| 210 | |
| 211 | // Add display name if present |
| 212 | if job.DisplayName != "" { |
| 213 | fmt.Fprintf(b, " name: %s\n", job.DisplayName) |
| 214 | } |
| 215 | |
| 216 | // Add needs clause if there are dependencies |
| 217 | if len(job.Needs) > 0 { |
| 218 | if len(job.Needs) == 1 { |
| 219 | fmt.Fprintf(b, " needs: %s\n", job.Needs[0]) |
| 220 | } else { |
| 221 | b.WriteString(" needs:\n") |
| 222 | // Sort needs for consistent output |
| 223 | sortedNeeds := make([]string, len(job.Needs)) |
| 224 | copy(sortedNeeds, job.Needs) |
| 225 | sort.Strings(sortedNeeds) |
| 226 | for _, dep := range sortedNeeds { |
| 227 | fmt.Fprintf(b, " - %s\n", dep) |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | // Add if condition if present |
| 233 | if job.If != "" { |
| 234 | // Add zizmor ignore comment if this job has workflow_run safety checks |
| 235 | if job.HasWorkflowRunSafetyChecks { |
| 236 | b.WriteString(" # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation\n") |
| 237 | } |
| 238 | |
| 239 | // Check if expression is multiline or longer than MaxExpressionLineLength characters |
| 240 | if hasNewlineInStringLiteral(job.If) { |
| 241 | // The condition contains a literal newline inside a GitHub Actions expression string literal |
| 242 | // (e.g. startsWith(body, '/command\n') for matching bot comments with attribution metadata). |
| 243 | // Use a YAML double-quoted scalar so the \n escape is preserved as a real newline after |
| 244 | // YAML parsing, which GitHub Actions then evaluates correctly. |
| 245 | fmt.Fprintf(b, " if: \"%s\"\n", escapeForYAMLDoubleQuoted(job.If)) |
| 246 | } else if strings.Contains(job.If, "\n") || len(job.If) > int(constants.MaxExpressionLineLength) { |
| 247 | // Use YAML folded style for multiline expressions or long expressions |
| 248 | b.WriteString(" if: >\n") |
| 249 | |
| 250 | if strings.Contains(job.If, "\n") { |
| 251 | // Already has newlines, use existing logic |
| 252 | lines := strings.SplitSeq(job.If, "\n") |
| 253 | for line := range lines { |
| 254 | if strings.TrimSpace(line) != "" { |
| 255 | fmt.Fprintf(b, " %s\n", strings.TrimSpace(line)) |
| 256 | } |
| 257 | } |
| 258 | } else { |
| 259 | // Long single-line expression, break it into logical lines |
| 260 | lines := BreakLongExpression(job.If) |
| 261 | for _, line := range lines { |
| 262 | fmt.Fprintf(b, " %s\n", strings.TrimSpace(line)) |
| 263 | } |