@@ -55,99 +55,74 @@ jobs:
5555 echo "EOF"
5656 } >> "$GITHUB_OUTPUT"
5757
58- - name : Workflows on github
59- id : github
60- env :
61- GH_TOKEN : ${{ github.token }}
62- run : |
63- # Note that we filter by `.github` path prefix to ensure we only get locally defined workflows.
64- #
65- # Examples of non-local workflows are `dependabot` and `copilot` which have paths:
66- # - dynamic/dependabot/dependabot-updates
67- # - dynamic/copilot-pull-request-reviewer/copilot-pull-request-reviewer
68- WORKFLOWS=$(gh workflow list \
69- --all \
70- --json path \
71- --jq '.[] | select(.path | startswith(".github")) | .path' \
72- )
73- printf "%s\n" $WORKFLOWS
74- {
75- echo "workflows<<EOF"
76- echo "$WORKFLOWS"
77- echo "EOF"
78- } >> "$GITHUB_OUTPUT"
79-
8058 - name : Filter for deleted workflows
59+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
8160 id : deleted
8261 run : |
83- # Union of `main` and `next` workflows.
84- EXISTING_FILES=$( \
85- printf "%s\n%s\n" \
62+ set -euo pipefail
63+
64+ # Union of `main` and `next` workflows as a JSON array
65+ EXISTING_FILES=$(printf "%s\n%s\n" \
8666 "${{ steps.main.outputs.workflows }}" \
8767 "${{ steps.next.outputs.workflows }}" \
8868 )
89- EXISTING_FILES=$(echo "$EXISTING_FILES" | sort -u)
90- printf "%s\n" $EXISTING_FILES
91-
92- # Find deleted workflows as the items in `WORKFLOWS` but not in the union of main and next.
93- # This assumes that _all_ items in main and next are present in `WORKFLOWS`.
94- DELETED_FILES=$( \
95- printf "%s\n%s\n" \
96- "$EXISTING_FILES" \
97- "${{ steps.github.outputs.workflows }}" \
69+ EXISTING_JSON=$(echo "$EXISTING_FILES" | sort -u | jq -R . | jq -s .)
70+ echo "Existing workflows JSON:"
71+ echo "$EXISTING_JSON"
72+
73+ # Get workflows currently on GitHub
74+ #
75+ # Note that we filter by `.github` path prefix to ensure we only get locally defined workflows.
76+ # Examples of non-local workflows are `dependabot` and `copilot` which have paths:
77+ # - dynamic/dependabot/dependabot-updates
78+ # - dynamic/copilot-pull-request-reviewer/copilot-pull-request-reviewer
79+ GITHUB=$(gh workflow list --all --json path,name,databaseId \
80+ --jq '.[] | select(.path | startswith(".github"))'
81+ )
82+ echo "Workflows on GitHub:"
83+ echo "$GITHUB"
84+
85+ # Find deleted workflows: in GitHub but not in existing
86+ DELETED=$(jq -c \
87+ --argjson github "$GITHUB" \
88+ --argjson existing "$EXISTING_JSON" '
89+ $github | map(select(.path as $p | $existing | index($p) | not))
90+ '
9891 )
99- DELETED_FILES=$(echo "$DELETED_FILES" | sort | uniq -u)
100- printf "%s\n" $DELETED_FILES
92+ echo "Deleted workflows:"
93+ echo "$DELETED"
94+
95+ # Output to GitHub Actions
10196 {
10297 echo "workflows<<EOF"
103- echo "$DELETED_FILES "
98+ echo "$DELETED "
10499 echo "EOF"
105100 } >> "$GITHUB_OUTPUT"
106101
107102 - name : Delete runs from deleted workflows
108103 env :
109104 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
110105 MODE : ${{ inputs.mode }}
111- DELETED_WORKFLOWS : ${{ steps.deleted.outputs.workflows }}
106+ WORKFLOWS : ${{ steps.deleted.outputs.workflows }}
112107 OWNER : ${{ github.repository_owner }}
113108 REPO : ${{ github.repository }}
114109 run : |
115110 set -euo pipefail
116111
117112 TOTAL_AFFECTED=0
118113 SUMMARY=()
119-
120- # Read workflows into an array for indexing
121- mapfile -t WORKFLOWS_ARRAY <<< "$DELETED_WORKFLOWS"
122- TOTAL_WORKFLOWS=${#WORKFLOWS_ARRAY[@]}
123-
124- # Convert workflow paths to workflow names
125- WORKFLOW_NAMES_ARRAY=()
126- for wf_path in "${WORKFLOWS_ARRAY[@]}"; do
127- # Extract 'name' from YAML, fallback to filename
128- name=$(yq -r '.name // ""' "$wf_path" 2>/dev/null || true)
129- if [ -z "$name" ]; then
130- name=$(basename "$wf_path")
131- fi
132- WORKFLOW_NAMES_ARRAY+=("$name")
133- done
134-
135- # Determine max workflow name length for alignment
136- MAX_WF_LENGTH=0
137- for wf in "${WORKFLOW_NAMES_ARRAY[@]}"; do
138- len=${#wf}
139- (( len > MAX_WF_LENGTH )) && MAX_WF_LENGTH=$len
140- done
141- (( MAX_WF_LENGTH < 30 )) && MAX_WF_LENGTH=30 # minimum width
114+ CURRENT_INDEX=0
142115
143116 # Column widths
144117 INDEX_WIDTH=9
118+ MAX_WF_LENGTH=30
145119 WORKFLOW_COUNT_WIDTH=14
146120 GLOBAL_TOTAL_WIDTH=12
147-
148- # Total table width
149121 TOTAL_WIDTH=$(( INDEX_WIDTH + 3 + MAX_WF_LENGTH + 3 + WORKFLOW_COUNT_WIDTH + 3 + GLOBAL_TOTAL_WIDTH ))
150122
123+ # Count total workflows for progress display
124+ TOTAL_WORKFLOWS=$(echo "$WORKFLOWS" | jq -r '. | length')
125+
151126 # Function to print dynamic table headers padded with '=' and spaces
152127 print_header() {
153128 local name="$1"
@@ -173,48 +148,51 @@ jobs:
173148 "$WORKFLOW_COUNT_WIDTH" "$(printf '%.0s-' $(seq 1 $WORKFLOW_COUNT_WIDTH))" \
174149 "$GLOBAL_TOTAL_WIDTH" "$(printf '%.0s-' $(seq 1 $GLOBAL_TOTAL_WIDTH))"
175150
176- # Process each workflow by name
177- for i in "${!WORKFLOW_NAMES_ARRAY[@]}"; do
151+ # Loop over deleted workflows JSON
152+ echo "$WORKFLOWS" | jq -c '.[]' | while read -r wf; do
153+ CURRENT_INDEX=$((CURRENT_INDEX + 1))
154+ WORKFLOW_NAME=$(echo "$wf" | jq -r '.name')
155+ WORKFLOW_ID=$(echo "$wf" | jq -r '.databaseId')
156+ WORKFLOW_PATH=$(echo "$wf" | jq -r '.path')
157+
158+ # Safety checks
159+ if [ -z "$WORKFLOW_NAME" ]; then
160+ echo "::error title=Workflow name empty::Resolved workflow name is empty at index $CURRENT_INDEX"
161+ exit 1
162+ fi
163+ if [ -z "$WORKFLOW_ID" ]; then
164+ echo "::error title=Workflow ID missing::Workflow '$WORKFLOW_NAME' (path: $WORKFLOW_PATH) has no ID"
165+ exit 1
166+ fi
178167
179- WORKFLOW_NAME=${WORKFLOW_NAMES_ARRAY[$i]}
180- [ -z "$WORKFLOW_NAME" ] && continue
181- CURRENT_INDEX=$((i + 1))
182168 WORKFLOW_COUNT=0
183-
184- WORKFLOW_ID=$(gh workflow list --all --json id,name \
185- --jq ".[] | select(.name==\"$WORKFLOW_NAME\") | .id")
186-
187- # GraphQL pagination variables
188169 AFTER_CURSOR=null
189170
171+ # Paginate over workflow runs
190172 while true; do
191173 RESPONSE=$(gh api graphql -F workflowId="$WORKFLOW_ID" -F after="$AFTER_CURSOR" \
192174 -f query='query($workflowId: ID!, $after: String) {
193175 node(id: $workflowId) {
194176 ... on Workflow {
195177 runs(first: 100, after: $after) {
196- pageInfo {
197- hasNextPage
198- endCursor
199- }
200- nodes {
201- databaseId
202- }
178+ pageInfo { hasNextPage endCursor }
179+ nodes { databaseId }
203180 }
204181 }
205182 }
206183 }')
207- RUN_IDS=$(echo "$RESPONSE" | jq -r '.data.repository.workflowRuns.nodes[].databaseId')
208- HAS_NEXT=$(echo "$RESPONSE" | jq -r '.data.repository.workflowRuns.pageInfo.hasNextPage')
209- AFTER_CURSOR=$(echo "$RESPONSE" | jq -r '.data.repository.workflowRuns.pageInfo.endCursor')
184+
185+ RUN_IDS=$(echo "$RESPONSE" | jq -r '.data.node.runs.nodes[].databaseId')
186+ HAS_NEXT=$(echo "$RESPONSE" | jq -r '.data.node.runs.pageInfo.hasNextPage')
187+ AFTER_CURSOR=$(echo "$RESPONSE" | jq -r '.data.node.runs.pageInfo.endCursor')
210188
211189 [ -z "$RUN_IDS" ] && break
212190
213191 BATCH_COUNT=$(echo "$RUN_IDS" | wc -l | tr -d ' ')
214192 WORKFLOW_COUNT=$((WORKFLOW_COUNT + BATCH_COUNT))
215193 TOTAL_AFFECTED=$((TOTAL_AFFECTED + BATCH_COUNT))
216194
217- # Print progress line
195+ # Print progress
218196 printf "%*s | %-*s | %*s | %*s\n" \
219197 "$INDEX_WIDTH" "[$CURRENT_INDEX/$TOTAL_WORKFLOWS]" \
220198 "$MAX_WF_LENGTH" "$WORKFLOW_NAME" \
@@ -223,30 +201,15 @@ jobs:
223201
224202 if [ "$MODE" = "execute" ]; then
225203 for RUN_ID in $RUN_IDS; do
226- echo | gh run delete "$RUN_ID" >/dev/null
204+ gh run delete "$RUN_ID" >/dev/null
227205 done
228206 fi
229207
230208 [ "$HAS_NEXT" != "true" ] && break
231209
232- # TEMPORARY break for testing large workflows
210+ # TEMPORARY safety break
233211 if [ "$WORKFLOW_COUNT" -gt 200 ]; then
234212 echo " ⚠️ Temporary break: workflow count exceeded 200, stopping early."
235213 break
236214 fi
237215 done
238-
239- SUMMARY+=("$WORKFLOW_NAME|$WORKFLOW_COUNT")
240- done
241-
242- # === Summary Header ===
243- print_header "Workflow Cleanup Summary"
244- printf "%*s | %-*s | %-*s | %-*s\n" \
245- "$INDEX_WIDTH" "" \
246- "$MAX_WF_LENGTH" "Workflow" \
247- "$WORKFLOW_COUNT_WIDTH" "Runs" \
248- "$GLOBAL_TOTAL_WIDTH" ""
249- printf "%*s-+-%-*s-+-%-*s-+-%-*s\n" \
250- "$INDEX_WIDTH" "$(printf '%.0s-' $(seq 1 $INDEX_WIDTH))" \
251- "$MAX_WF_LENGTH" "$(printf '%.0s-' $(seq 1 $MAX_WF_LENGTH))" \
252- "$WORKFLOW_COUNT_WIDT
0 commit comments