Skip to content

Commit 96957bd

Browse files
committed
fix
1 parent 4ac683f commit 96957bd

2 files changed

Lines changed: 106 additions & 19 deletions

File tree

internal/lockfile/lockfile.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ func (f *File) RewriteActionRefs(replacements map[string]string) ([]byte, int, e
478478
}
479479

480480
var (
481-
reDepsSectionWithComment = regexp.MustCompile(`(?m)^\n?# Automatically generated and managed by:.*\ndependencies:\n(?: - .*\n)*`)
481+
reDepsSectionWithComment = regexp.MustCompile(`(?m)^\n?# Automatically generated and managed by[^\n]*\ndependencies:\n(?: - .*\n)*`)
482482
reDepsSectionBare = regexp.MustCompile(`(?m)^dependencies:\n(?: - .*\n)*`)
483483
)
484484

root.go

Lines changed: 105 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ type upgradeTarget struct {
4848
}
4949

5050
type validationError struct {
51-
Type string `json:"type"`
52-
Dependency string `json:"dependency"`
53-
Details string `json:"details"`
54-
CompareURL string `json:"compare_url,omitempty"`
55-
ReleasesURL string `json:"releases_url,omitempty"`
51+
Type string `json:"type"`
52+
Dependency string `json:"dependency"`
53+
Details string `json:"details"`
54+
CompareURL string `json:"compare_url,omitempty"`
55+
ReleasesURL string `json:"releases_url,omitempty"`
56+
UnreachableDetail string `json:"unreachable_detail,omitempty"`
5657
}
5758

5859
type validationWarning struct {
@@ -659,10 +660,67 @@ func runCheck(opts *checkOptions) error {
659660
if aggregate.Valid && checked > 0 {
660661
output.Success("All %d %s valid", checked, ui.Pluralize(checked, "workflow", "workflows"))
661662
} else if checked > 0 {
662-
typeCounts := map[string]int{}
663+
// Group errors by dependency so we can merge related findings
664+
// (e.g. TAMPERED + UNREACHABLE for the same dep).
665+
type depFindings struct {
666+
dep string
667+
errors []validationError
668+
}
669+
var depOrder []string
670+
depMap := map[string]*depFindings{}
663671
for _, e := range aggregate.Errors {
664-
typeCounts[e.Type]++
672+
if df, ok := depMap[e.Dependency]; ok {
673+
df.errors = append(df.errors, e)
674+
} else {
675+
depOrder = append(depOrder, e.Dependency)
676+
depMap[e.Dependency] = &depFindings{dep: e.Dependency, errors: []validationError{e}}
677+
}
665678
}
679+
680+
// Merge TAMPERED+UNREACHABLE: when both exist for a dep, fold
681+
// the unreachable detail into the tampered entry.
682+
typeCounts := map[string]int{}
683+
for _, dep := range depOrder {
684+
df := depMap[dep]
685+
hasTampered := false
686+
for _, e := range df.errors {
687+
if e.Type == "TAMPERED" {
688+
hasTampered = true
689+
break
690+
}
691+
}
692+
if hasTampered {
693+
// Absorb UNREACHABLE into TAMPERED — keep unreachable
694+
// detail as supplementary info but don't double-count.
695+
var merged []validationError
696+
var unreachableDetail string
697+
for _, e := range df.errors {
698+
if e.Type == "UNREACHABLE" && hasTampered {
699+
unreachableDetail = e.Details
700+
continue
701+
}
702+
if e.Type == "TAMPERED" && unreachableDetail == "" {
703+
// Haven't seen UNREACHABLE yet; scan ahead.
704+
for _, e2 := range df.errors {
705+
if e2.Type == "UNREACHABLE" {
706+
unreachableDetail = e2.Details
707+
break
708+
}
709+
}
710+
}
711+
me := e
712+
if me.Type == "TAMPERED" && unreachableDetail != "" {
713+
me.UnreachableDetail = unreachableDetail
714+
}
715+
merged = append(merged, me)
716+
}
717+
df.errors = merged
718+
}
719+
for _, e := range df.errors {
720+
typeCounts[e.Type]++
721+
}
722+
}
723+
666724
parts := []string{}
667725
for _, t := range []string{"TAMPERED", "MISSING", "STALE", "SHA_MISMATCH", "UNREACHABLE", "ERROR"} {
668726
if n, ok := typeCounts[t]; ok {
@@ -673,18 +731,29 @@ func runCheck(opts *checkOptions) error {
673731
failed, checked,
674732
ui.Pluralize(checked, "workflow", "workflows"),
675733
strings.Join(parts, ", "))
676-
677-
// Show each error inline so users don't have to dig through the log.
678-
for _, e := range aggregate.Errors {
679-
label := output.Dim("[" + e.Type + "]")
680-
output.Detail(" %s %s: %s", label, e.Dependency, e.Details)
681-
if e.CompareURL != "" {
682-
output.Detail(" → Compare: %s", e.CompareURL)
734+
fmt.Fprintln(os.Stderr)
735+
736+
// Show grouped errors with doctor-style formatting.
737+
for i, dep := range depOrder {
738+
df := depMap[dep]
739+
for _, e := range df.errors {
740+
fmt.Fprintf(os.Stderr, " ! %s %s\n", output.Dim(e.Type), e.Dependency)
741+
fmt.Fprintf(os.Stderr, " %s\n", e.Details)
742+
if e.UnreachableDetail != "" {
743+
fmt.Fprintf(os.Stderr, " %s\n", e.UnreachableDetail)
744+
}
745+
if e.CompareURL != "" {
746+
fmt.Fprintf(os.Stderr, " → %s\n", output.Dim(e.CompareURL))
747+
}
748+
if e.ReleasesURL != "" {
749+
fmt.Fprintf(os.Stderr, " → %s\n", output.Dim(e.ReleasesURL))
750+
}
683751
}
684-
if e.ReleasesURL != "" {
685-
output.Detail(" → Releases: %s", e.ReleasesURL)
752+
if i < len(depOrder)-1 {
753+
fmt.Fprintln(os.Stderr)
686754
}
687755
}
756+
fmt.Fprintln(os.Stderr)
688757
}
689758
if len(aggregate.Warnings) > 0 {
690759
// Group warnings by key (same dependency+details) and collect workflow files.
@@ -711,9 +780,27 @@ func runCheck(opts *checkOptions) error {
711780
}
712781
for _, key := range order {
713782
g := groups[key]
714-
output.Warning("%s", g.warning.String())
783+
w := g.warning
784+
if w.Transitive {
785+
// Extract owner/repo from dep key for a cleaner message.
786+
nwo := w.Dependency
787+
if idx := strings.Index(nwo, "@"); idx > 0 {
788+
nwo = nwo[:idx]
789+
}
790+
// Strip sub-path from NWO (e.g. owner/repo/subpath → owner/repo).
791+
nwoParts := strings.SplitN(nwo, "/", 3)
792+
repoNWO := nwo
793+
if len(nwoParts) >= 2 {
794+
repoNWO = nwoParts[0] + "/" + nwoParts[1]
795+
}
796+
output.Warning("%s: transitive dependency pinned to a bare SHA — reachability cannot be verified", w.Dependency)
797+
output.Detail(" ↳ this comes from a composite action's internal dependency")
798+
output.Detail(" ↳ ask the maintainer of %s to onboard to dependency pinning", output.Bold(repoNWO))
799+
} else {
800+
output.Warning("%s", w.String())
801+
}
715802
for _, f := range g.files {
716-
output.Detail("in %s", f)
803+
output.Detail(" in %s", f)
717804
}
718805
}
719806
}

0 commit comments

Comments
 (0)