From f17190cada4f1d1218d9a0b2f36adee7c731c9a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:00:02 +0000 Subject: [PATCH 1/4] Initial plan From db9806472b9c6f18abc611cee228ca0fa3b0d996 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:13:24 +0000 Subject: [PATCH 2/4] Skip campaign validation for specific non-campaign workflows Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_campaign_validation_test.go | 279 ++++++++++++++++++++ pkg/cli/compile_orchestration.go | 16 +- 2 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 pkg/cli/compile_campaign_validation_test.go diff --git a/pkg/cli/compile_campaign_validation_test.go b/pkg/cli/compile_campaign_validation_test.go new file mode 100644 index 0000000000..b30abbc176 --- /dev/null +++ b/pkg/cli/compile_campaign_validation_test.go @@ -0,0 +1,279 @@ +//go:build integration + +package cli + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/githubnext/gh-aw/pkg/testutil" + "github.com/githubnext/gh-aw/pkg/workflow" +) + +// initGitRepo initializes a git repository in the given directory +func initGitRepoForCampaignTest(t *testing.T, dir string) { + t.Helper() + cmd := exec.Command("git", "init") + cmd.Dir = dir + if err := cmd.Run(); err != nil { + t.Fatalf("failed to initialize git repo: %v", err) + } +} + +// TestCampaignValidationSkippedForSpecificWorkflow tests that campaign validation +// is skipped when compiling a specific non-campaign workflow +func TestCampaignValidationSkippedForSpecificWorkflow(t *testing.T) { + // Create a temporary directory + tmpDir := testutil.TempDir(t, "test-*") + + // Create workflows directory + workflowsDir := filepath.Join(tmpDir, ".github", "workflows") + if err := os.MkdirAll(workflowsDir, 0755); err != nil { + t.Fatalf("Failed to create workflows directory: %v", err) + } + + // Initialize git repo + initGitRepoForCampaignTest(t, tmpDir) + + // Create a simple non-campaign workflow + workflowPath := filepath.Join(workflowsDir, "test-workflow.md") + workflowContent := `--- +name: Test Workflow +engine: copilot +--- + +# Test Task + +This is a simple test workflow. +` + if err := os.WriteFile(workflowPath, []byte(workflowContent), 0600); err != nil { + t.Fatalf("Failed to create workflow file: %v", err) + } + + // Create an invalid campaign spec to verify validation is skipped + campaignDir := filepath.Join(tmpDir, ".github", "campaigns") + if err := os.MkdirAll(campaignDir, 0755); err != nil { + t.Fatalf("Failed to create campaigns directory: %v", err) + } + + invalidCampaignPath := filepath.Join(campaignDir, "invalid.campaign.yml") + invalidCampaignContent := `id: invalid-campaign +# Missing required 'workflows' field +name: Invalid Campaign +description: This campaign is missing the required workflows field +` + if err := os.WriteFile(invalidCampaignPath, []byte(invalidCampaignContent), 0600); err != nil { + t.Fatalf("Failed to create invalid campaign spec: %v", err) + } + + // Change to the temporary directory + oldDir, _ := os.Getwd() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + defer os.Chdir(oldDir) + + // Create compiler + compiler := workflow.NewCompiler(false, "", GetVersion()) + compiler.SetSkipValidation(true) + compiler.SetNoEmit(false) + + // Compile the specific workflow + config := CompileConfig{ + MarkdownFiles: []string{workflowPath}, + Verbose: false, + WorkflowDir: ".github/workflows", + NoEmit: false, + Strict: false, + } + + ctx := context.Background() + _, err := CompileWorkflows(ctx, config) + + // The compilation should succeed without campaign validation errors + // If campaign validation ran, it would detect the invalid campaign and return an error + if err != nil { + // Check if the error is related to campaign validation + if strings.Contains(err.Error(), "campaign") { + t.Errorf("Campaign validation should not run for specific non-campaign workflow, but got error: %v", err) + } + // Other errors might be acceptable (e.g., compilation issues) + } +} + +// TestCampaignValidationRunsForCampaignFile tests that campaign validation +// runs when compiling a specific campaign file +func TestCampaignValidationRunsForCampaignFile(t *testing.T) { + // Create a temporary directory + tmpDir := testutil.TempDir(t, "test-*") + + // Create workflows directory + workflowsDir := filepath.Join(tmpDir, ".github", "workflows") + if err := os.MkdirAll(workflowsDir, 0755); err != nil { + t.Fatalf("Failed to create workflows directory: %v", err) + } + + // Initialize git repo + initGitRepoForCampaignTest(t, tmpDir) + + // Create a campaign spec file + campaignPath := filepath.Join(workflowsDir, "test-campaign.campaign.md") + campaignContent := `--- +id: test-campaign +name: Test Campaign +description: A test campaign +workflows: + - example-workflow +--- + +# Campaign Task + +This is a test campaign. +` + if err := os.WriteFile(campaignPath, []byte(campaignContent), 0600); err != nil { + t.Fatalf("Failed to create campaign file: %v", err) + } + + // Create an invalid campaign spec in campaigns directory to trigger validation error + campaignDir := filepath.Join(tmpDir, ".github", "campaigns") + if err := os.MkdirAll(campaignDir, 0755); err != nil { + t.Fatalf("Failed to create campaigns directory: %v", err) + } + + invalidCampaignPath := filepath.Join(campaignDir, "invalid.campaign.yml") + invalidCampaignContent := `id: invalid-campaign +# Missing required 'workflows' field +name: Invalid Campaign +description: This campaign is missing the required workflows field +` + if err := os.WriteFile(invalidCampaignPath, []byte(invalidCampaignContent), 0600); err != nil { + t.Fatalf("Failed to create invalid campaign spec: %v", err) + } + + // Change to the temporary directory + oldDir, _ := os.Getwd() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + defer os.Chdir(oldDir) + + // Create compiler + compiler := workflow.NewCompiler(false, "", GetVersion()) + compiler.SetSkipValidation(true) + compiler.SetNoEmit(false) + + // Compile the specific campaign file + config := CompileConfig{ + MarkdownFiles: []string{campaignPath}, + Verbose: false, + WorkflowDir: ".github/workflows", + NoEmit: false, + Strict: false, // Use non-strict mode so validation errors become warnings + } + + ctx := context.Background() + _, err := CompileWorkflows(ctx, config) + + // In non-strict mode, campaign validation errors should be warnings, not fatal errors + // So compilation should succeed even with invalid campaigns + if err != nil { + // Check if the error is related to campaign validation + if strings.Contains(err.Error(), "campaign") { + // This is expected in non-strict mode - validation ran and reported as warning + // The test passes because validation actually ran + return + } + } + + // If no error, validation still ran but was reported as a warning + // This is the expected behavior in non-strict mode +} + +// TestCampaignValidationRunsForAllWorkflows tests that campaign validation +// runs when compiling all workflows (no specific files) +func TestCampaignValidationRunsForAllWorkflows(t *testing.T) { + // Create a temporary directory + tmpDir := testutil.TempDir(t, "test-*") + + // Create workflows directory + workflowsDir := filepath.Join(tmpDir, ".github", "workflows") + if err := os.MkdirAll(workflowsDir, 0755); err != nil { + t.Fatalf("Failed to create workflows directory: %v", err) + } + + // Initialize git repo + initGitRepoForCampaignTest(t, tmpDir) + + // Create a simple workflow + workflowPath := filepath.Join(workflowsDir, "test-workflow.md") + workflowContent := `--- +name: Test Workflow +engine: copilot +--- + +# Test Task + +This is a simple test workflow. +` + if err := os.WriteFile(workflowPath, []byte(workflowContent), 0600); err != nil { + t.Fatalf("Failed to create workflow file: %v", err) + } + + // Create an invalid campaign spec to verify validation runs + campaignDir := filepath.Join(tmpDir, ".github", "campaigns") + if err := os.MkdirAll(campaignDir, 0755); err != nil { + t.Fatalf("Failed to create campaigns directory: %v", err) + } + + invalidCampaignPath := filepath.Join(campaignDir, "invalid.campaign.yml") + invalidCampaignContent := `id: invalid-campaign +# Missing required 'workflows' field +name: Invalid Campaign +description: This campaign is missing the required workflows field +` + if err := os.WriteFile(invalidCampaignPath, []byte(invalidCampaignContent), 0600); err != nil { + t.Fatalf("Failed to create invalid campaign spec: %v", err) + } + + // Change to the temporary directory + oldDir, _ := os.Getwd() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + defer os.Chdir(oldDir) + + // Create compiler + compiler := workflow.NewCompiler(false, "", GetVersion()) + compiler.SetSkipValidation(true) + compiler.SetNoEmit(false) + + // Compile all workflows (no specific files) + config := CompileConfig{ + MarkdownFiles: []string{}, // Empty means compile all + Verbose: false, + WorkflowDir: ".github/workflows", + NoEmit: false, + Strict: false, // Use non-strict mode so validation errors become warnings + } + + ctx := context.Background() + _, err := CompileWorkflows(ctx, config) + + // In non-strict mode, campaign validation errors should be warnings, not fatal errors + // So compilation should succeed even with invalid campaigns + if err != nil { + // Check if the error is related to campaign validation + if strings.Contains(err.Error(), "campaign") { + // This is expected in non-strict mode - validation ran and reported as warning + // The test passes because validation actually ran + return + } + } + + // If no error, validation still ran but was reported as a warning + // This is the expected behavior in non-strict mode +} diff --git a/pkg/cli/compile_orchestration.go b/pkg/cli/compile_orchestration.go index 8e259933c7..227ae5a069 100644 --- a/pkg/cli/compile_orchestration.go +++ b/pkg/cli/compile_orchestration.go @@ -49,6 +49,7 @@ func compileSpecificFiles( var errorMessages []string var lockFilesForActionlint []string var lockFilesForZizmor []string + var hasCampaignFiles bool // Compile each specified file for _, markdownFile := range config.MarkdownFiles { @@ -88,6 +89,7 @@ func compileSpecificFiles( // Handle campaign spec files separately if strings.HasSuffix(resolvedFile, ".campaign.md") { + hasCampaignFiles = true campaignResult, success := processCampaignSpec( compiler, resolvedFile, config.Verbose, config.JSONOutput, config.NoEmit, false, false, false, // Disable per-file security tools @@ -171,7 +173,7 @@ func compileSpecificFiles( displayScheduleWarnings(compiler, config.JSONOutput) // Post-processing - if err := runPostProcessing(compiler, workflowDataList, config, compiledCount); err != nil { + if err := runPostProcessing(compiler, workflowDataList, config, compiledCount, hasCampaignFiles); err != nil { return workflowDataList, err } @@ -422,6 +424,7 @@ func runPostProcessing( workflowDataList []*workflow.WorkflowData, config CompileConfig, successCount int, + hasCampaignFiles bool, ) error { // Get action cache actionCache := compiler.GetSharedActionCache() @@ -442,10 +445,13 @@ func runPostProcessing( } } - // Validate campaigns - if err := validateCampaignsWrapper(config.WorkflowDir, config.Verbose, config.Strict); err != nil { - if config.Strict { - return err + // Validate campaigns only if we're compiling campaign files + // When compiling specific non-campaign workflows, skip campaign validation + if hasCampaignFiles { + if err := validateCampaignsWrapper(config.WorkflowDir, config.Verbose, config.Strict); err != nil { + if config.Strict { + return err + } } } From f390df5e58dfc605cdc549624d77d3dc00499c68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:23:01 +0000 Subject: [PATCH 3/4] Delete compile_campaign_validation_test.go as requested Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_campaign_validation_test.go | 279 -------------------- 1 file changed, 279 deletions(-) delete mode 100644 pkg/cli/compile_campaign_validation_test.go diff --git a/pkg/cli/compile_campaign_validation_test.go b/pkg/cli/compile_campaign_validation_test.go deleted file mode 100644 index b30abbc176..0000000000 --- a/pkg/cli/compile_campaign_validation_test.go +++ /dev/null @@ -1,279 +0,0 @@ -//go:build integration - -package cli - -import ( - "context" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - - "github.com/githubnext/gh-aw/pkg/testutil" - "github.com/githubnext/gh-aw/pkg/workflow" -) - -// initGitRepo initializes a git repository in the given directory -func initGitRepoForCampaignTest(t *testing.T, dir string) { - t.Helper() - cmd := exec.Command("git", "init") - cmd.Dir = dir - if err := cmd.Run(); err != nil { - t.Fatalf("failed to initialize git repo: %v", err) - } -} - -// TestCampaignValidationSkippedForSpecificWorkflow tests that campaign validation -// is skipped when compiling a specific non-campaign workflow -func TestCampaignValidationSkippedForSpecificWorkflow(t *testing.T) { - // Create a temporary directory - tmpDir := testutil.TempDir(t, "test-*") - - // Create workflows directory - workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - if err := os.MkdirAll(workflowsDir, 0755); err != nil { - t.Fatalf("Failed to create workflows directory: %v", err) - } - - // Initialize git repo - initGitRepoForCampaignTest(t, tmpDir) - - // Create a simple non-campaign workflow - workflowPath := filepath.Join(workflowsDir, "test-workflow.md") - workflowContent := `--- -name: Test Workflow -engine: copilot ---- - -# Test Task - -This is a simple test workflow. -` - if err := os.WriteFile(workflowPath, []byte(workflowContent), 0600); err != nil { - t.Fatalf("Failed to create workflow file: %v", err) - } - - // Create an invalid campaign spec to verify validation is skipped - campaignDir := filepath.Join(tmpDir, ".github", "campaigns") - if err := os.MkdirAll(campaignDir, 0755); err != nil { - t.Fatalf("Failed to create campaigns directory: %v", err) - } - - invalidCampaignPath := filepath.Join(campaignDir, "invalid.campaign.yml") - invalidCampaignContent := `id: invalid-campaign -# Missing required 'workflows' field -name: Invalid Campaign -description: This campaign is missing the required workflows field -` - if err := os.WriteFile(invalidCampaignPath, []byte(invalidCampaignContent), 0600); err != nil { - t.Fatalf("Failed to create invalid campaign spec: %v", err) - } - - // Change to the temporary directory - oldDir, _ := os.Getwd() - if err := os.Chdir(tmpDir); err != nil { - t.Fatalf("Failed to change directory: %v", err) - } - defer os.Chdir(oldDir) - - // Create compiler - compiler := workflow.NewCompiler(false, "", GetVersion()) - compiler.SetSkipValidation(true) - compiler.SetNoEmit(false) - - // Compile the specific workflow - config := CompileConfig{ - MarkdownFiles: []string{workflowPath}, - Verbose: false, - WorkflowDir: ".github/workflows", - NoEmit: false, - Strict: false, - } - - ctx := context.Background() - _, err := CompileWorkflows(ctx, config) - - // The compilation should succeed without campaign validation errors - // If campaign validation ran, it would detect the invalid campaign and return an error - if err != nil { - // Check if the error is related to campaign validation - if strings.Contains(err.Error(), "campaign") { - t.Errorf("Campaign validation should not run for specific non-campaign workflow, but got error: %v", err) - } - // Other errors might be acceptable (e.g., compilation issues) - } -} - -// TestCampaignValidationRunsForCampaignFile tests that campaign validation -// runs when compiling a specific campaign file -func TestCampaignValidationRunsForCampaignFile(t *testing.T) { - // Create a temporary directory - tmpDir := testutil.TempDir(t, "test-*") - - // Create workflows directory - workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - if err := os.MkdirAll(workflowsDir, 0755); err != nil { - t.Fatalf("Failed to create workflows directory: %v", err) - } - - // Initialize git repo - initGitRepoForCampaignTest(t, tmpDir) - - // Create a campaign spec file - campaignPath := filepath.Join(workflowsDir, "test-campaign.campaign.md") - campaignContent := `--- -id: test-campaign -name: Test Campaign -description: A test campaign -workflows: - - example-workflow ---- - -# Campaign Task - -This is a test campaign. -` - if err := os.WriteFile(campaignPath, []byte(campaignContent), 0600); err != nil { - t.Fatalf("Failed to create campaign file: %v", err) - } - - // Create an invalid campaign spec in campaigns directory to trigger validation error - campaignDir := filepath.Join(tmpDir, ".github", "campaigns") - if err := os.MkdirAll(campaignDir, 0755); err != nil { - t.Fatalf("Failed to create campaigns directory: %v", err) - } - - invalidCampaignPath := filepath.Join(campaignDir, "invalid.campaign.yml") - invalidCampaignContent := `id: invalid-campaign -# Missing required 'workflows' field -name: Invalid Campaign -description: This campaign is missing the required workflows field -` - if err := os.WriteFile(invalidCampaignPath, []byte(invalidCampaignContent), 0600); err != nil { - t.Fatalf("Failed to create invalid campaign spec: %v", err) - } - - // Change to the temporary directory - oldDir, _ := os.Getwd() - if err := os.Chdir(tmpDir); err != nil { - t.Fatalf("Failed to change directory: %v", err) - } - defer os.Chdir(oldDir) - - // Create compiler - compiler := workflow.NewCompiler(false, "", GetVersion()) - compiler.SetSkipValidation(true) - compiler.SetNoEmit(false) - - // Compile the specific campaign file - config := CompileConfig{ - MarkdownFiles: []string{campaignPath}, - Verbose: false, - WorkflowDir: ".github/workflows", - NoEmit: false, - Strict: false, // Use non-strict mode so validation errors become warnings - } - - ctx := context.Background() - _, err := CompileWorkflows(ctx, config) - - // In non-strict mode, campaign validation errors should be warnings, not fatal errors - // So compilation should succeed even with invalid campaigns - if err != nil { - // Check if the error is related to campaign validation - if strings.Contains(err.Error(), "campaign") { - // This is expected in non-strict mode - validation ran and reported as warning - // The test passes because validation actually ran - return - } - } - - // If no error, validation still ran but was reported as a warning - // This is the expected behavior in non-strict mode -} - -// TestCampaignValidationRunsForAllWorkflows tests that campaign validation -// runs when compiling all workflows (no specific files) -func TestCampaignValidationRunsForAllWorkflows(t *testing.T) { - // Create a temporary directory - tmpDir := testutil.TempDir(t, "test-*") - - // Create workflows directory - workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - if err := os.MkdirAll(workflowsDir, 0755); err != nil { - t.Fatalf("Failed to create workflows directory: %v", err) - } - - // Initialize git repo - initGitRepoForCampaignTest(t, tmpDir) - - // Create a simple workflow - workflowPath := filepath.Join(workflowsDir, "test-workflow.md") - workflowContent := `--- -name: Test Workflow -engine: copilot ---- - -# Test Task - -This is a simple test workflow. -` - if err := os.WriteFile(workflowPath, []byte(workflowContent), 0600); err != nil { - t.Fatalf("Failed to create workflow file: %v", err) - } - - // Create an invalid campaign spec to verify validation runs - campaignDir := filepath.Join(tmpDir, ".github", "campaigns") - if err := os.MkdirAll(campaignDir, 0755); err != nil { - t.Fatalf("Failed to create campaigns directory: %v", err) - } - - invalidCampaignPath := filepath.Join(campaignDir, "invalid.campaign.yml") - invalidCampaignContent := `id: invalid-campaign -# Missing required 'workflows' field -name: Invalid Campaign -description: This campaign is missing the required workflows field -` - if err := os.WriteFile(invalidCampaignPath, []byte(invalidCampaignContent), 0600); err != nil { - t.Fatalf("Failed to create invalid campaign spec: %v", err) - } - - // Change to the temporary directory - oldDir, _ := os.Getwd() - if err := os.Chdir(tmpDir); err != nil { - t.Fatalf("Failed to change directory: %v", err) - } - defer os.Chdir(oldDir) - - // Create compiler - compiler := workflow.NewCompiler(false, "", GetVersion()) - compiler.SetSkipValidation(true) - compiler.SetNoEmit(false) - - // Compile all workflows (no specific files) - config := CompileConfig{ - MarkdownFiles: []string{}, // Empty means compile all - Verbose: false, - WorkflowDir: ".github/workflows", - NoEmit: false, - Strict: false, // Use non-strict mode so validation errors become warnings - } - - ctx := context.Background() - _, err := CompileWorkflows(ctx, config) - - // In non-strict mode, campaign validation errors should be warnings, not fatal errors - // So compilation should succeed even with invalid campaigns - if err != nil { - // Check if the error is related to campaign validation - if strings.Contains(err.Error(), "campaign") { - // This is expected in non-strict mode - validation ran and reported as warning - // The test passes because validation actually ran - return - } - } - - // If no error, validation still ran but was reported as a warning - // This is the expected behavior in non-strict mode -} From 59406f1f625fb51a8304c86d062bbe56cd3c6931 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:31:31 +0000 Subject: [PATCH 4/4] Validate only specific campaign files when compiling by name Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_campaign.go | 48 ++++++++++++++++++++++++++---- pkg/cli/compile_orchestration.go | 15 +++++----- pkg/cli/compile_post_processing.go | 4 +-- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/pkg/cli/compile_campaign.go b/pkg/cli/compile_campaign.go index 31befc0cca..6fc2bc63ce 100644 --- a/pkg/cli/compile_campaign.go +++ b/pkg/cli/compile_campaign.go @@ -39,8 +39,10 @@ import ( var compileCampaignLog = logger.New("cli:compile_campaign") // validateCampaigns validates campaign spec files and their referenced workflows. +// If campaignFiles is provided (non-nil), only those specific campaign files are validated. +// If campaignFiles is nil, all campaign specs are validated. // Returns an error if any campaign specs are invalid or reference missing workflows. -func validateCampaigns(workflowDir string, verbose bool) error { +func validateCampaigns(workflowDir string, verbose bool, campaignFiles []string) error { compileCampaignLog.Printf("Validating campaigns with workflow directory: %s", workflowDir) // Get absolute path to workflows directory @@ -81,16 +83,50 @@ func validateCampaigns(workflowDir string, verbose bool) error { return nil } - compileCampaignLog.Printf("Loaded %d campaign specs for validation", len(specs)) + // Filter specs if specific campaign files were provided + var specsToValidate []campaign.CampaignSpec + if campaignFiles != nil && len(campaignFiles) > 0 { + compileCampaignLog.Printf("Filtering to validate only %d specific campaign file(s)", len(campaignFiles)) + // Create a map of absolute paths for quick lookup + campaignFileMap := make(map[string]bool) + for _, cf := range campaignFiles { + absPath, err := filepath.Abs(cf) + if err == nil { + campaignFileMap[absPath] = true + } + } + + for _, spec := range specs { + // Get absolute path of the spec's config file + specPath := spec.ConfigPath + if !filepath.IsAbs(specPath) { + specPath = filepath.Join(gitRoot, specPath) + } + absSpecPath, err := filepath.Abs(specPath) + if err == nil && campaignFileMap[absSpecPath] { + specsToValidate = append(specsToValidate, spec) + } + } + compileCampaignLog.Printf("Filtered to %d campaign spec(s) for validation", len(specsToValidate)) + } else { + // Validate all specs + specsToValidate = specs + compileCampaignLog.Printf("Loaded %d campaign specs for validation", len(specs)) + } + + if len(specsToValidate) == 0 { + compileCampaignLog.Print("No matching campaign specs found to validate") + return nil + } if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Validating %d campaign spec(s)...", len(specs)))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Validating %d campaign spec(s)...", len(specsToValidate)))) } var allProblems []string hasErrors := false - for _, spec := range specs { + for _, spec := range specsToValidate { // Validate the spec itself problems := campaign.ValidateSpec(&spec) @@ -115,9 +151,9 @@ func validateCampaigns(workflowDir string, verbose bool) error { return fmt.Errorf("found %d problem(s) in campaign specs", len(allProblems)) } - compileCampaignLog.Printf("All %d campaign specs validated successfully", len(specs)) + compileCampaignLog.Printf("All %d campaign specs validated successfully", len(specsToValidate)) if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("All %d campaign spec(s) validated successfully", len(specs)))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("All %d campaign spec(s) validated successfully", len(specsToValidate)))) } return nil diff --git a/pkg/cli/compile_orchestration.go b/pkg/cli/compile_orchestration.go index 227ae5a069..a62d9a52c2 100644 --- a/pkg/cli/compile_orchestration.go +++ b/pkg/cli/compile_orchestration.go @@ -49,7 +49,7 @@ func compileSpecificFiles( var errorMessages []string var lockFilesForActionlint []string var lockFilesForZizmor []string - var hasCampaignFiles bool + var campaignFiles []string // Compile each specified file for _, markdownFile := range config.MarkdownFiles { @@ -89,7 +89,7 @@ func compileSpecificFiles( // Handle campaign spec files separately if strings.HasSuffix(resolvedFile, ".campaign.md") { - hasCampaignFiles = true + campaignFiles = append(campaignFiles, resolvedFile) campaignResult, success := processCampaignSpec( compiler, resolvedFile, config.Verbose, config.JSONOutput, config.NoEmit, false, false, false, // Disable per-file security tools @@ -173,7 +173,7 @@ func compileSpecificFiles( displayScheduleWarnings(compiler, config.JSONOutput) // Post-processing - if err := runPostProcessing(compiler, workflowDataList, config, compiledCount, hasCampaignFiles); err != nil { + if err := runPostProcessing(compiler, workflowDataList, config, compiledCount, campaignFiles); err != nil { return workflowDataList, err } @@ -424,7 +424,7 @@ func runPostProcessing( workflowDataList []*workflow.WorkflowData, config CompileConfig, successCount int, - hasCampaignFiles bool, + campaignFiles []string, ) error { // Get action cache actionCache := compiler.GetSharedActionCache() @@ -447,8 +447,9 @@ func runPostProcessing( // Validate campaigns only if we're compiling campaign files // When compiling specific non-campaign workflows, skip campaign validation - if hasCampaignFiles { - if err := validateCampaignsWrapper(config.WorkflowDir, config.Verbose, config.Strict); err != nil { + // When compiling specific campaign files, validate only those campaign files + if len(campaignFiles) > 0 { + if err := validateCampaignsWrapper(config.WorkflowDir, config.Verbose, config.Strict, campaignFiles); err != nil { if config.Strict { return err } @@ -497,7 +498,7 @@ func runPostProcessingForDirectory( } // Validate campaigns - if err := validateCampaignsWrapper(config.WorkflowDir, config.Verbose, config.Strict); err != nil { + if err := validateCampaignsWrapper(config.WorkflowDir, config.Verbose, config.Strict, nil); err != nil { if config.Strict { return err } diff --git a/pkg/cli/compile_post_processing.go b/pkg/cli/compile_post_processing.go index 1ddda6b44a..bf0e4d0ebd 100644 --- a/pkg/cli/compile_post_processing.go +++ b/pkg/cli/compile_post_processing.go @@ -83,10 +83,10 @@ func generateMaintenanceWorkflowWrapper( } // validateCampaignsWrapper validates campaign specs if they exist -func validateCampaignsWrapper(workflowDir string, verbose bool, strict bool) error { +func validateCampaignsWrapper(workflowDir string, verbose bool, strict bool, campaignFiles []string) error { compilePostProcessingLog.Print("Validating campaign specs") - if err := validateCampaigns(workflowDir, verbose); err != nil { + if err := validateCampaigns(workflowDir, verbose, campaignFiles); err != nil { if strict { return fmt.Errorf("campaign validation failed: %w", err) }