Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion definitions/npm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ commands:
default_flags: [--depth, Infinity, --json, --long]
exit_codes:
0: success
1: error
1: partial

capabilities:
- install
Expand Down
30 changes: 30 additions & 0 deletions translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,36 @@ func (t *Translator) validate(validatorName, value string) error {
return nil
}

// ExitCodeMeaning returns the semantic meaning of an exit code for a
// manager/operation pair, as defined in the YAML. Returns "" if the
// manager, operation, or exit code is not defined.
func (t *Translator) ExitCodeMeaning(managerName, operation string, exitCode int) string {
def, ok := t.definitions[managerName]
if !ok {
return ""
}
cmd, ok := def.Commands[operation]
if !ok {
return ""
}
return cmd.ExitCodes[exitCode]
}

// IsFatalExitCode reports whether exitCode represents a fatal error for
// the given manager/operation. Exit code 0 is never fatal. For non-zero
// codes, the result is fatal unless the YAML definition assigns a
// non-"error" meaning.
func (t *Translator) IsFatalExitCode(managerName, operation string, exitCode int) bool {
if exitCode == 0 {
return false
}
meaning := t.ExitCodeMeaning(managerName, operation, exitCode)
if meaning == "" || meaning == "error" {
return true
}
return false
}

func isTruthy(val any) bool {
if val == nil {
return false
Expand Down
65 changes: 65 additions & 0 deletions translator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3356,3 +3356,68 @@ func TestPipVendor(t *testing.T) {
t.Errorf("got %v, want %v", cmd, expected)
}
}

// --- ExitCodeMeaning tests ---

func TestExitCodeMeaning(t *testing.T) {
tr := loadTranslator(t)

tests := []struct {
name string
manager string
operation string
exitCode int
want string
}{
{"npm resolve 0", "npm", "resolve", 0, "success"},
{"npm resolve 1 partial", "npm", "resolve", 1, "partial"},
{"npm install 0", "npm", "install", 0, "success"},
{"npm install 1 error", "npm", "install", 1, "error"},
{"npm outdated 1 outdated", "npm", "outdated", 1, "outdated"},
{"unknown manager", "nonexistent", "install", 1, ""},
{"unknown operation", "npm", "nonexistent", 1, ""},
{"undefined exit code", "npm", "install", 42, ""},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tr.ExitCodeMeaning(tt.manager, tt.operation, tt.exitCode)
if got != tt.want {
t.Errorf("ExitCodeMeaning(%q, %q, %d) = %q, want %q",
tt.manager, tt.operation, tt.exitCode, got, tt.want)
}
})
}
}

// --- IsFatalExitCode tests ---

func TestIsFatalExitCode(t *testing.T) {
tr := loadTranslator(t)

tests := []struct {
name string
manager string
operation string
exitCode int
want bool
}{
{"exit 0 never fatal", "npm", "install", 0, false},
{"npm install 1 is fatal", "npm", "install", 1, true},
{"npm resolve 1 not fatal", "npm", "resolve", 1, false},
{"npm outdated 1 not fatal", "npm", "outdated", 1, false},
{"unknown manager is fatal", "nonexistent", "install", 1, true},
{"unknown operation is fatal", "npm", "nonexistent", 1, true},
{"undefined exit code is fatal", "npm", "install", 42, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tr.IsFatalExitCode(tt.manager, tt.operation, tt.exitCode)
if got != tt.want {
t.Errorf("IsFatalExitCode(%q, %q, %d) = %v, want %v",
tt.manager, tt.operation, tt.exitCode, got, tt.want)
}
})
}
}