diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 33638c2ec5..ae68d1b1f4 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -5978,7 +5978,7 @@ jobs: } core.info(`Event ${eventName} requires validation (write role not allowed)`); } - const safeEvents = ["workflow_run", "schedule"]; + const safeEvents = ["schedule"]; if (safeEvents.includes(eventName)) { core.info(`✅ Event ${eventName} does not require validation`); core.setOutput("is_team_member", "true"); diff --git a/pkg/cli/compile_command.go b/pkg/cli/compile_command.go index acde49f1c3..572fe70855 100644 --- a/pkg/cli/compile_command.go +++ b/pkg/cli/compile_command.go @@ -472,12 +472,12 @@ func CompileWorkflows(config CompileConfig) ([]*workflow.WorkflowData, error) { if err := actionCache.Save(); err != nil { compileLog.Printf("Failed to save action cache: %v", err) if verbose { - fmt.Fprintf(os.Stderr, "⚠️ Failed to save action cache: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to save action cache: %v", err))) } } else { compileLog.Print("Action cache saved successfully") if verbose { - fmt.Fprintf(os.Stderr, "✓ Action cache saved to %s\n", actionCache.GetCachePath()) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Action cache saved to %s", actionCache.GetCachePath()))) } } } @@ -698,12 +698,12 @@ func CompileWorkflows(config CompileConfig) ([]*workflow.WorkflowData, error) { if err := actionCache.Save(); err != nil { compileLog.Printf("Failed to save action cache: %v", err) if verbose { - fmt.Fprintf(os.Stderr, "⚠️ Failed to save action cache: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to save action cache: %v", err))) } } else { compileLog.Print("Action cache saved successfully") if verbose { - fmt.Fprintf(os.Stderr, "✓ Action cache saved to %s\n", actionCache.GetCachePath()) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Action cache saved to %s", actionCache.GetCachePath()))) } } } @@ -812,7 +812,7 @@ func watchAndCompileWorkflows(markdownFile string, compiler *workflow.Compiler, fmt.Fprintln(os.Stderr, "Watching for file changes") if verbose { - fmt.Fprintf(os.Stderr, "🔨 Initial compilation of %s...\n", markdownFile) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Initial compilation of %s...", markdownFile))) } if err := CompileWorkflowWithValidation(compiler, markdownFile, verbose, false, false, false, false, false); err != nil { // Always show initial compilation errors on new line without wrapping @@ -913,7 +913,7 @@ func compileSingleFile(compiler *workflow.Compiler, file string, stats *Compilat compileLog.Printf("Compiling: %s", file) if verbose { - fmt.Fprintf(os.Stderr, "🔨 Compiling: %s\n", file) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Compiling: %s", file))) } if err := CompileWorkflowWithValidation(compiler, file, verbose, false, false, false, false, false); err != nil { @@ -967,12 +967,12 @@ func compileAllWorkflowFiles(compiler *workflow.Compiler, workflowsDir string, v if err := actionCache.Save(); err != nil { compileLog.Printf("Failed to save action cache: %v", err) if verbose { - fmt.Fprintf(os.Stderr, "⚠️ Failed to save action cache: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to save action cache: %v", err))) } } else { compileLog.Print("Action cache saved successfully") if verbose { - fmt.Fprintf(os.Stderr, "✓ Action cache saved to %s\n", actionCache.GetCachePath()) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Action cache saved to %s", actionCache.GetCachePath()))) } } } @@ -998,7 +998,7 @@ func compileModifiedFiles(compiler *workflow.Compiler, files []string, verbose b fmt.Fprintln(os.Stderr, "Watching for file changes") if verbose { - fmt.Fprintf(os.Stderr, "🔨 Compiling %d modified file(s)...\n", len(files)) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Compiling %d modified file(s)...", len(files)))) } // Reset warning count before compilation @@ -1020,7 +1020,7 @@ func compileModifiedFiles(compiler *workflow.Compiler, files []string, verbose b if err := actionCache.Save(); err != nil { compileLog.Printf("Failed to save action cache: %v", err) if verbose { - fmt.Fprintf(os.Stderr, "⚠️ Failed to save action cache: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to save action cache: %v", err))) } } else { compileLog.Print("Action cache saved successfully") diff --git a/pkg/cli/enable.go b/pkg/cli/enable.go index 006255f471..a95c192d0e 100644 --- a/pkg/cli/enable.go +++ b/pkg/cli/enable.go @@ -89,7 +89,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st // Get GitHub workflows status for comparison; warn but continue if unavailable githubWorkflows, err := fetchGitHubWorkflows("", false) if err != nil { - fmt.Fprintf(os.Stderr, "Warning: Unable to fetch GitHub workflows (gh CLI may not be authenticated): %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Unable to fetch GitHub workflows (gh CLI may not be authenticated): %v", err))) githubWorkflows = make(map[string]*GitHubWorkflow) } @@ -126,7 +126,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st if enable { if _, err := os.Stat(lockFile); os.IsNotExist(err) { if err := compileWorkflow(file, false, ""); err != nil { - fmt.Fprintf(os.Stderr, "Warning: Failed to compile workflow %s to create lock file: %v\n", name, err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to compile workflow %s to create lock file: %v", name, err))) // If we can't compile and there's no GitHub entry, skip because we can't address it if !exists { continue @@ -218,7 +218,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st // First cancel any running workflows (by ID when available, else by lock file name) if t.ID != 0 { if err := cancelWorkflowRuns(t.ID); err != nil { - fmt.Fprintf(os.Stderr, "Warning: Failed to cancel runs for workflow %s: %v\n", t.Name, err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to cancel runs for workflow %s: %v", t.Name, err))) } // Prefer disabling by lock file name for reliability args := []string{"workflow", "disable", t.LockFileBase} @@ -228,7 +228,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st cmd = exec.Command("gh", args...) } else { if err := cancelWorkflowRunsByLockFile(t.LockFileBase); err != nil { - fmt.Fprintf(os.Stderr, "Warning: Failed to cancel runs for workflow %s: %v\n", t.Name, err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to cancel runs for workflow %s: %v", t.Name, err))) } args := []string{"workflow", "disable", t.LockFileBase} if repoOverride != "" { @@ -351,7 +351,7 @@ func DisableAllWorkflowsExcept(repoSlug string, exceptWorkflows []string, verbos cmd := exec.Command("gh", args...) if output, err := cmd.CombinedOutput(); err != nil { if verbose { - fmt.Fprintf(os.Stderr, "Warning: Failed to disable workflow %s: %v\n%s\n", workflow, err, string(output)) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to disable workflow %s: %v\n%s", workflow, err, string(output)))) } failures = append(failures, workflow) } else { diff --git a/pkg/cli/packages.go b/pkg/cli/packages.go index 98c22cb91f..fbcaf9f82c 100644 --- a/pkg/cli/packages.go +++ b/pkg/cli/packages.go @@ -8,6 +8,7 @@ import ( "regexp" "strings" + "github.com/githubnext/gh-aw/pkg/console" "github.com/githubnext/gh-aw/pkg/logger" "github.com/githubnext/gh-aw/pkg/parser" ) @@ -193,7 +194,7 @@ func downloadWorkflows(repo, version, targetDir string, verbose bool) error { metadataPath := filepath.Join(targetDir, ".commit-sha") if err := os.WriteFile(metadataPath, []byte(commitSHA), 0644); err != nil { if verbose { - fmt.Fprintf(os.Stderr, "Warning: Failed to write commit SHA metadata: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to write commit SHA metadata: %v", err))) } } diff --git a/pkg/cli/update_command.go b/pkg/cli/update_command.go index d8a5988c18..a899dc67c5 100644 --- a/pkg/cli/update_command.go +++ b/pkg/cli/update_command.go @@ -302,7 +302,7 @@ func showUpdateSummary(successfulUpdates []string, failedUpdates []updateFailure if len(successfulUpdates) > 0 { fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Successfully updated and compiled %d workflow(s):", len(successfulUpdates)))) for _, name := range successfulUpdates { - fmt.Fprintf(os.Stderr, " ✓ %s\n", name) + fmt.Fprintln(os.Stderr, console.FormatListItem(name)) } fmt.Fprintln(os.Stderr, "") } @@ -311,7 +311,7 @@ func showUpdateSummary(successfulUpdates []string, failedUpdates []updateFailure if len(failedUpdates) > 0 { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("Failed to update %d workflow(s):", len(failedUpdates)))) for _, failure := range failedUpdates { - fmt.Fprintf(os.Stderr, " ✗ %s: %s\n", failure.Name, failure.Error) + fmt.Fprintf(os.Stderr, " %s %s: %s\n", console.FormatErrorMessage("✗"), failure.Name, failure.Error) } fmt.Fprintln(os.Stderr, "") } diff --git a/pkg/workflow/shell_backslash_integration_test.go b/pkg/workflow/shell_backslash_integration_test.go index a8c994ba09..70fe6bb15a 100644 --- a/pkg/workflow/shell_backslash_integration_test.go +++ b/pkg/workflow/shell_backslash_integration_test.go @@ -4,47 +4,47 @@ package workflow import ( -"testing" + "testing" ) func TestSingleQuoteEscapingPreservesBackslashes(t *testing.T) { -// Test that the shell escaping function preserves backslashes -// This is a regression test for the typist workflow control character issue -testCases := []struct { -name string -input string -expected string -}{ -{ -name: "grep with word boundaries", -input: "grep -r '\\bany\\b' pkg", -expected: "'grep -r '\\''\\bany\\b'\\'' pkg'", -}, -{ -name: "echo with escape sequences", -input: "echo '\\n\\t'", -expected: "'echo '\\''\\n\\t'\\'''", -}, -{ -name: "path with backslashes", -input: "path\\to\\file", -expected: "'path\\to\\file'", -}, -} + // Test that the shell escaping function preserves backslashes + // This is a regression test for the typist workflow control character issue + testCases := []struct { + name string + input string + expected string + }{ + { + name: "grep with word boundaries", + input: "grep -r '\\bany\\b' pkg", + expected: "'grep -r '\\''\\bany\\b'\\'' pkg'", + }, + { + name: "echo with escape sequences", + input: "echo '\\n\\t'", + expected: "'echo '\\''\\n\\t'\\'''", + }, + { + name: "path with backslashes", + input: "path\\to\\file", + expected: "'path\\to\\file'", + }, + } -for _, tc := range testCases { -t.Run(tc.name, func(t *testing.T) { -result := shellEscapeArg(tc.input) -if result != tc.expected { -t.Errorf("shellEscapeArg(%q) = %q, expected %q", tc.input, result, tc.expected) -} + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := shellEscapeArg(tc.input) + if result != tc.expected { + t.Errorf("shellEscapeArg(%q) = %q, expected %q", tc.input, result, tc.expected) + } -// Verify no control characters in the result -for i, ch := range result { -if ch < 32 && ch != '\n' && ch != '\t' && ch != '\r' { -t.Errorf("Found control character in result at position %d: %q (0x%02x)", i, ch, ch) -} -} -}) -} + // Verify no control characters in the result + for i, ch := range result { + if ch < 32 && ch != '\n' && ch != '\t' && ch != '\r' { + t.Errorf("Found control character in result at position %d: %q (0x%02x)", i, ch, ch) + } + } + }) + } }