Files
Ed GaniukovandGitHub 01ecfada39 fix: exit 75 for transient failures (#690)
Every failure exited 1, so callers could only retry blindly:
deterministic failures (no provider detected, missing start command)
were run and logged three times by builderv3 before giving up.

Transient failures are now typed as mise.TemporaryError and map to exit
75 (EX_TEMPFAIL): transport errors, 429, 408, 5xx, and truncated
bodies. A 404 for a pinned mise version and a malformed archive stay
deterministic, since retrying them cannot succeed. The mise download
also had no response status check at all, so a 5xx body was written to
disk and later failed as "binary not found in archive".

Carrying that across the process boundary meant giving
GenerateBuildPlan an error return: a mise install failure was
previously swallowed into BuildResult{Success: false} and exited 1
along with every app-level failure. The error is non-nil only for
transient failures, so !Success stays on exit 1.

The download gets an http.Client timeout, since http.DefaultClient has
none and a stalled connection would hang the build forever. It
deliberately does not retry in-process: the caller already retries the
whole command, and retrying here would multiply that and delay the
signal.

Also writes --info-out even when the build result is unsuccessful, so
callers can read {"success": false, "logs": [...]} instead of scraping
stdout.
2026-08-12 14:46:23 +02:00

65 lines
1.6 KiB
Go

package cli
import (
"context"
"encoding/json"
"os"
"path/filepath"
"github.com/charmbracelet/log"
"github.com/urfave/cli/v3"
)
var PlanCommand = &cli.Command{
Name: "plan",
Aliases: []string{"p"},
Usage: "generate a build plan for a directory",
ArgsUsage: "DIRECTORY",
EnableShellCompletion: true,
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "out",
Aliases: []string{"o"},
Usage: "output file name",
},
}, commonPlanFlags()...),
Action: func(ctx context.Context, cmd *cli.Command) error {
buildResult, _, _, err := GenerateBuildResultForCommand(cmd)
if err != nil {
return cli.Exit(err, exitCodeForError(err))
}
// Include $schema in the generated plan JSON for editor support
planMap, err := addSchemaToPlanMap(buildResult.Plan)
if err != nil {
return cli.Exit(err, ExitCodeFailure)
}
serializedPlan, err := json.MarshalIndent(planMap, "", " ")
if err != nil {
return cli.Exit(err, ExitCodeFailure)
}
buildResultString := serializedPlan
output := cmd.String("out")
if output == "" {
// Write to stdout if no output file specified
_, _ = os.Stdout.Write([]byte(buildResultString))
_, _ = os.Stdout.Write([]byte("\n"))
return nil
} else {
if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
return cli.Exit(err, ExitCodeFailure)
}
err = os.WriteFile(output, []byte(buildResultString), 0644)
if err != nil {
return cli.Exit(err, ExitCodeFailure)
}
log.Infof("Plan written to %s", output)
}
return nil
},
}