CompileWorkflowWithValidation compiles a workflow with always-on YAML validation for CLI usage
(ctx context.Context, compiler *workflow.Compiler, filePath string, opts CompileValidationOptions)
| 29 | |
| 30 | // CompileWorkflowWithValidation compiles a workflow with always-on YAML validation for CLI usage |
| 31 | func CompileWorkflowWithValidation(ctx context.Context, compiler *workflow.Compiler, filePath string, opts CompileValidationOptions) error { |
| 32 | compileValidationLog.Printf("Compiling workflow with validation: file=%s, strict=%v, validateSHAs=%v", filePath, opts.Strict, opts.ValidateActionSHAs) |
| 33 | |
| 34 | compiler.SetContext(ctx) |
| 35 | |
| 36 | // Set workflow identifier for schedule scattering (use repository-relative path for stability) |
| 37 | relPath, err := getRepositoryRelativePath(filePath) |
| 38 | if err != nil { |
| 39 | compileValidationLog.Printf("Warning: failed to get repository-relative path for %s: %v", filePath, err) |
| 40 | // Fallback to basename if we can't get relative path |
| 41 | relPath = filepath.Base(filePath) |
| 42 | } |
| 43 | compiler.SetWorkflowIdentifier(relPath) |
| 44 | |
| 45 | // Set repository slug for this specific file (may differ from CWD's repo) |
| 46 | // Uses SetRepositorySlugIfUnlocked so that an explicit --schedule-seed flag is never overridden. |
| 47 | fileRepoSlug := getRepositorySlugFromRemoteForPath(filePath) |
| 48 | if fileRepoSlug != "" { |
| 49 | if compiler.IsRepositorySlugLocked() { |
| 50 | compileValidationLog.Printf("Repository slug from file remote (%s) ignored: overridden via --schedule-seed (%s)", fileRepoSlug, compiler.GetRepositorySlug()) |
| 51 | } else { |
| 52 | compiler.SetRepositorySlugIfUnlocked(fileRepoSlug) |
| 53 | compileValidationLog.Printf("Repository slug for file set: %s", fileRepoSlug) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | // Compile the workflow first |
| 58 | if err := compiler.CompileWorkflow(filePath); err != nil { |
| 59 | compileValidationLog.Printf("Workflow compilation failed: %v", err) |
| 60 | return err |
| 61 | } |
| 62 | |
| 63 | // Always validate that the generated lock file is valid YAML (CLI requirement) |
| 64 | lockFile := stringutil.MarkdownToLockFile(filePath) |
| 65 | if _, err := os.Stat(lockFile); err != nil { |
| 66 | compileValidationLog.Print("Lock file not found, skipping validation (likely no-emit mode)") |
| 67 | // Lock file doesn't exist (likely due to no-emit), skip YAML validation |
| 68 | return nil |
| 69 | } |
| 70 | |
| 71 | compileValidationLog.Print("Validating generated lock file YAML syntax") |
| 72 | |
| 73 | lockContent, err := os.ReadFile(lockFile) |
| 74 | if err != nil { |
| 75 | return fmt.Errorf("failed to read generated lock file for validation: %w", err) |
| 76 | } |
| 77 | |
| 78 | // Validate the lock file is valid YAML |
| 79 | var yamlValidationTest any |
| 80 | if err := yaml.Unmarshal(lockContent, &yamlValidationTest); err != nil { |
| 81 | return fmt.Errorf("generated lock file is not valid YAML: %w", err) |
| 82 | } |
| 83 | |
| 84 | // Validate action SHAs if requested |
| 85 | if opts.ValidateActionSHAs { |
| 86 | compileValidationLog.Print("Validating action SHAs in lock file") |
| 87 | // Use the compiler's shared action cache to benefit from cached resolutions |
| 88 | actionCache := compiler.GetSharedActionCache() |