2026-05-29 20:19:53 +05:30
|
|
|
package ui
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-06 18:31:18 +05:30
|
|
|
type exitCoder interface {
|
|
|
|
|
ExitCode() int
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 20:19:53 +05:30
|
|
|
// transparentExit is satisfied by *runner.ChildExitError without importing it.
|
|
|
|
|
type transparentExit interface {
|
|
|
|
|
error
|
|
|
|
|
Transparent() bool
|
|
|
|
|
ExitCode() int
|
|
|
|
|
IsSignaled() bool
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 11:40:33 +05:30
|
|
|
// scrubbedEnvReporter is optionally satisfied by a transparent exit error to
|
|
|
|
|
// surface how many environment variables the sandbox scrubbed from the failed
|
|
|
|
|
// run. Kept separate from transparentExit so older implementations still
|
|
|
|
|
// classify as transparent.
|
|
|
|
|
type scrubbedEnvReporter interface {
|
|
|
|
|
ScrubbedEnvCount() int
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 20:19:53 +05:30
|
|
|
type exitDecision struct {
|
|
|
|
|
transparent bool
|
|
|
|
|
notice bool
|
|
|
|
|
code int
|
|
|
|
|
message string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// classifyExit is the pure decision behind ExitFromCommandError. The notice is
|
|
|
|
|
// suppressed for signal exits (the user initiated the interrupt) and in silent
|
|
|
|
|
// mode.
|
|
|
|
|
func classifyExit(err error) exitDecision {
|
|
|
|
|
var te transparentExit
|
|
|
|
|
if !errors.As(err, &te) || !te.Transparent() {
|
|
|
|
|
return exitDecision{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d := exitDecision{transparent: true, code: te.ExitCode()}
|
|
|
|
|
if !te.IsSignaled() && verbosityLevel != VerbosityLevelSilent {
|
|
|
|
|
d.notice = true
|
|
|
|
|
d.message = "↳ pmg: " + te.Error()
|
2026-06-11 11:40:33 +05:30
|
|
|
|
|
|
|
|
// Env scrubbing produces no sandbox violation and the child's own
|
|
|
|
|
// error (e.g. a registry 401) does not point at the cause, so hint at
|
|
|
|
|
// it here. Names are not printed; they are at info level via --debug.
|
|
|
|
|
if sr, ok := te.(scrubbedEnvReporter); ok && sr.ScrubbedEnvCount() > 0 {
|
|
|
|
|
d.message += fmt.Sprintf(
|
|
|
|
|
"\n↳ pmg: sandbox scrubbed %d env var(s) (names via --debug, re-allow with --sandbox-allow env=NAME)",
|
|
|
|
|
sr.ScrubbedEnvCount())
|
|
|
|
|
}
|
2026-05-29 20:19:53 +05:30
|
|
|
}
|
|
|
|
|
return d
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ExitFromCommandError is the single exit point for package-manager commands. A
|
|
|
|
|
// child that exited on its own is mirrored transparently; everything else keeps
|
|
|
|
|
// the visible PMG error framing.
|
|
|
|
|
func ExitFromCommandError(err error) {
|
|
|
|
|
if err == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if d := classifyExit(err); d.transparent {
|
|
|
|
|
ClearStatus()
|
|
|
|
|
if d.notice {
|
|
|
|
|
fmt.Fprintln(os.Stderr, Colors.Dim(d.message))
|
|
|
|
|
}
|
|
|
|
|
os.Exit(d.code)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-06 18:31:18 +05:30
|
|
|
var ec exitCoder
|
|
|
|
|
if errors.As(err, &ec) {
|
|
|
|
|
ErrorExitWithCode(err, ec.ExitCode())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 20:19:53 +05:30
|
|
|
ErrorExit(err)
|
|
|
|
|
}
|