package cmd import ( "context" "errors" "fmt" "os" "os/exec" "github.com/spf13/cobra" ) // Shared by every download subcommand. Only one command runs per // process, so package-level flag vars are safe (same convention as // flagForce). var ( flagExec string flagOptional bool ) // addExecFlag attaches --exec. produces describes what the script is // expected to leave behind, so each subcommand's help text is concrete. func addExecFlag(c *cobra.Command, produces string) { // The backquoted word sets the value placeholder cobra prints in // --help ("--exec script"), so keep exactly one of them. c.Flags().StringVar(&flagExec, "exec", "", "on cache miss, run this `script` through sh -c (it must produce "+produces+") and seed the cache with the result") } // addOptionalFlag attaches --optional: a miss stops being an error, so // `set -e` scripts don't need a trailing `|| echo "cache miss"`. func addOptionalFlag(c *cobra.Command) { c.Flags().BoolVar(&flagOptional, "optional", false, "treat a cache miss as success (exit 0) instead of an error") } // runExecFallback runs the --exec script through `sh -c` with stdio // inherited, so its output lands in the CI log in place, unbuffered and // in order. A non-zero exit is propagated verbatim by exitCodeFor — // the script's own status is more informative than cacher's. func runExecFallback(ctx context.Context, w *cobra.Command, script string) error { fmt.Fprintf(w.ErrOrStderr(), "Running — sh -c %q\n", script) c := exec.CommandContext(ctx, "sh", "-c", script) c.Stdin = os.Stdin c.Stdout = os.Stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { var ee *exec.ExitError if errors.As(err, &ee) { return &execError{code: ee.ExitCode(), err: err} } return fmt.Errorf("--exec: %w", err) } return nil } // execError carries the --exec script's own exit status up to Execute. type execError struct { code int err error } func (e *execError) Error() string { return fmt.Sprintf("--exec script failed: %v", e.err) } func (e *execError) Unwrap() error { return e.err } func (e *execError) ExitCode() int { return e.code }