[object Object]

← back to Cli Printing Press

refactor(cli): split verify exec helpers (#321)

18cf50f130ea3876e4cf17a197ffd5e32b9c4caf · 2026-04-26 18:50:09 -0700 · Trevin Chow

Move verify build and subprocess helpers into runtime_exec.go so runtime.go stays focused on verification orchestration. Collapse runCLI onto runCLIWithOutput to keep command execution semantics in one place while preserving caller behavior.

Verification:
- go test ./internal/pipeline
- go test ./...
- go vet ./...
- golangci-lint run ./...
- scripts/golden.sh verify
- go build -o ./printing-press ./cmd/printing-press

Files touched

Diff

commit 18cf50f130ea3876e4cf17a197ffd5e32b9c4caf
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Sun Apr 26 18:50:09 2026 -0700

    refactor(cli): split verify exec helpers (#321)
    
    Move verify build and subprocess helpers into runtime_exec.go so runtime.go stays focused on verification orchestration. Collapse runCLI onto runCLIWithOutput to keep command execution semantics in one place while preserving caller behavior.
    
    Verification:
    - go test ./internal/pipeline
    - go test ./...
    - go vet ./...
    - golangci-lint run ./...
    - scripts/golden.sh verify
    - go build -o ./printing-press ./cmd/printing-press
---
 internal/pipeline/runtime.go      | 102 --------------------------------------
 internal/pipeline/runtime_exec.go | 101 +++++++++++++++++++++++++++++++++++++
 2 files changed, 101 insertions(+), 102 deletions(-)

diff --git a/internal/pipeline/runtime.go b/internal/pipeline/runtime.go
index fd13c759..a84a91d3 100644
--- a/internal/pipeline/runtime.go
+++ b/internal/pipeline/runtime.go
@@ -11,7 +11,6 @@ import (
 	"path/filepath"
 	"regexp"
 	"slices"
-	"sort"
 	"strings"
 	"time"
 
@@ -195,7 +194,6 @@ func RunVerify(cfg VerifyConfig) (*VerifyReport, error) {
 		report.Results = append(report.Results, result)
 	}
 
-	// 8. Data pipeline test
 	report.DataPipeline, report.DataPipelineDetail = runDataPipelineTest(binaryPath, report.Mode, buildEnv)
 	report.Freshness = runFreshnessContractTest(cfg.Dir)
 
@@ -216,78 +214,6 @@ func RunVerify(cfg VerifyConfig) (*VerifyReport, error) {
 	return report, nil
 }
 
-// buildCLI compiles the generated CLI and returns the binary path.
-func buildCLI(dir string) (string, error) {
-	name := filepath.Base(dir)
-	binaryPath, err := filepath.Abs(filepath.Join(dir, name))
-	if err != nil {
-		return "", fmt.Errorf("resolving binary path: %w", err)
-	}
-	cmdDir, err := findCLICommandDir(dir)
-	if err != nil {
-		return "", err
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
-	defer cancel()
-	cmd := exec.CommandContext(ctx, "go", "build", "-o", binaryPath, "./"+filepath.Base(cmdDir))
-	cmd.Dir = filepath.Dir(cmdDir)
-	cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
-	if out, err := cmd.CombinedOutput(); err != nil {
-		return "", fmt.Errorf("go build: %s\n%s", err, string(out))
-	}
-	return binaryPath, nil
-}
-
-func findCLICommandDir(dir string) (string, error) {
-	name := filepath.Base(dir)
-	apiName := naming.TrimCLISuffix(name)
-	candidates := []string{
-		filepath.Join(dir, "cmd", name),
-		filepath.Join(dir, "cmd", naming.CLI(apiName)),
-		filepath.Join(dir, "cmd", naming.LegacyCLI(apiName)),
-		filepath.Join(dir, "cmd", apiName),
-	}
-
-	for _, candidate := range candidates {
-		info, err := os.Stat(candidate)
-		if err == nil && info.IsDir() {
-			return candidate, nil
-		}
-		if err != nil && !os.IsNotExist(err) {
-			return "", fmt.Errorf("stat %s: %w", candidate, err)
-		}
-	}
-
-	entries, err := os.ReadDir(filepath.Join(dir, "cmd"))
-	if err != nil {
-		return "", fmt.Errorf("reading cmd directory: %w", err)
-	}
-
-	var cliEntries []string
-	var dirEntries []string
-	for _, entry := range entries {
-		if !entry.IsDir() {
-			continue
-		}
-		dirEntries = append(dirEntries, entry.Name())
-		if naming.IsCLIDirName(entry.Name()) {
-			cliEntries = append(cliEntries, entry.Name())
-		}
-	}
-
-	sort.Strings(cliEntries)
-	if len(cliEntries) == 1 {
-		return filepath.Join(dir, "cmd", cliEntries[0]), nil
-	}
-
-	if len(dirEntries) == 1 {
-		return filepath.Join(dir, "cmd", dirEntries[0]), nil
-	}
-
-	return "", fmt.Errorf("cannot find CLI cmd entry point in %s", dir)
-}
-
 // discoverCommands finds all registered commands. It first tries to parse the
 // binary's --help output for ground-truth command names. If that fails (binary
 // missing, crash, timeout), it falls back to regex extraction from root.go with
@@ -812,34 +738,6 @@ func runDataPipelineTest(binary, mode string, envFn func() []string) (bool, stri
 	return true, fmt.Sprintf("PASS: %d domain tables created", len(tables))
 }
 
-// runCLI executes the CLI binary with the given args and returns any error.
-func runCLI(binary string, args []string, env []string, timeout time.Duration) error {
-	ctx, cancel := context.WithTimeout(context.Background(), timeout)
-	defer cancel()
-
-	cmd := exec.CommandContext(ctx, binary, args...)
-	cmd.Env = env
-	out, err := cmd.CombinedOutput()
-	if err != nil {
-		return fmt.Errorf("exit %v: %s", err, string(out))
-	}
-	return nil
-}
-
-// runCLIWithOutput executes the CLI binary and returns its combined output.
-func runCLIWithOutput(binary string, args []string, env []string, timeout time.Duration) ([]byte, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), timeout)
-	defer cancel()
-
-	cmd := exec.CommandContext(ctx, binary, args...)
-	cmd.Env = env
-	out, err := cmd.CombinedOutput()
-	if err != nil {
-		return out, fmt.Errorf("exit %v: %s", err, string(out))
-	}
-	return out, nil
-}
-
 // parseSQLOutput extracts non-empty, non-header lines from sql command output.
 func parseSQLOutput(out []byte) []string {
 	var tables []string
diff --git a/internal/pipeline/runtime_exec.go b/internal/pipeline/runtime_exec.go
new file mode 100644
index 00000000..a3cbd9db
--- /dev/null
+++ b/internal/pipeline/runtime_exec.go
@@ -0,0 +1,101 @@
+package pipeline
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"sort"
+	"time"
+
+	"github.com/mvanhorn/cli-printing-press/v2/internal/naming"
+)
+
+func buildCLI(dir string) (string, error) {
+	binaryPath, err := filepath.Abs(filepath.Join(dir, filepath.Base(dir)))
+	if err != nil {
+		return "", fmt.Errorf("resolving binary path: %w", err)
+	}
+	cmdDir, err := findCLICommandDir(dir)
+	if err != nil {
+		return "", err
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
+	defer cancel()
+	cmd := exec.CommandContext(ctx, "go", "build", "-o", binaryPath, "./"+filepath.Base(cmdDir))
+	cmd.Dir = filepath.Dir(cmdDir)
+	cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
+	if out, err := cmd.CombinedOutput(); err != nil {
+		return "", fmt.Errorf("go build: %s\n%s", err, string(out))
+	}
+	return binaryPath, nil
+}
+
+func findCLICommandDir(dir string) (string, error) {
+	name := filepath.Base(dir)
+	apiName := naming.TrimCLISuffix(name)
+	candidates := []string{
+		filepath.Join(dir, "cmd", name),
+		filepath.Join(dir, "cmd", naming.CLI(apiName)),
+		filepath.Join(dir, "cmd", naming.LegacyCLI(apiName)),
+		filepath.Join(dir, "cmd", apiName),
+	}
+
+	for _, candidate := range candidates {
+		info, err := os.Stat(candidate)
+		if err == nil && info.IsDir() {
+			return candidate, nil
+		}
+		if err != nil && !os.IsNotExist(err) {
+			return "", fmt.Errorf("stat %s: %w", candidate, err)
+		}
+	}
+
+	entries, err := os.ReadDir(filepath.Join(dir, "cmd"))
+	if err != nil {
+		return "", fmt.Errorf("reading cmd directory: %w", err)
+	}
+
+	var cliEntries []string
+	var dirEntries []string
+	for _, entry := range entries {
+		if !entry.IsDir() {
+			continue
+		}
+		dirEntries = append(dirEntries, entry.Name())
+		if naming.IsCLIDirName(entry.Name()) {
+			cliEntries = append(cliEntries, entry.Name())
+		}
+	}
+
+	sort.Strings(cliEntries)
+	if len(cliEntries) == 1 {
+		return filepath.Join(dir, "cmd", cliEntries[0]), nil
+	}
+
+	if len(dirEntries) == 1 {
+		return filepath.Join(dir, "cmd", dirEntries[0]), nil
+	}
+
+	return "", fmt.Errorf("cannot find CLI cmd entry point in %s", dir)
+}
+
+func runCLI(binary string, args []string, env []string, timeout time.Duration) error {
+	_, err := runCLIWithOutput(binary, args, env, timeout)
+	return err
+}
+
+func runCLIWithOutput(binary string, args []string, env []string, timeout time.Duration) ([]byte, error) {
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	cmd := exec.CommandContext(ctx, binary, args...)
+	cmd.Env = env
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		return out, fmt.Errorf("exit %v: %s", err, string(out))
+	}
+	return out, nil
+}

← 7e733aa7 refactor(cli): split structural verify runtime (#320)  ·  back to Cli Printing Press  ·  refactor(cli): split verify command helpers (#322) b1001491 →