writeWorkflowOutput writes the compiled workflow to the lock file and handles console output formatting.
(lockFile, yamlContent string, markdownPath string)
| 250 | // writeWorkflowOutput writes the compiled workflow to the lock file |
| 251 | // and handles console output formatting. |
| 252 | func (c *Compiler) writeWorkflowOutput(lockFile, yamlContent string, markdownPath string) error { |
| 253 | // Write to lock file (unless noEmit is enabled) |
| 254 | if c.noEmit { |
| 255 | workflowLog.Print("Validation completed - no lock file generated (--no-emit enabled)") |
| 256 | } else { |
| 257 | workflowLog.Printf("Writing output to: %s", lockFile) |
| 258 | |
| 259 | // Check if content has actually changed |
| 260 | contentUnchanged := false |
| 261 | if existingContent, err := os.ReadFile(lockFile); err == nil { |
| 262 | if normalizeHeredocDelimiters(string(existingContent)) == normalizeHeredocDelimiters(yamlContent) { |
| 263 | // Content is identical (modulo random heredoc tokens) - skip write to preserve timestamp |
| 264 | contentUnchanged = true |
| 265 | workflowLog.Print("Lock file content unchanged - skipping write to preserve timestamp") |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | // Only write if content has changed |
| 270 | if !contentUnchanged { |
| 271 | if err := os.WriteFile(lockFile, []byte(yamlContent), constants.FilePermPublic); err != nil { |
| 272 | return formatCompilerError(lockFile, "error", fmt.Sprintf("failed to write lock file: %v", err), err) |
| 273 | } |
| 274 | workflowLog.Print("Lock file written successfully") |
| 275 | } |
| 276 | |
| 277 | // Validate file size after writing |
| 278 | if lockFileInfo, err := os.Stat(lockFile); err == nil { |
| 279 | if lockFileInfo.Size() > MaxLockFileSize { |
| 280 | lockSize := console.FormatFileSize(lockFileInfo.Size()) |
| 281 | maxSize := console.FormatFileSize(MaxLockFileSize) |
| 282 | warningMsg := fmt.Sprintf("Generated lock file size (%s) exceeds recommended maximum size (%s)", lockSize, maxSize) |
| 283 | fmt.Fprintln(os.Stderr, console.FormatWarningMessage(warningMsg)) |
| 284 | } |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | // Display success message with file size if we generated a lock file (unless quiet mode) |
| 289 | if !c.quiet { |
| 290 | if c.noEmit { |
| 291 | fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(console.ToRelativePath(markdownPath))) |
| 292 | } else { |
| 293 | // Get the size of the generated lock file for display |
| 294 | if lockFileInfo, err := os.Stat(lockFile); err == nil { |
| 295 | lockSize := console.FormatFileSize(lockFileInfo.Size()) |
| 296 | fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("%s (%s)", console.ToRelativePath(markdownPath), lockSize))) |
| 297 | } else { |
| 298 | // Fallback to original display if we can't get file info |
| 299 | fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(console.ToRelativePath(markdownPath))) |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | return nil |
| 304 | } |
| 305 | |
| 306 | // validateTemplateInjection checks compiled YAML for template injection vulnerabilities |
| 307 | // (unsafe GitHub Actions expressions used directly in run: blocks). |