1+ #! /bin/bash
2+
3+ # Ensure jq is installed
4+ if ! command -v jq & > /dev/null; then
5+ echo " Error: 'jq' is not installed. Please install it first."
6+ exit 1
7+ fi
8+
9+ # Ensure GitHub CLI is installed
10+ if ! command -v gh & > /dev/null; then
11+ echo " Error: GitHub CLI ('gh') is not installed. Please install it from https://cli.github.com/"
12+ exit 1
13+ fi
14+
15+ # Verify gh is authenticated
16+ if ! gh auth status & > /dev/null; then
17+ echo " Error: You are not logged into GitHub CLI."
18+ echo " Please run 'gh auth login' first to authenticate and avoid rate limits."
19+ exit 1
20+ fi
21+
22+ # Determine 3 years ago in seconds
23+ if date --version & > /dev/null; then
24+ THRESHOLD=$( date -d " 3 years ago" +%s)
25+ IS_GNU_DATE=true
26+ else
27+ THRESHOLD=$( date -v-3y +%s)
28+ IS_GNU_DATE=false
29+ fi
30+
31+ # Reusable function to check a GitHub repo's status
32+ # Returns a string in the format "status|pushed_at"
33+ function get_repo_status() {
34+ local REPO=$1
35+ local RESPONSE=$( gh api " repos/$REPO " 2> /dev/null)
36+
37+ if [ $? -ne 0 ]; then
38+ echo " unknown|"
39+ return
40+ fi
41+
42+ local ARCHIVED=$( echo " $RESPONSE " | jq -r ' .archived' )
43+ local PUSHED_AT=$( echo " $RESPONSE " | jq -r ' .pushed_at' )
44+
45+ if [ " $PUSHED_AT " = " null" ] || [ -z " $PUSHED_AT " ]; then
46+ echo " unknown|"
47+ return
48+ fi
49+
50+ local LAST_COMMIT_SEC
51+ if $IS_GNU_DATE ; then
52+ LAST_COMMIT_SEC=$( date -d " $PUSHED_AT " +%s)
53+ else
54+ LAST_COMMIT_SEC=$( date -j -f " %Y-%m-%dT%H:%M:%SZ" " $PUSHED_AT " +%s)
55+ fi
56+
57+ local STATUS=" fine"
58+ if [ " $ARCHIVED " = " true" ]; then
59+ STATUS=" archived"
60+ elif [ " $LAST_COMMIT_SEC " -lt " $THRESHOLD " ]; then
61+ STATUS=" stale"
62+ fi
63+
64+ echo " $STATUS |$PUSHED_AT "
65+ }
66+
67+ echo " Fetching all direct and indirect dependencies..."
68+
69+ # Get the main module name to identify direct dependencies
70+ MAIN_MODULE=$( go list -m)
71+
72+ # Extract unique GitHub modules exactly as they are known to Go
73+ MODULES=$( go list -m all | awk ' {print $1}' | grep ' ^github\.com/' | sort -u)
74+
75+ echo " Scanning repositories..."
76+ echo " -------------------------------------------------------------------"
77+
78+ for MODULE in $MODULES ; do
79+ # 1. Run `go mod why` first to see if the dependency is actually needed
80+ WHY_OUTPUT=$( go mod why -m " $MODULE " 2> /dev/null)
81+
82+ # If the module is not needed by the main module, skip it immediately
83+ if echo " $WHY_OUTPUT " | grep -q " main module does not need module" ; then
84+ continue
85+ fi
86+
87+ # 2. Extract the base GitHub repo (owner/name) and check its status
88+ REPO=$( echo " $MODULE " | sed -E ' s|^github\.com/([^/]+)/([^/]+).*|\1/\2|' )
89+ RESULT=$( get_repo_status " $REPO " )
90+
91+ STATUS=" ${RESULT%% |* } "
92+ PUSHED_AT=" ${RESULT##* |} "
93+
94+ # 3. Process if the repository is stale or archived
95+ if [ " $STATUS " = " archived" ] || [ " $STATUS " = " stale" ]; then
96+ FORMATTED_DATE=$( echo " $PUSHED_AT " | cut -d' T' -f1)
97+
98+ REASONS=()
99+ if [ " $STATUS " = " archived" ]; then
100+ REASONS+=(" Archived" )
101+ fi
102+ if [ " $STATUS " = " stale" ]; then
103+ REASONS+=(" Inactive > 3 years" )
104+ fi
105+ REASON_STR=$( IFS=" , " ; echo " ${REASONS[*]} " )
106+
107+ # 4. Check the immediate parent module in the dependency chain
108+ # Strip comments/empty lines, get the second to last line (the parent)
109+ PARENT_MOD=$( echo " $WHY_OUTPUT " | grep -v ' ^#' | sed ' /^$/d' | tail -n 2 | head -n 1)
110+ PARENT_MSG=" "
111+
112+ if [ -n " $PARENT_MOD " ] && [ " $PARENT_MOD " != " $MODULE " ]; then
113+ if [ " $PARENT_MOD " = " $MAIN_MODULE " ]; then
114+ PARENT_MSG=" Notice: This is a direct dependency. You may want to replace it."
115+ elif [[ " $PARENT_MOD " == github.com/* ]]; then
116+ # Get the parent's base repository
117+ PARENT_REPO=$( echo " $PARENT_MOD " | sed -E ' s|^github\.com/([^/]+)/([^/]+).*|\1/\2|' )
118+
119+ # Check the parent's status
120+ PARENT_RESULT=$( get_repo_status " $PARENT_REPO " )
121+ PARENT_STATUS=" ${PARENT_RESULT%% |* } "
122+
123+ if [ " $PARENT_STATUS " = " fine" ]; then
124+ PARENT_MSG=" Notice: Brought in by '$PARENT_MOD ' which is active. This dependency is still fine."
125+ fi
126+ fi
127+ fi
128+
129+ # 5. Print the formatted report
130+ echo " [!] $MODULE "
131+ echo " Status: $REASON_STR "
132+ echo " Last updated: $FORMATTED_DATE "
133+
134+ # Output the parent notice if it exists
135+ if [ -n " $PARENT_MSG " ]; then
136+ echo " $PARENT_MSG "
137+ fi
138+
139+ echo " Dependency chain (go mod why):"
140+ echo " $WHY_OUTPUT " | sed ' s/^/ /'
141+ echo " "
142+ fi
143+ done
144+
145+ echo " Scan complete."
0 commit comments